Как заставить JavaCompiler.CompilationTask использовать собственный ClassLoader или использовать файлы .class для пропуска файлов .java? - PullRequest
0 голосов
/ 14 мая 2018

Более конкретно: я написал свой собственный ClassLoader, который загружает

  1. .jar файлов из глобального пути к библиотеке
  2. .jar файлов из конкретного пути проекта
  3. компилирует .java файлы в пути, специфичном для проекта
  4. загружает все .class файлы в пути, специфичном для проекта

Пока что это отделяет экземпляры моего проекта, все нормально загружаетсявсе (под) каталоги, работает для (1) всех библиотек и (2) под-библиотек, (3) может компилировать все .java файлы, (4) может загружать .class файлы, и я также могу повторно создавать экземпляры классовкоторые уже загружены, потому что мои ClassLoader управляют суб-ClassLoader с, чтобы разрешить это.

Теперь, что я хочу улучшить , так это в (3), когда я вызываюкомпилятор, я не хочу перекомпилировать каждый .java файл в каталоге, а только те, где соответствующий файл .class не существует или имеет неправильную временную метку.

Так что я передаю только те .java файлы, которые нужно перекомпилировать, а не все, с последующимкомпилятор не может найти все необходимые классы (внутри тех файлов .java, которые я не передал ему для компиляции).Скорее, компилятор должен получить недостающую информацию о компиляции (.class файлов вместо .java файлов) из моего ClassLoader, который уже загрузил эти .class файлы.

Чтобы понять это, я реализовал свойвладею FileManager, которую я передаю JavaCompiler.getTask().В рамках этого пользовательского FileManager я возвращаю свой ClassLoader в FileManager.getClassLoader().

Должно выглядеть следующим образом:

  1. .jar файлы из глобального пути к библиотеке
  2. .jar файлы из конкретного пути проекта
  3. компилирует только некоторые .java файлы по конкретному пути проекта, загружая отсутствующие определения классов из .class файлов (загруженных моим конкретным ClassLoader)
  4. загружает все .class файлы по конкретному пути проекта

Но при запуске JavaCompiler.CompilationTask никогда не обращается к методам .loadClass() или .findClass() моего ClassLoader, поэтому не находит необходимые файлы .class, таким образом 'выдает мне ошибку компиляции.(то есть я получаю диагностику и превращаю ее в исключение)

Итак, мой актуальный вопрос:

  • Не нарушена ли моя концепция?
  • Возможно ли это вообще?Или компилятор не может использовать .class файлы вместо .java файлов?
  • Есть ли какая-то хитрость?

ОК, так что яЯ собираюсь опубликовать части кода, который я написал, но он использует много моих библиотек и много внутренних классов, поэтому вы не получите этот код для работы !

Также обратите внимание, что это оченьболее запутанный, потому что я храню много дополнительной информации, которая не понадобится для задачи, о которой я просил.

Так что используйте это в качестве руководства. По сути, вы можете опустить всеобработка информации, где я храню данные в Картах / Мультикартах, в основном для отслеживания ресурсов.

Кроме того, JcDirLoader не должен расширять ClassLoader для работы, которая также предназначена для "более высокой" цели; -) И способ, которым ClassLoaders вызывают друг друга, также может быть значительно упрощен!

package jc.lib.lang.reflect.loader;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashSet;

import com.sun.org.apache.xalan.internal.xsltc.compiler.CompilerException;

import jc.lib.collection.list.JcList;
import jc.lib.collection.map.JcHashMap;
import jc.lib.collection.map.JcMultiMap;
import jc.lib.io.files.finder.JcFileFinder;
import jc.lib.lang.JcUFile;
import jc.lib.lang.JcUFileType;
import jc.lib.lang.reflect.classfileanalyzer.JcClassFileInfo;
import jc.lib.lang.reflect.compiler.JcJavaFileCompiler;
import jc.lib.lang.reflect.loader.classes.JcClassFileLoader;
import jc.lib.lang.reflect.loader.classes.JcUClassLoader;
import jc.lib.lang.reflect.loader.jars.JcUJarfileLoader;
import jc.lib.lang.reflect.loader.util.ClassName;
import jc.lib.lang.reflect.loader.util.ClassState;
import jc.lib.lang.reflect.loader.util.JcClassLoaderInfo;



public class JcDirLoader extends ClassLoader {



    private final JcHashMap<File, ClassName>                mFile2Classname         = new JcHashMap<>();
    private final JcHashMap<ClassName, JcClassLoaderInfo>   mClassname2Classinfo    = new JcHashMap<>();

