package org.mdkt.compiler;
import java.util.*;
import javax.tools.*;
/**
* Compile Java sources in-memory
*/
public class InMemoryJavaCompiler {
private JavaCompiler javac;
private DynamicClassLoader classLoader;
private Iterable<String> options;
boolean ignoreWarnings = false;
private Map<String, SourceCode> sourceCodes = new HashMap<String, SourceCode>();
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<String, Class<?>> compileAll() throws Exception {
if (sourceCodes.size() == 0) {
throw new CompilationException("No source code to compile");
}
Collection<SourceCode> compilationUnits = sourceCodes.values();
CompiledCode[] code;
code = new CompiledCode[compilationUnits.size()];
Iterator<SourceCode> iter = compilationUnits.iterator();
for (int i = 0; i < code.length; i++) {
code[i] = new CompiledCode(iter.next().getClassName());
}
DiagnosticCollector<JavaFileObject> collector = new DiagnosticCollector<>();
ExtendedStandardJavaFileManager fileManager = new ExtendedStandardJavaFileManager(javac.getStandardFileManager(null, null, null), classLoader);
JavaCompiler.CompilationTask task = javac.getTask(null, fileManager, collector, options, null, compilationUnits);
boolean result = task.call();
if (!result || collector.getDiagnostics().size() > 0) {
StringBuffer exceptionMsg = new StringBuffer();
exceptionMsg.append("Unable to compile the source");
boolean hasWarnings = false;
boolean hasErrors = false;
for (Diagnostic<? extends JavaFileObject> 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<String, Class<?>> classes = new HashMap<String, Class<?>>();
for (String className : sourceCodes.keySet()) {
classes.put(className, classLoader.loadClass(className));
}
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;
}
}