diff --git a/pom.xml b/pom.xml index 42f3040..43aa20c 100644 --- a/pom.xml +++ b/pom.xml @@ -1,4 +1,5 @@ - + 4.0.0 org.mdkt.compiler @@ -35,8 +36,8 @@ https://github.com/trung/InMemoryJavaCompiler scm:git:git://github.com/trung/InMemoryJavaCompiler scm:git:git@github.com:trung/InMemoryJavaCompiler.git - HEAD - + HEAD + diff --git a/src/main/java/org/mdkt/compiler/DynamicClassLoader.java b/src/main/java/org/mdkt/compiler/DynamicClassLoader.java index ebda934..f514e47 100644 --- a/src/main/java/org/mdkt/compiler/DynamicClassLoader.java +++ b/src/main/java/org/mdkt/compiler/DynamicClassLoader.java @@ -1,6 +1,7 @@ package org.mdkt.compiler; import java.util.HashMap; +import java.util.List; import java.util.Map; /** @@ -14,7 +15,13 @@ public DynamicClassLoader(ClassLoader parent) { super(parent); } - public void setCode(CompiledCode cc) { + public void addCodes(List compiledCodes) { + for (CompiledCode cc : compiledCodes) { + customCompiledCode.put(cc.getName(), cc); + } + } + + public void addCode(CompiledCode cc) { customCompiledCode.put(cc.getName(), cc); } diff --git a/src/main/java/org/mdkt/compiler/ExtendedStandardJavaFileManager.java b/src/main/java/org/mdkt/compiler/ExtendedStandardJavaFileManager.java index 4e1cd0b..4b6f297 100644 --- a/src/main/java/org/mdkt/compiler/ExtendedStandardJavaFileManager.java +++ b/src/main/java/org/mdkt/compiler/ExtendedStandardJavaFileManager.java @@ -5,6 +5,7 @@ import javax.tools.JavaFileManager; import javax.tools.JavaFileObject; import java.io.IOException; +import java.util.List; /** * Created by trung on 5/3/15. @@ -12,28 +13,68 @@ public class ExtendedStandardJavaFileManager extends ForwardingJavaFileManager { private CompiledCode compiledCode; - private DynamicClassLoader cl; + private DynamicClassLoader classLoader; + private List compiledCodes; /** * Creates a new instance of ForwardingJavaFileManager. * * @param fileManager delegate to this file manager - * @param cl + * @param classLoader */ - protected ExtendedStandardJavaFileManager(JavaFileManager fileManager, CompiledCode compiledCode, DynamicClassLoader cl) { + protected ExtendedStandardJavaFileManager(JavaFileManager fileManager, CompiledCode compiledCode, DynamicClassLoader classLoader) { super(fileManager); this.compiledCode = compiledCode; - this.cl = cl; - this.cl.setCode(compiledCode); + this.classLoader = classLoader; + this.classLoader.addCode(compiledCode); + } + + /** + * Creates a new instance of ForwardingJavaFileManager. + * + * @param fileManager delegate to this file manager + * @param classLoader + */ + protected ExtendedStandardJavaFileManager(JavaFileManager fileManager, List compiledCodes, DynamicClassLoader classLoader) { + super(fileManager); + this.compiledCodes = compiledCodes; + this.classLoader = classLoader; + this.classLoader.addCodes(compiledCodes); } @Override public JavaFileObject getJavaFileForOutput(JavaFileManager.Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException { - return compiledCode; + + if (compiledCode != null) { + return compiledCode; + } + + return getCompiledCode(className); + } + + + private CompiledCode getCompiledCode(String className) { + for (CompiledCode compiledCode : compiledCodes) { + if (compiledCode.getName().equals(className)) { + return compiledCode; + } + } + + try { + CompiledCode innerClass = new CompiledCode(className); + compiledCodes.add(innerClass); + classLoader.addCode(innerClass); + return innerClass; + } catch (Exception e) { + e.printStackTrace(); + } + + + return null; } @Override public ClassLoader getClassLoader(JavaFileManager.Location location) { - return cl; + return classLoader; } } diff --git a/src/main/java/org/mdkt/compiler/InMemoryCompilerException.java b/src/main/java/org/mdkt/compiler/InMemoryCompilerException.java new file mode 100644 index 0000000..890e37e --- /dev/null +++ b/src/main/java/org/mdkt/compiler/InMemoryCompilerException.java @@ -0,0 +1,47 @@ +package org.mdkt.compiler; + +import javax.tools.Diagnostic; +import javax.tools.JavaFileObject; +import java.util.*; + +/************************************************** + * @author Ryan Rowe (1531352) + * 2/15/16 + * InMemoryJavaCompiler + **************************************************/ + +public class InMemoryCompilerException extends Exception { + + private List> diags; + private int line; + + /** + * Constructs a new exception with {@code null} as its detail message. + * The cause is not initialized, and may subsequently be initialized by a + * call to {@link #initCause}. + */ + public InMemoryCompilerException(List> diags) { + this.diags = diags; + line = -1; + } + + public List> getErrorList() { + List> list = new ArrayList<>(); + + for (Diagnostic diag : diags) { + Map diagnostic = new HashMap<>(); + + diagnostic.put("kind", diag.getKind()); + diagnostic.put("line", diag.getLineNumber() - line + 1); + diagnostic.put("message", diag.getMessage(Locale.US)); + + list.add(diagnostic); + } + + return list; + } + + public void setInsertLine(int line) { + this.line = line; + } +} \ No newline at end of file diff --git a/src/main/java/org/mdkt/compiler/InMemoryJavaCompiler.java b/src/main/java/org/mdkt/compiler/InMemoryJavaCompiler.java index 865fd4a..bca19c5 100644 --- a/src/main/java/org/mdkt/compiler/InMemoryJavaCompiler.java +++ b/src/main/java/org/mdkt/compiler/InMemoryJavaCompiler.java @@ -1,24 +1,71 @@ package org.mdkt.compiler; +import javax.tools.DiagnosticCollector; import javax.tools.JavaCompiler; import javax.tools.JavaFileObject; import javax.tools.ToolProvider; -import java.util.Arrays; +import java.util.*; /** * Created by trung on 5/3/15. */ public class InMemoryJavaCompiler { - static JavaCompiler javac = ToolProvider.getSystemJavaCompiler(); - - public static Class compile(String className, String sourceCodeInText) throws Exception { - SourceCode sourceCode = new SourceCode(className, sourceCodeInText); - CompiledCode compiledCode = new CompiledCode(className); - Iterable compilationUnits = Arrays.asList(sourceCode); - DynamicClassLoader cl = new DynamicClassLoader(ClassLoader.getSystemClassLoader()); - ExtendedStandardJavaFileManager fileManager = new ExtendedStandardJavaFileManager(javac.getStandardFileManager(null, null, null), compiledCode, cl); - JavaCompiler.CompilationTask task = javac.getTask(null, fileManager, null, null, null, compilationUnits); - boolean result = task.call(); - return cl.loadClass(className); + private static final Iterable options = Collections.singletonList("-Xlint:unchecked"); + private static JavaCompiler javac = ToolProvider.getSystemJavaCompiler(); + DynamicClassLoader classLoader = new DynamicClassLoader(ClassLoader.getSystemClassLoader()); + private DiagnosticCollector collector; + + public InMemoryJavaCompiler() { + this.collector = new DiagnosticCollector<>(); } -} + + Map clazzCode = new HashMap<>(); + + public void addSource(String className, String sourceCodeInText) + throws Exception { + clazzCode.put(className, new SourceCode(className, sourceCodeInText)); + } + + public Map> compileAll() throws Exception { + + Collection compilationUnits = clazzCode.values(); + List compiledCodes = new ArrayList<>(); + Iterator iterator = compilationUnits.iterator(); + for (SourceCode sourceCode : compilationUnits) { + compiledCodes.add(new CompiledCode(sourceCode.getClassName())); + } + + ExtendedStandardJavaFileManager fileManager = new ExtendedStandardJavaFileManager(javac.getStandardFileManager(null, null, null), compiledCodes, classLoader); + + JavaCompiler.CompilationTask task = javac.getTask(null, fileManager, collector, + options, null, compilationUnits); + + + try { + boolean result = task.call(); + + if (!result || collector.getDiagnostics().size() > 0) { + throw new InMemoryCompilerException(collector.getDiagnostics()); + } + + Map> classes = new HashMap>(); + for (String className : clazzCode.keySet()) { + classes.put(className, classLoader.loadClass(className)); + } + return classes; + } catch (ClassFormatError e) { + throw new InMemoryCompilerException(collector.getDiagnostics()); + } + + } + + + public Class compile(String className, String sourceCodeInText) throws Exception { + addSource(className, sourceCodeInText); + Map> compiled = compileAll(); + Class compiledClass = compiled.get(className); + return compiledClass; + } + + +} \ No newline at end of file diff --git a/src/main/java/org/mdkt/compiler/SourceCode.java b/src/main/java/org/mdkt/compiler/SourceCode.java index cfc848c..deccb3a 100644 --- a/src/main/java/org/mdkt/compiler/SourceCode.java +++ b/src/main/java/org/mdkt/compiler/SourceCode.java @@ -9,13 +9,20 @@ */ public class SourceCode extends SimpleJavaFileObject { private String contents = null; + private String className; public SourceCode(String className, String contents) throws Exception { super(URI.create("string:///" + className.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE); this.contents = contents; + this.className = className; } + @Override public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { return contents; } + + public String getClassName() { + return className; + } } diff --git a/src/test/java/org/mdkt/compiler/InMemoryJavaCompilerTest.java b/src/test/java/org/mdkt/compiler/InMemoryJavaCompilerTest.java index b20d169..f110bc7 100644 --- a/src/test/java/org/mdkt/compiler/InMemoryJavaCompilerTest.java +++ b/src/test/java/org/mdkt/compiler/InMemoryJavaCompilerTest.java @@ -3,6 +3,8 @@ import org.junit.Assert; import org.junit.Test; +import java.util.Map; + /** * Created by trung on 5/3/15. */ @@ -10,14 +12,51 @@ public class InMemoryJavaCompilerTest { @Test public void compile_whenTypical() throws Exception { + StringBuilder sourceCode = new StringBuilder(); + + sourceCode.append("package org.mdkt;\n"); + sourceCode.append("public class HelloClass {\n"); + sourceCode.append(" public String hello() { return \"hello\"; }"); + sourceCode.append("}"); + + InMemoryJavaCompiler inMemoryJavaCompiler = new InMemoryJavaCompiler(); + Class helloClass = inMemoryJavaCompiler.compile("org.mdkt.HelloClass", sourceCode.toString()); + Assert.assertNotNull(helloClass); + Assert.assertEquals(1, helloClass.getDeclaredMethods().length); + } + + + @Test + public void compile_severalFiles() throws Exception { + String cls1 = "public class A{ public B b() { return new B(); }}"; + String cls2 = "public class B{ public String toString() { return \"B!\"; }}"; + + InMemoryJavaCompiler compiler = new InMemoryJavaCompiler(); + compiler.addSource("A", cls1); + compiler.addSource("B", cls2); + Map> compiled = compiler.compileAll(); + ; + Assert.assertNotNull(compiled.get("A")); + Assert.assertNotNull(compiled.get("B")); + + Class aClass = compiled.get("A"); + Object a = aClass.newInstance(); + Assert.assertEquals("B!", aClass.getMethod("b").invoke(a).toString()); + } + + + @Test + public void compile_filesWithInnerClasses() throws Exception { StringBuffer sourceCode = new StringBuffer(); sourceCode.append("package org.mdkt;\n"); sourceCode.append("public class HelloClass {\n"); + sourceCode.append(" private static class InnerHelloWorld { int inner; }\n"); sourceCode.append(" public String hello() { return \"hello\"; }"); sourceCode.append("}"); - Class helloClass = InMemoryJavaCompiler.compile("org.mdkt.HelloClass", sourceCode.toString()); + InMemoryJavaCompiler inMemoryJavaCompiler = new InMemoryJavaCompiler(); + Class helloClass = inMemoryJavaCompiler.compile("org.mdkt.HelloClass", sourceCode.toString()); Assert.assertNotNull(helloClass); Assert.assertEquals(1, helloClass.getDeclaredMethods().length); }