    private final JcMultiMap<ClassName, JcClassFileLoader>  mClassname2Loaders      = new JcMultiMap<>();
    private final ArrayList<JcClassFileLoader>              mAvailableFileLoaders   = new ArrayList<>();

    private final ClassLoader mParentLoader;

    public JcDirLoader() {
        //      super(JcUClassLoader.getThreadContextClassLoader());
        // JcDirLoader.class.getClassLoader();
        mParentLoader = JcUClassLoader.getThreadContextClassLoader();
    }



    public JcList<JcClassLoaderInfo> getLoadedClasses() {
        final JcList<JcClassLoaderInfo> ret = new JcList<>(mClassname2Classinfo.values());
        return ret;
    }

    public Class<?> forName(final ClassName pClassName) throws ClassNotFoundException {
        final JcClassLoaderInfo ret = mClassname2Classinfo.get(pClassName);
        if (ret != null) return ret.mClass;

        final Class<?> ret2 = mParentLoader.loadClass(pClassName.toString());
        return ret2;
    }

    public JcClassLoaderInfo getClassInfo(final ClassName pClassName) {
        return mClassname2Classinfo.get(pClassName);
    }
    public ArrayList<JcClassLoaderInfo> getClassInfos() {
        return new ArrayList<>(mClassname2Classinfo.values());
    }



    public void loadDirectory(final File pDir) throws ClassNotFoundException, IOException, CompilerException {
        // collect all files
        final JcList<File> files = JcFileFinder.findInDir(pDir, true);
        final JcList<File> jarFiles = new JcList<>();
        final JcList<File> classFiles = new JcList<>();
        final JcList<File> javaFiles = new JcList<>();
        for (final File file : files) {
            if (JcUFileType.isJavaPackedFile(file)) jarFiles.addItem(file);
            if (JcUFileType.isJavaClassFile(file)) classFiles.addItem(file);
            if (JcUFileType.isJavaSourceFile(file)) javaFiles.addItem(file);
        }

        // handle .jar files
        handleJars(jarFiles);

        // compile .java files
        final boolean reloadNecessary = handleJavaFiles(javaFiles, jarFiles);
        if (reloadNecessary) {
            classFiles.removeAllItems();
            final JcList<File> files2 = JcFileFinder.findInDir(pDir, true);
            for (final File file : files2) {
                if (JcUFileType.isJavaClassFile(file)) classFiles.addItem(file);
            }
        }

        // handle .class files
        handleClassFiles(classFiles);
    }



    /*
     * .jar files
     */
    private void handleJars(final JcList<File> pAvailableJarFiles) throws MalformedURLException, IOException {
        System.out.println("\tLoading " + pAvailableJarFiles.getItemCount() + " .jar files:");
        final JcList<JcClassLoaderInfo> loadedClasses = JcUJarfileLoader.loadJars(pAvailableJarFiles);
        for (final JcClassLoaderInfo ci : loadedClasses) {
            mFile2Classname.put(ci.mContainingFile, ci.mClassName);
            mClassname2Classinfo.put(ci.mClassName, ci);
        }
        for (final File file : pAvailableJarFiles) {
            System.out.println("\t\t" + file + " OK");
        }
        System.out.println("\t\tAll OK");
    }



    /*
     * .class files
     */
    private ArrayList<Class<?>> handleClassFiles(final JcList<File> pClassFiles) throws FileNotFoundException, IOException, ClassNotFoundException {
        System.out.println("\tLoading " + pClassFiles.getItemCount() + " .class files:");

        for (final File file : pClassFiles) {
            final JcClassFileInfo cfi = new JcClassFileInfo(file);
            final ClassName className = ClassName.fromClassFileInfo(cfi);

            final JcClassLoaderInfo info = new JcClassLoaderInfo(null, file, className, null, ClassState.CLASS_FILE, file.lastModified(), null);
            mFile2Classname.put(file, className);
            mClassname2Classinfo.put(className, info);
        }

        final ArrayList<Class<?>> ret = new ArrayList<>();
        for (final File file : pClassFiles) {
            final Class<?> cls = handleClassFile(file);
            ret.add(cls);
        }

        System.out.println("\t\t" + pClassFiles.getItemCount() + " .class files loaded.");
        return ret;
    }
    private Class<?> handleClassFile(final File pFile) throws FileNotFoundException, IOException, ClassNotFoundException {
        final JcClassFileInfo cfi = new JcClassFileInfo(pFile);
        final ClassName className = ClassName.fromClassFileInfo(cfi);
        final JcClassLoaderInfo existing = mClassname2Classinfo.get(className);
        if (!needsReloading(existing, pFile)) return existing.mClass;

        final JcClassFileLoader fileLoader = getCompatibleFileLoader(className);
        fileLoader.setInfo(pFile, className);
        final Class<?> c = fileLoader.loadClass(className.toString());

        final JcClassLoaderInfo info = new JcClassLoaderInfo(null, pFile, className, c, ClassState.CLASS_FILE, pFile.lastModified(), fileLoader);
        mFile2Classname.put(pFile, className);
        mClassname2Classinfo.put(className, info);

        return c;
    }
    static private boolean needsReloading(final JcClassLoaderInfo pExisting, final File pNewFile) {
        if (pExisting == null) return true;
        if (pExisting.mClass == null) return true;
        if (!pNewFile.equals(pExisting.mContainingFile)) return true;
        if (pNewFile.lastModified() != pExisting.mContainingFile.lastModified()) return true;
        return false;
    }



