diff --git a/README.md b/README.md index b0ce5d8..78e09ab 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,19 @@ E.g.: Class helloClass = InMemoryJavaCompiler.compile("org.mdkt.HelloClass", sourceCode.toString()); +Alternatively, if you want to load several (dependent) classes: + + 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(); + + Class aClass = compiled.get("A"); + + Artifact is pushed to Sonatype OSS Releases Repository https://oss.sonatype.org/content/repositories/releases/ diff --git a/src/main/java/org/mdkt/compiler/CompiledCode.java b/src/main/java/org/mdkt/compiler/CompiledCode.java index fcd42de..e6715cc 100644 --- a/src/main/java/org/mdkt/compiler/CompiledCode.java +++ b/src/main/java/org/mdkt/compiler/CompiledCode.java @@ -11,10 +11,16 @@ */ public class CompiledCode extends SimpleJavaFileObject { private ByteArrayOutputStream baos = new ByteArrayOutputStream(); + private String className; public CompiledCode(String className) throws Exception { super(new URI(className), Kind.CLASS); + this.className = className; } + + public String getClassName() { + return className; + } @Override public OutputStream openOutputStream() throws IOException { diff --git a/src/main/java/org/mdkt/compiler/DynamicClassLoader.java b/src/main/java/org/mdkt/compiler/DynamicClassLoader.java index ebda934..a436ec2 100644 --- a/src/main/java/org/mdkt/compiler/DynamicClassLoader.java +++ b/src/main/java/org/mdkt/compiler/DynamicClassLoader.java @@ -14,7 +14,7 @@ public DynamicClassLoader(ClassLoader parent) { super(parent); } - public void setCode(CompiledCode 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..1dc8a41 100644 --- a/src/main/java/org/mdkt/compiler/ExtendedStandardJavaFileManager.java +++ b/src/main/java/org/mdkt/compiler/ExtendedStandardJavaFileManager.java @@ -1,39 +1,58 @@ package org.mdkt.compiler; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + import javax.tools.FileObject; import javax.tools.ForwardingJavaFileManager; import javax.tools.JavaFileManager; import javax.tools.JavaFileObject; -import java.io.IOException; /** - * Created by trung on 5/3/15. + * Created by trung on 5/3/15. Edited by turpid-monkey on 9/25/15, completed + * support for multiple compile units. */ -public class ExtendedStandardJavaFileManager extends ForwardingJavaFileManager { - - private CompiledCode compiledCode; - private DynamicClassLoader cl; - - /** - * Creates a new instance of ForwardingJavaFileManager. - * - * @param fileManager delegate to this file manager - * @param cl - */ - protected ExtendedStandardJavaFileManager(JavaFileManager fileManager, CompiledCode compiledCode, DynamicClassLoader cl) { - super(fileManager); - this.compiledCode = compiledCode; - this.cl = cl; - this.cl.setCode(compiledCode); - } - - @Override - public JavaFileObject getJavaFileForOutput(JavaFileManager.Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException { - return compiledCode; - } - - @Override - public ClassLoader getClassLoader(JavaFileManager.Location location) { - return cl; - } +public class ExtendedStandardJavaFileManager extends + ForwardingJavaFileManager { + + private List compiledCode = new ArrayList(); + private DynamicClassLoader cl; + + /** + * Creates a new instance of ForwardingJavaFileManager. + * + * @param fileManager + * delegate to this file manager + * @param cl + */ + protected ExtendedStandardJavaFileManager(JavaFileManager fileManager, + DynamicClassLoader cl) { + super(fileManager); + this.cl = cl; + } + + @Override + public JavaFileObject getJavaFileForOutput( + JavaFileManager.Location location, String className, + JavaFileObject.Kind kind, FileObject sibling) throws IOException { + + try { + CompiledCode innerClass = new CompiledCode(className); + compiledCode.add(innerClass); + cl.addCode(innerClass); + return innerClass; + } catch (Exception e) { + throw new RuntimeException( + "Error while creating in-memory output file for " + + className, e); + } + } + + @Override + public ClassLoader getClassLoader(JavaFileManager.Location location) { + return cl; + } } diff --git a/src/main/java/org/mdkt/compiler/InMemoryJavaCompiler.java b/src/main/java/org/mdkt/compiler/InMemoryJavaCompiler.java index 865fd4a..628fbc2 100644 --- a/src/main/java/org/mdkt/compiler/InMemoryJavaCompiler.java +++ b/src/main/java/org/mdkt/compiler/InMemoryJavaCompiler.java @@ -1,24 +1,81 @@ package org.mdkt.compiler; +import java.io.IOException; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import javax.tools.Diagnostic; +import javax.tools.DiagnosticListener; import javax.tools.JavaCompiler; import javax.tools.JavaFileObject; import javax.tools.ToolProvider; -import java.util.Arrays; /** * Created by trung on 5/3/15. + * Edited by turpid-monkey on 9/25/15, added support for multiple, dependent compile units. */ 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); - } + JavaCompiler javac; + DynamicClassLoader classLoader; + + Map clazzCode = new HashMap(); + + public InMemoryJavaCompiler(ClassLoader parent) { + this(ToolProvider.getSystemJavaCompiler(), parent); + } + + public InMemoryJavaCompiler(JavaCompiler javac, ClassLoader parent) { + this.javac = javac; + this.classLoader = new DynamicClassLoader(parent); + } + + public InMemoryJavaCompiler() { + this(ToolProvider.getSystemJavaCompiler(), ClassLoader + .getSystemClassLoader()); + } + + 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(); + CompiledCode[] code; + + code = new CompiledCode[compilationUnits.size()]; + Iterator iter = compilationUnits.iterator(); + for (int i=0; i> classes = new HashMap>(); + for (String className : clazzCode.keySet()) { + classes.put(className, classLoader.loadClass(className)); + } + return classes; + } + + public static Class compile(String className, String sourceCodeInText) + throws Exception { + InMemoryJavaCompiler comp = new InMemoryJavaCompiler(); + comp.addSource(className, sourceCodeInText); + Map> clzzes = comp.compileAll(); + Class result = clzzes.get(className); + return result; + } + + public DynamicClassLoader getClassLoader() { + return classLoader; + } } diff --git a/src/main/java/org/mdkt/compiler/SourceCode.java b/src/main/java/org/mdkt/compiler/SourceCode.java index cfc848c..9c7a9e7 100644 --- a/src/main/java/org/mdkt/compiler/SourceCode.java +++ b/src/main/java/org/mdkt/compiler/SourceCode.java @@ -8,14 +8,22 @@ * Created by trung on 5/3/15. */ public class SourceCode extends SimpleJavaFileObject { - private String contents = null; + 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; - } + 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; + } - public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { - return contents; - } + public String getClassName() { + return className; + } + + public CharSequence getCharContent(boolean ignoreEncodingErrors) + throws IOException { + return contents; + } } diff --git a/src/test/java/org/mdkt/compiler/InMemoryJavaCompilerTest.java b/src/test/java/org/mdkt/compiler/InMemoryJavaCompilerTest.java index b20d169..f00d3ea 100644 --- a/src/test/java/org/mdkt/compiler/InMemoryJavaCompilerTest.java +++ b/src/test/java/org/mdkt/compiler/InMemoryJavaCompilerTest.java @@ -1,5 +1,8 @@ package org.mdkt.compiler; +import java.io.StringWriter; +import java.util.Map; + import org.junit.Assert; import org.junit.Test; @@ -21,4 +24,37 @@ public void compile_whenTypical() throws Exception { 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()); + Assert.assertNotNull(helloClass); + Assert.assertEquals(1, helloClass.getDeclaredMethods().length); + } }