package org.mdkt.compiler; import javax.tools.*; import java.util.*; /** * Compile Java sources in-memory */ public class InMemoryJavaCompiler { private JavaCompiler javac; private DynamicClassLoader classLoader; private Iterable options; boolean ignoreWarnings = false; private Map sourceCodes = new HashMap(); public static InMemoryJavaCompiler newInstance() { return new InMemoryJavaCompiler(); } private InMemoryJavaCompiler() { this.javac = ToolProvider.getSystemJavaCompiler(); this.classLoader = new DynamicClassLoader(ClassLoader.getSystemClassLoader()); } public InMemoryJavaCompiler useParentClassLoader(ClassLoader parent) { this.classLoader = new DynamicClassLoader(parent); return this; } /** * @return the class loader used internally by the compiler */ public ClassLoader getClassloader() { return classLoader; } /** * Options used by the compiler, e.g. '-Xlint:unchecked'. * * @param options * @return */ public InMemoryJavaCompiler useOptions(String... options) { this.options = Arrays.asList(options); return this; } /** * Ignore non-critical compiler output, like unchecked/unsafe operation * warnings. * * @return */ public InMemoryJavaCompiler ignoreWarnings() { ignoreWarnings = true; return this; } /** * Compile all sources * * @return Map containing instances of all compiled classes * @throws Exception */ public Map> compileAll() throws Exception { Map compiled = compileAllToBytes(); return loadCompiledBytes(compiled); } public Class loadCompiledBytes(String className, byte[] compiledClasses) throws ClassNotFoundException { Map map = new HashMap<>(); map.put(className, compiledClasses); return loadCompiledBytes(map).get(className); } public Map> loadCompiledBytes(Map compiledClasses) throws ClassNotFoundException { ClassLoader classLoader = new ClassLoader() { @Override protected Class findClass(String name) { byte[] b = compiledClasses.get(name); return defineClass(name, b, 0, b.length); } }; Map> classes = new HashMap<>(); for (String className : compiledClasses.keySet()) { classes.put(className, classLoader.loadClass(className)); } return classes; } public Map compileAllToBytes() throws Exception { if (sourceCodes.size() == 0) { throw new CompilationException("No source code to compile"); } Collection compilationUnits = sourceCodes.values(); CompiledCode[] code = new CompiledCode[compilationUnits.size()]; Iterator iter = compilationUnits.iterator(); for (int i = 0; i < code.length; i++) { code[i] = new CompiledCode(iter.next().getClassName()); } DiagnosticCollector collector = new DiagnosticCollector<>(); ExtendedStandardJavaFileManager fileManager = new ExtendedStandardJavaFileManager(javac.getStandardFileManager(null, null, null), classLoader, code); JavaCompiler.CompilationTask task = javac.getTask(null, fileManager, collector, options, null, compilationUnits); boolean result = task.call(); if (!result || collector.getDiagnostics().size() > 0) { StringBuilder exceptionMsg = new StringBuilder(); exceptionMsg.append("Unable to compile the source"); boolean hasWarnings = false; boolean hasErrors = false; for (Diagnostic d : collector.getDiagnostics()) { switch (d.getKind()) { case NOTE: case MANDATORY_WARNING: case WARNING: hasWarnings = true; break; case OTHER: case ERROR: default: hasErrors = true; break; } exceptionMsg.append("\n").append("[kind=").append(d.getKind()); exceptionMsg.append(", ").append("line=").append(d.getLineNumber()); exceptionMsg.append(", ").append("message=").append(d.getMessage(Locale.US)).append("]"); } if (hasWarnings && !ignoreWarnings || hasErrors) { throw new CompilationException(exceptionMsg.toString()); } } Map classes = new HashMap<>(); for (CompiledCode aCode : code) { classes.put(aCode.getClassName(), aCode.getByteCode()); } return classes; } /** * Compile single source * * @param className * @param sourceCode * @return * @throws Exception */ public Class compile(String className, String sourceCode) throws Exception { return addSource(className, sourceCode).compileAll().get(className); } /** * Add source code to the compiler * * @param className * @param sourceCode * @return * @throws Exception * @see {@link #compileAll()} */ public InMemoryJavaCompiler addSource(String className, String sourceCode) throws Exception { sourceCodes.put(className, new SourceCode(className, sourceCode)); return this; } }