    /*
     * .java files
     */
    private boolean handleJavaFiles(final JcList<File> pJavaFiles, final JcList<File> pAvailableJarFiles) throws IOException, CompilerException {
        System.out.println("\tChecking " + pJavaFiles.getItemCount() + " .java files:");

        final boolean recompile = needRecompiling(pJavaFiles);
        if (!recompile) {
            System.out.println("\t\tNo Java files needed recompiling.");
            return false;
        }

        final JcList<File> javaFilesToCompile = pJavaFiles;

        // info
        System.out.print("\t\tRecompiling files: ");
        for (final File file : javaFilesToCompile) {
            System.out.print(file.getName() + " ");
        }
        System.out.println();

        // recompile
        final String[] jarFileNames = new String[pAvailableJarFiles.getItemCount()];
        for (int i = 0; i < pAvailableJarFiles.getItemCount(); i++) {
            final File jarFile = pAvailableJarFiles.getItem(i);
            jarFileNames[i] = jarFile.getAbsolutePath();
        }
        JcJavaFileCompiler.compileFiles(pJavaFiles.toArray(), this, jarFileNames);

        // set time of compiled files to match dates
        for (final File f : pJavaFiles) {
            final long timestamp = f.lastModified();
            final File cls = getClassfileForSourcefile(f);
            cls.setLastModified(timestamp);
        }

        // return results
        final JcList<JcClassLoaderInfo> ret = new JcList<>();
        for (final File file : javaFilesToCompile) {
            final File classFile = getClassfileForSourcefile(file);
            if (!classFile.exists()) throw new FileNotFoundException("File '" + classFile.getAbsolutePath() + "' could not be found, but was compiled from '" + file.getAbsolutePath() + "'!");

            final JcClassLoaderInfo ci = new JcClassLoaderInfo(file, classFile, null, null, ClassState.JAVA_FILE, classFile.lastModified(), null);
            ret.addItem(ci);
        }

        System.out.println("\t\t" + pJavaFiles.getItemCount() + " Java files recompiled.");
        return true;
    }
    static private boolean needRecompiling(final JcList<File> pJavaFiles) {
        for (final File file : pJavaFiles) {
            if (!JcUFileType.isJavaSourceFile(file)) continue;

            final long sourceDate = file.lastModified();
            final File classFile = getClassfileForSourcefile(file);
            final long classDate = !classFile.exists() ? 0 : classFile.lastModified();
            if (sourceDate > classDate) return true;
        }

        return false;
    }

    private JcClassFileLoader getCompatibleFileLoader(final ClassName pClassName) {
        // check if can re-use another existing loader
        final HashSet<JcClassFileLoader> oldLoaders = mClassname2Loaders.getUniqueValues(pClassName);
        for (final JcClassFileLoader loader : mAvailableFileLoaders) {
            if (oldLoaders.contains(loader)) continue;

            mClassname2Loaders.put(pClassName, loader);
            //          System.out.println("\tUsing " + loader + " for " + pClassName);
            return loader;
        }

        // create new loader
        final JcClassFileLoader newLoader = new JcClassFileLoader(mParentLoader, this);
        mAvailableFileLoaders.add(newLoader);
        mClassname2Loaders.put(pClassName, newLoader);
        //      System.out.println("Created new " + newLoader + " for " + pClassName);
        return newLoader;
    }



    static public File getClassfileForSourcefile(final File pSourceFile) {
        final String classFilename = JcUFile.toString(pSourceFile, true, true, true, false) + JcUFileType.CLASS_EXTENSION;
        final File classFile = new File(classFilename);
        return classFile;
    }



