web-dev-qa-db-fra.com

Obtenir le code source de n'importe quelle classe à partir d'un programme Java

J'essaie de récupérer le code source de n'importe quelle classe (si disponible) à partir d'un programme Java à des fins de débogage. Disons que j'ai la référence de Class[_] à laquelle j'aimerais récupérer le code source.

Ce que j'ai essayé jusqu'à présent - en Scala:

val clazz = classOf[ClassDefinedInSeparateFile]

  1. clazz.getProtectionDomain.getCodeSource.getLocation.toString + "/" + clazz.getPackage.getName.replaceAll("\\.","/") + "/" + clazz.getSimpleName + ".scala" - semble correct, le fichier JAR est présent et contient le fichier .scala, mais n'a pas pu s'ouvrir à l'aide de Source.fromFile(...).
  2. "/" + clazz.getPackage.getName.replaceAll("\\.","/") + "/" + clazz.getSimpleName + ".scala" - semble OK, mais n'a pas pu s'ouvrir avec Source.fromInputStream(...)

Remarques:

  • Il n'y a pas de IDE disponible dans les environnements de production ou de transfert.
  • Dans notre configuration, les fichiers JAR contiennent les fichiers de code source .Java ou .scala; par conséquent, aucun décompilateur n'est nécessaire. (Au moins pour le code source de l'application, mais pas pour les dépendances. Si un extrait du code source de l'application est accessible, cela suffit: la plupart des exceptions sont interceptées au niveau de l'application et pertinentes.)

Merci.

11
Dyin

Si la source est dans le fichier jar, qui est dans classpath, vous devez savoir où il se trouve exactement. 

clazz.getName.replaceAll("\\.", "/") + ".scala" est une hypothèse correcte, mais : (1) le code source peut ne pas être au même endroit que les classes - il peut y avoir un préfixe (comme src/ ou autre), ou même dans un autre pot, et (2) il n'est pas nécessaire que les classes scala soient dans des fichiers portant le même nom - vous pouvez avoir plusieurs classes dans un seul fichier, le fichier peut s'appeler foo.scala, certaines classes sont générées à la volée, etc. De plus, un package n'est pas toujours un répertoire dans scala (par exemple, un objet de package). 

Si vous connaissez l'emplacement dans le pot (et que celui-ci se trouve dans le chemin de classe), le moyen de l'ouvrir est le suivant: clazz.getClassLoader.getResourceAsStream ... mais, comme je l'ai dit plus haut, l'astuce consiste à déterminer l'emplacement. Ce n'est pas facile (et il n'y a pas de moyen standard unique de le faire). 

Votre meilleur pari est en effet d'utiliser un IDE. Je comprends que vous ne l’avez pas disponible dans un environnement de production, mais vous n’avez pas vraiment besoin de ça. Ce dont vous avez besoin, c'est du code source de production disponible sur une machine sur laquelle vous avez un IDE, et que vous pouvez obtenir avec une simple commande scp.

3
Dima

Si j'ai simplement placé la source dans le même dossier que la classe, le code ci-dessous a bien fonctionné, que ce soit dans le classpath ou dans les fichiers JAR.

Notez qu'il n'y a aucune raison de préfixer le nom avec "/", mais vous devez rechercher la classe de niveau supérieur.

Je m'excuse de l'avoir dans le noyau Java, mais je ne voulais pas ajouter de dépendances supplémentaires et je voulais être aussi clair que 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 + "]";
            }

        }
    }
}
1
Jeff

Java Decompiler est nécessaire si vous voulez obtenir quelque chose de similaire au code source original en Java.

Vous ne pourrez pas accéder au code source (votre programme exécutable est constitué de bytecode Java , pas de code source Java).

Voici un lien vers mon favori Java Decompiler .

0
Pablo Santa Cruz