Dyin
Dyin

Reputation: 5376

Get source code of any class from within a Java program

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]

  1. 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(...).
  2. "/" + clazz.getPackage.getName.replaceAll("\\.","/") + "/" + clazz.getSimpleName + ".scala" - looks OK, but could not open using Source.fromInputStream(...)

Remarks:

Thanks.

Upvotes: 13

Views: 6775

Answers (3)

Jeff
Jeff

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

Dima
Dima

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

Pablo Santa Cruz
Pablo Santa Cruz

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

Related Questions