    @Override public Class<?> loadClass(final String pClassname) throws ClassNotFoundException {
        System.out.println(" -> JcDirLoader.loadClass(" + pClassname + ")");
        try {
            final ClassName cn = ClassName.fromString(pClassname);
            final JcClassLoaderInfo ci = getClassInfo(cn);
            if (ci == null) return mParentLoader.loadClass(pClassname);

            final File f = ci.mContainingFile;
            final Class<?> cls = handleClassFile(f);
            return cls;

        } catch (final Exception e) {
            throw new ClassNotFoundException(pClassname, e);
        }
    }

    @Override public Class<?> findClass(final String pClassname) throws ClassNotFoundException {
        System.out.println(" -> JcDirLoader.findClass(" + pClassname + ")");

        final ClassName cn = ClassName.fromString(pClassname);
        final JcClassLoaderInfo ci = getClassInfo(cn);
        if (ci != null) return ci.mClassLoader.loadClass(pClassname);

        final Class<?> test = loadClass(pClassname);
        if (test != null) return test;

        System.out.println("XXX -> " + pClassname);
        return super.findClass(pClassname);
    }

    @Override public URL getResource(final String pName) {
        return super.getResource(pName);
    }



}

и более код

public class JcUJarfileLoader {

    static public JcList<JcClassLoaderInfo> loadJars(final JcList<File> pJarFiles) throws MalformedURLException, IOException {
        final JcList<JcClassLoaderInfo> ret = new JcList<>();
        if (pJarFiles == null || pJarFiles.getItemCount() < 1) return ret;

        // convert files to URL to make all jars available to all requests
        final ArrayList<URL> urls = new ArrayList<>(pJarFiles.getItemCount());
        for (final File jarFile : pJarFiles) {
            final URL url = jarFile.toURI().toURL();
            urls.add(url);
        }
        final URL[] urlArr = urls.toArray(new URL[0]);

        // iterate through jar, load all inner classes
        try (final URLClassLoader classLoader = new URLClassLoader(urlArr);) { //
            for (final File jarFile : pJarFiles) {
                try (final JarFile file = new JarFile(jarFile);) {
                    final Enumeration<JarEntry> entries = file.entries();
                    while (entries.hasMoreElements()) {
                        final JarEntry jarEntry = entries.nextElement();
                        if (!JcUFileType.isJavaClassFile(jarEntry)) continue;

                        try {
                            //                          System.out.println("reloading1 " + jarEntry);
                            final ClassName className = ClassName.fromZipEntry(jarEntry);
                            final Class<?> cls = classLoader.loadClass(className.toString());
                            final JcClassLoaderInfo i = new JcClassLoaderInfo(null, jarFile, className, cls, ClassState.CLASS_FILE_IN_JAR, jarFile.lastModified(), classLoader);
                            ret.addItem(i);

                        } catch (final ClassNotFoundException e2) {
                            System.out.println("JcUJarfileLoader.reloadJar(e2) " + e2);
                        }
                    }
                }
            }
        }

        return ret;
    }



}

и более

package jc.lib.lang.reflect.compiler;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;

import javax.tools.Diagnostic;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;

import com.sun.org.apache.xalan.internal.xsltc.compiler.CompilerException;

import jc.lib.lang.JcUFile;
import jc.lib.lang.reflect.loader.JcDirLoader;
import jc.lib.lang.string.JcUString;



public class JcJavaFileCompiler {



    static public void compileCode(final String pCode) throws IOException, CompilerException {
        final File tempFile = File.createTempFile("jccompiler_", ".java");
        try {
            JcUFile.writeString_UTF8(tempFile, pCode);
            compileFiles(new File[] { tempFile }, null);

        } finally {
            tempFile.deleteOnExit();
        }
    }

