Reputation: 5376
I'm attempting to retrieve the source code of any class (if available) from within a Java program for debugging purposes. Let's say I have the Class[_]
's reference to which I would like to retrieve the source code.
What I have attempted so far - in Scala:
val clazz = classOf[ClassDefinedInSeparateFile]
clazz.getProtectionDomain.getCodeSource.getLocation.toString + "/" +
clazz.getPackage.getName.replaceAll("\\.","/") + "/" + clazz.getSimpleName + ".scala"
- looks OK, the JAR is there and contains the .scala
file, but could not open using Source.fromFile(...)
."/" + clazz.getPackage.getName.replaceAll("\\.","/") + "/" + clazz.getSimpleName + ".scala"
- looks OK, but could not open using Source.fromInputStream(...)
Remarks:
.java
or .scala
files, therefore a decompiler is not necessary. (At least for the source code of the application, but not the dependencies. If a snippet is accessible of the application source code, that is enough - most exceptions are caught at the application level and relevant there.)Thanks.
Upvotes: 13
Views: 6775
Reputation: 3892
If I simply placed the source in the same folder as the class the below code worked fine for both code in the classpath and in jars
Note that there is no reason to prefix the name with "/", but you should scan for top level class.
I apologize for it being in core java but I didn't want to add any additional dependencies and wanted to be as clear as possible.
package com.stackoverflow.q53749060;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.util.Arrays;
import java.util.stream.Stream;
import org.junit.Test;
import com.stackoverflow.q53749060.Answer.Result.Found;
import com.stackoverflow.q53749060.Answer.Result.NotFound;
import com.stackoverflow.q53749060.MyTopLevelClass.MyNestedClass;
import com.stackoverflow.q53749060.MyTopLevelClassInAnotherJar.MyNestedClassInAnotherJar;
@SuppressWarnings("javadoc")
public class Answer {
static final String[] EXTENSIONS = { "java", "scala" };
@Test
public void test() {
Arrays.stream(EXTENSIONS)
.flatMap(ext -> toSource(ext, MyTopLevelClass.class, MyNestedClass.class,MyTopLevelClassInAnotherJar.class,MyNestedClassInAnotherJar.class, String.class))
.forEach(System.out::println);
}
public Stream<Result> toSource(final String extension, final Class<?>... classes) {
return Arrays.stream(classes)
.map(clazz -> toSource(extension, clazz));
}
public Result toSource(final String extension, final Class<?> clazz) {
Class<?> topLevelClass = clazz;
while (topLevelClass.getEnclosingClass() != null) {
topLevelClass = topLevelClass.getEnclosingClass();
}
final String name = topLevelClass.getName()
.replaceAll("\\.", "/") + "." + extension;
final Thread currentThread = Thread.currentThread();
final ClassLoader contextClassLoader = currentThread.getContextClassLoader();
if (contextClassLoader.getResource(name) == null) {
return new NotFound(clazz);
}
final String source = toSource(name, contextClassLoader);
return new Found(clazz, name, source);
}
public String toSource(final String name, final ClassLoader contextClassLoader) {
try (final InputStream resourceInputStream = contextClassLoader.getResourceAsStream(name);
final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
int length;
byte[] data = new byte[1024];
while ((length = resourceInputStream.read(data, 0, data.length)) != -1) {
byteArrayOutputStream.write(data, 0, length);
}
byteArrayOutputStream.flush();
byte[] byteArray = byteArrayOutputStream.toByteArray();
return new String(byteArray);
} catch (IOException ioe) {
throw new UncheckedIOException("Failed to read source file: " + name, ioe);
}
}
static class Result {
final Class<?> clazz;
Result(Class<?> clazz) {
super();
this.clazz = clazz;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((this.clazz == null) ? 0 : this.clazz.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Result other = (Result) obj;
if (this.clazz == null) {
if (other.clazz != null) {
return false;
}
} else if (!this.clazz.equals(other.clazz)) {
return false;
}
return true;
}
@Override
public String toString() {
return "Result [clazz=" + this.clazz + "]";
}
static class Found extends Result {
final String source;
final String path;
Found(Class<?> clazz, String path, String source) {
super(clazz);
this.path = path;
this.source = source;
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + ((this.source == null) ? 0 : this.source.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!super.equals(obj)) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Found other = (Found) obj;
if (this.source == null) {
if (other.source != null) {
return false;
}
} else if (!this.source.equals(other.source)) {
return false;
}
return true;
}
@Override
public String toString() {
return "Found [source=" + this.source + ", clazz=" + this.clazz + "]";
}
}
static class NotFound extends Result {
NotFound(Class<?> clazz) {
super(clazz);
}
@Override
public String toString() {
return "NotFound [clazz=" + this.clazz + "]";
}
}
}
}
Upvotes: 1
Reputation: 40510
If the source is inside the jar, which is in classpath, you need to find out where it is exactly.
clazz.getName.replaceAll("\\.", "/") + ".scala"
is an ok guess, but: (1) source code may not be in the same place as the classes - there could be a prefix (like src/
or whatever), or it could even be in a different jar, and (2) scala classes do not have to be in files with the same name - you can have several classes in one file, the file can be called foo.scala
, some classes are generated on the fly etc. Also, a package is not always a directory in scala (it could be a package object for instance).
If you know the location inside the jar (and the jar is in the classpath), the way to open it is: clazz.getClassLoader.getResourceAsStream
... but, like I said above, the trick is to figure out the location. It is not easy (and there is no single standard way to do it).
Your best bet is indeed to use an IDE. I understand that you don't have it available in production environment, but you don't really need that. What you need is the production source code available on some machine where you have an IDE, and that you can achieve with a simple scp
command.
Upvotes: 3
Reputation: 181350
You need a Java Decompiler if you want to get something similar to the original source code in Java.
You won't be able to access the source code (your executable program is made of Java bytecode, not Java Source code).
Here's a link to my favorite Java Decompiler.
Upvotes: 0