InMemoryCodeCompiler.java

package info.smart_tools.smartactors.core.class_generator_java_compile_api;

import javax.tools.Diagnostic;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.JavaFileObject;
import javax.tools.ToolProvider;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
 * Class for compile string with java code to byte code
 * in memory
 *
 * @since 1.8
 */
class InMemoryCodeCompiler {

    /**
     * System java compiler
     */
    private static JavaCompiler javac = ToolProvider.getSystemJavaCompiler();
    private DynamicClassLoader classLoader;

    /**
     * Constructor.
     * Creates instance of {@link InMemoryCodeCompiler} by given class loader
     * @param classLoader instance of {@link ClassLoader}
     */
    InMemoryCodeCompiler(final ClassLoader classLoader) {
        this.classLoader = new DynamicClassLoader(
                null != classLoader ? classLoader : ClassLoader.getSystemClassLoader()
        );
    }

    /**
     * Compile {@link String} with custom class to java byte code and represent
     * compiled class
     * @param className full name of future class
     * @param sourceCodeInText code source
     * @return compiled class
     * @throws Exception if any errors occurred
     */
    Class<?> compile(
            final String className,
            final String sourceCodeInText
    )
            throws Exception {
        try {
            return this.classLoader.loadClass(className);
        } catch (ClassNotFoundException e) { }
        try {
            List<String> optionList = new ArrayList<>();
            if (null != this.classLoader) {
                optionList.addAll(Arrays.asList("-classpath", getClassPath(this.classLoader)));
            }
            SourceCode sourceCode = new SourceCode(className, sourceCodeInText);
            CompiledCode compiledCode = new CompiledCode(className);
            List compilationUnits = Collections.singletonList(sourceCode);
            ExtendedStandardJavaFileManager fileManager = new ExtendedStandardJavaFileManager(
                    javac.getStandardFileManager(null, null, null), compiledCode, this.classLoader
            );
            DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
            CompilationTask task = javac.getTask(
                    null,
                    fileManager,
                    diagnostics,
                    optionList,
                    null,
                    compilationUnits
            );
            if (!task.call()) {
                StringBuilder s = new StringBuilder();
                for (Diagnostic<?> diagnostic : diagnostics.getDiagnostics()) {
                    s
                            .append("\n")
                            .append(diagnostic);
                }
                throw new Exception("Failed to compile " + className + s.toString());

            }
            return this.classLoader.loadClass(className);
        } catch (Throwable e) {
            throw new Exception(e);
        }
    }

    /**
     * Return all class paths as instance of {@link String} form given instance of {@link ClassLoader}
     * @param classLoader instance of {@link ClassLoader}
     * @return all class paths
     */
    private static String getClassPath(final ClassLoader classLoader) {
        ClassLoader cl = classLoader;
        StringBuilder buf = new StringBuilder();
        buf.append(".");
        String separator = System.getProperty("path.separator");
        while (null != cl) {
            try {
                URLClassLoader ucl = (URLClassLoader) cl;

                URL[] urls = ucl.getURLs();
                for (URL url : urls) {
                    String jarPathName = url.getFile();
                    if (jarPathName.startsWith("file:")) {
                        jarPathName = jarPathName.substring(
                                jarPathName.indexOf("file:") + "file:".length(), jarPathName.indexOf("!/")
                        );
                    }
                    buf.append(separator).append(
                            jarPathName
                    );
                }
            } catch (Exception e) {
                // do nothing
                // because this try-catch check cast ClassLoader to URLClassLoader
            }
            cl = cl.getParent();
        }

        return buf.toString();
    }
}