    static public void compileFiles(final File pFiles[], @SuppressWarnings("unused") final JcDirLoader pClassLoader_Nullable, final String... pBindingLibraries) throws IOException, CompilerException {
        if (pFiles == null || pFiles.length < 1) return;
        for (final File f : pFiles) {
            if (!f.exists()) throw new FileNotFoundException(f.getAbsolutePath());
        }

        final DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
        final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        if (compiler == null) throw new NoClassDefFoundError("ToolProvider.getSystemJavaCompiler() cannot find a compiler! Make sure you're running on JDK or have linked tools.jar into the classpath!");

        try (final StandardJavaFileManager fileManager2 = compiler.getStandardFileManager(diagnostics, null, null);
        /*JcJavaFileManager fileManager = new JcJavaFileManager(pClassLoader_Nullable, fileManager2);*/) {
            String addLibs = "";
            if (pBindingLibraries != null) for (final String l : pBindingLibraries) {
                if (l == null || l.length() < 1) continue;
                addLibs += ";" + l;
            }
            final ArrayList<String> optionList = new ArrayList<>();
            optionList.add("-classpath");
            optionList.add(System.getProperty("java.class.path") + addLibs);

            final Iterable<? extends JavaFileObject> compilationUnit = /*fileManager*/fileManager2.getJavaFileObjectsFromFiles(Arrays.asList(pFiles));
            final JavaCompiler.CompilationTask task = compiler.getTask(null, /*fileManager*/ fileManager2, diagnostics, optionList, null, compilationUnit);
            final boolean done = task.call().booleanValue();
            if (done) return;

            // collect error data and throw
            final StringBuilder sb = new StringBuilder();
            for (final Diagnostic<? extends JavaFileObject> diagnostic : diagnostics.getDiagnostics()) {
                sb.append(diagnostic + "\n");
            }
            throw new CompilerException(sb.toString());

        } catch (final IOException e) {
            e.printStackTrace();
            throw e;
        }
    }

    static public String getClassNameFromJavaCode(final File pJavaFile) throws IOException {
        String code = JcUFile.loadString(pJavaFile);
        code = JcUString.removeJavaComments(code);

        final String pkg = JcUString.getBetween(code, "package ", ";");
        final String name = JcUFile.toString(pJavaFile, false, true, false, false);
        return pkg + "." + name;
    }

    static public Class<?> getClass(final String pFullClassName) throws ClassNotFoundException, IOException {
        try (final URLClassLoader classLoader = new URLClassLoader(new URL[] { new File("./").toURI().toURL() });) {
            final Class<?> loadedClass = classLoader.loadClass(pFullClassName);
            return loadedClass;
        }
    }



}

последний файлна сегодня

package jc.lib.lang.reflect.loader.classes;

import java.io.File;
import java.io.IOException;

import jc.lib.lang.JcUFile;
import jc.lib.lang.reflect.loader.JcDirLoader;
import jc.lib.lang.reflect.loader.util.ClassName;
import jc.lib.lang.string.JcUString;



/**
 * Our Custom Class Loader to load the classes. Any class in the com.journaldev
 * package will be loaded using this ClassLoader. For other classes, it will
 * delegate the request to its Parent ClassLoader.
 *
 */
public class JcClassFileLoader extends ClassLoader {



    private final JcDirLoader mJcDirLoader;

    private File        mFile;
    private ClassName   mClassname;

    public JcClassFileLoader(final ClassLoader parent, final JcDirLoader pJcDirLoader) {
        super(parent);
        mJcDirLoader = pJcDirLoader;
    }



    public void setInfo(final File pFile, final ClassName pClassname) {
        mFile = pFile;
        mClassname = pClassname;
    }



    @Override public Class<?> loadClass(final String pClassname) throws ClassNotFoundException {
        //      System.out.println(" *** JcClassFileLoader.loadClass(" + pClassname + ") primed with (" + mFile + "," + mClassname + ")");
        if (!JcUString.equals(pClassname, mClassname.toString())) return super.loadClass(pClassname);

        try {
            final byte[] b = JcUFile.readBytes(mFile);
            final Class<?> c = defineClass(mClassname.toString(), b, 0, b.length);
            resolveClass(c);
            //          System.out.println("LOADED: " + c.getSimpleName() + "\t" + c.getName() + "\t" + c.getPackage());
            return c;

        } catch (final LinkageError e) {
            throw new LinkageError("Error while loading file '" + mFile + "' as Class '" + mClassname + "'", e);
        } catch (final IOException e) {
            e.printStackTrace();
            return null;
        }
    }



    @Override protected Class<?> findClass(final String pName) throws ClassNotFoundException {
        System.out.println(" *** JcClassFileLoader.findClass(" + pName + ")");
        return mJcDirLoader.findClass(pName);
    }



}

1 Ответ

0 голосов
/ 25 мая 2018

Eclipse имеет инкрементный компилятор, который можно использовать автономно, его можно адаптировать к вашим потребностям. Я не думаю, что можно делать то, что вы пытаетесь со стандартным javac, но было бы интересно узнать об этом мысли людей из компилятора оракула. ​​

В Gradle также имеется инкрементный компилятор, хотя не уверен, насколько легко будет его повторно использовать.

...