Skip to content

Commit 049f604

Browse files
authored
Merge pull request trung#2 from turpid-monkey/master
Support for multiple (dependent) compile units.
2 parents a697d3b + b3af4e7 commit 049f604

7 files changed

Lines changed: 190 additions & 51 deletions

File tree

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,19 @@ E.g.:
1717

1818
Class<?> helloClass = InMemoryJavaCompiler.compile("org.mdkt.HelloClass", sourceCode.toString());
1919

20+
Alternatively, if you want to load several (dependent) classes:
21+
22+
String cls1 = "public class A{ public B b() { return new B(); }}";
23+
String cls2 = "public class B{ public String toString() { return \"B!\"; }}";
24+
25+
InMemoryJavaCompiler compiler = new InMemoryJavaCompiler();
26+
compiler.addSource("A", cls1);
27+
compiler.addSource("B", cls2);
28+
Map<String,Class<?>> compiled = compiler.compileAll();
29+
30+
Class<?> aClass = compiled.get("A");
31+
32+
2033
Artifact is pushed to Sonatype OSS Releases Repository
2134

2235
https://oss.sonatype.org/content/repositories/releases/

src/main/java/org/mdkt/compiler/CompiledCode.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,16 @@
1111
*/
1212
public class CompiledCode extends SimpleJavaFileObject {
1313
private ByteArrayOutputStream baos = new ByteArrayOutputStream();
14+
private String className;
1415

1516
public CompiledCode(String className) throws Exception {
1617
super(new URI(className), Kind.CLASS);
18+
this.className = className;
1719
}
20+
21+
public String getClassName() {
22+
return className;
23+
}
1824

1925
@Override
2026
public OutputStream openOutputStream() throws IOException {

src/main/java/org/mdkt/compiler/DynamicClassLoader.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ public DynamicClassLoader(ClassLoader parent) {
1414
super(parent);
1515
}
1616

17-
public void setCode(CompiledCode cc) {
17+
public void addCode(CompiledCode cc) {
1818
customCompiledCode.put(cc.getName(), cc);
1919
}
2020

Lines changed: 48 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,58 @@
11
package org.mdkt.compiler;
22

3+
import java.io.FileNotFoundException;
4+
import java.io.IOException;
5+
import java.util.ArrayList;
6+
import java.util.Arrays;
7+
import java.util.List;
8+
39
import javax.tools.FileObject;
410
import javax.tools.ForwardingJavaFileManager;
511
import javax.tools.JavaFileManager;
612
import javax.tools.JavaFileObject;
7-
import java.io.IOException;
813

914
/**
10-
* Created by trung on 5/3/15.
15+
* Created by trung on 5/3/15. Edited by turpid-monkey on 9/25/15, completed
16+
* support for multiple compile units.
1117
*/
12-
public class ExtendedStandardJavaFileManager extends ForwardingJavaFileManager<JavaFileManager> {
13-
14-
private CompiledCode compiledCode;
15-
private DynamicClassLoader cl;
16-
17-
/**
18-
* Creates a new instance of ForwardingJavaFileManager.
19-
*
20-
* @param fileManager delegate to this file manager
21-
* @param cl
22-
*/
23-
protected ExtendedStandardJavaFileManager(JavaFileManager fileManager, CompiledCode compiledCode, DynamicClassLoader cl) {
24-
super(fileManager);
25-
this.compiledCode = compiledCode;
26-
this.cl = cl;
27-
this.cl.setCode(compiledCode);
28-
}
29-
30-
@Override
31-
public JavaFileObject getJavaFileForOutput(JavaFileManager.Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException {
32-
return compiledCode;
33-
}
34-
35-
@Override
36-
public ClassLoader getClassLoader(JavaFileManager.Location location) {
37-
return cl;
38-
}
18+
public class ExtendedStandardJavaFileManager extends
19+
ForwardingJavaFileManager<JavaFileManager> {
20+
21+
private List<CompiledCode> compiledCode = new ArrayList<CompiledCode>();
22+
private DynamicClassLoader cl;
23+
24+
/**
25+
* Creates a new instance of ForwardingJavaFileManager.
26+
*
27+
* @param fileManager
28+
* delegate to this file manager
29+
* @param cl
30+
*/
31+
protected ExtendedStandardJavaFileManager(JavaFileManager fileManager,
32+
DynamicClassLoader cl) {
33+
super(fileManager);
34+
this.cl = cl;
35+
}
36+
37+
@Override
38+
public JavaFileObject getJavaFileForOutput(
39+
JavaFileManager.Location location, String className,
40+
JavaFileObject.Kind kind, FileObject sibling) throws IOException {
41+
42+
try {
43+
CompiledCode innerClass = new CompiledCode(className);
44+
compiledCode.add(innerClass);
45+
cl.addCode(innerClass);
46+
return innerClass;
47+
} catch (Exception e) {
48+
throw new RuntimeException(
49+
"Error while creating in-memory output file for "
50+
+ className, e);
51+
}
52+
}
53+
54+
@Override
55+
public ClassLoader getClassLoader(JavaFileManager.Location location) {
56+
return cl;
57+
}
3958
}
Lines changed: 70 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,81 @@
11
package org.mdkt.compiler;
22

3+
import java.io.IOException;
4+
import java.util.Collection;
5+
import java.util.HashMap;
6+
import java.util.Iterator;
7+
import java.util.Map;
8+
9+
import javax.tools.Diagnostic;
10+
import javax.tools.DiagnosticListener;
311
import javax.tools.JavaCompiler;
412
import javax.tools.JavaFileObject;
513
import javax.tools.ToolProvider;
6-
import java.util.Arrays;
714

815
/**
916
* Created by trung on 5/3/15.
17+
* Edited by turpid-monkey on 9/25/15, added support for multiple, dependent compile units.
1018
*/
1119
public class InMemoryJavaCompiler {
12-
static JavaCompiler javac = ToolProvider.getSystemJavaCompiler();
13-
14-
public static Class<?> compile(String className, String sourceCodeInText) throws Exception {
15-
SourceCode sourceCode = new SourceCode(className, sourceCodeInText);
16-
CompiledCode compiledCode = new CompiledCode(className);
17-
Iterable<? extends JavaFileObject> compilationUnits = Arrays.asList(sourceCode);
18-
DynamicClassLoader cl = new DynamicClassLoader(ClassLoader.getSystemClassLoader());
19-
ExtendedStandardJavaFileManager fileManager = new ExtendedStandardJavaFileManager(javac.getStandardFileManager(null, null, null), compiledCode, cl);
20-
JavaCompiler.CompilationTask task = javac.getTask(null, fileManager, null, null, null, compilationUnits);
21-
boolean result = task.call();
22-
return cl.loadClass(className);
23-
}
20+
JavaCompiler javac;
21+
DynamicClassLoader classLoader;
22+
23+
Map<String, SourceCode> clazzCode = new HashMap<String, SourceCode>();
24+
25+
public InMemoryJavaCompiler(ClassLoader parent) {
26+
this(ToolProvider.getSystemJavaCompiler(), parent);
27+
}
28+
29+
public InMemoryJavaCompiler(JavaCompiler javac, ClassLoader parent) {
30+
this.javac = javac;
31+
this.classLoader = new DynamicClassLoader(parent);
32+
}
33+
34+
public InMemoryJavaCompiler() {
35+
this(ToolProvider.getSystemJavaCompiler(), ClassLoader
36+
.getSystemClassLoader());
37+
}
38+
39+
public void addSource(String className, String sourceCodeInText)
40+
throws Exception {
41+
clazzCode.put(className, new SourceCode(className, sourceCodeInText));
42+
}
43+
44+
public Map<String, Class<?>> compileAll() throws Exception {
45+
Collection<SourceCode> compilationUnits = clazzCode.values();
46+
CompiledCode[] code;
47+
48+
code = new CompiledCode[compilationUnits.size()];
49+
Iterator<SourceCode> iter = compilationUnits.iterator();
50+
for (int i=0; i<code.length; i++)
51+
{
52+
code[i] = new CompiledCode(iter.next().getClassName());
53+
}
54+
55+
ExtendedStandardJavaFileManager fileManager = new ExtendedStandardJavaFileManager(
56+
javac.getStandardFileManager(null, null, null), classLoader);
57+
JavaCompiler.CompilationTask task = javac.getTask(null, fileManager,
58+
null, null, null, compilationUnits);
59+
boolean result = task.call();
60+
if (!result)
61+
throw new RuntimeException("Unknown error during compilation.");
62+
Map<String, Class<?>> classes = new HashMap<String, Class<?>>();
63+
for (String className : clazzCode.keySet()) {
64+
classes.put(className, classLoader.loadClass(className));
65+
}
66+
return classes;
67+
}
68+
69+
public static Class<?> compile(String className, String sourceCodeInText)
70+
throws Exception {
71+
InMemoryJavaCompiler comp = new InMemoryJavaCompiler();
72+
comp.addSource(className, sourceCodeInText);
73+
Map<String, Class<?>> clzzes = comp.compileAll();
74+
Class<?> result = clzzes.get(className);
75+
return result;
76+
}
77+
78+
public DynamicClassLoader getClassLoader() {
79+
return classLoader;
80+
}
2481
}

src/main/java/org/mdkt/compiler/SourceCode.java

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,22 @@
88
* Created by trung on 5/3/15.
99
*/
1010
public class SourceCode extends SimpleJavaFileObject {
11-
private String contents = null;
11+
private String contents = null;
12+
private String className;
1213

13-
public SourceCode(String className, String contents) throws Exception {
14-
super(URI.create("string:///" + className.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE);
15-
this.contents = contents;
16-
}
14+
public SourceCode(String className, String contents) throws Exception {
15+
super(URI.create("string:///" + className.replace('.', '/')
16+
+ Kind.SOURCE.extension), Kind.SOURCE);
17+
this.contents = contents;
18+
this.className = className;
19+
}
1720

18-
public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
19-
return contents;
20-
}
21+
public String getClassName() {
22+
return className;
23+
}
24+
25+
public CharSequence getCharContent(boolean ignoreEncodingErrors)
26+
throws IOException {
27+
return contents;
28+
}
2129
}

src/test/java/org/mdkt/compiler/InMemoryJavaCompilerTest.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package org.mdkt.compiler;
22

3+
import java.io.StringWriter;
4+
import java.util.Map;
5+
36
import org.junit.Assert;
47
import org.junit.Test;
58

@@ -21,4 +24,37 @@ public void compile_whenTypical() throws Exception {
2124
Assert.assertNotNull(helloClass);
2225
Assert.assertEquals(1, helloClass.getDeclaredMethods().length);
2326
}
27+
28+
@Test
29+
public void compile_severalFiles() throws Exception {
30+
String cls1 = "public class A{ public B b() { return new B(); }}";
31+
String cls2 = "public class B{ public String toString() { return \"B!\"; }}";
32+
33+
InMemoryJavaCompiler compiler = new InMemoryJavaCompiler();
34+
compiler.addSource("A", cls1);
35+
compiler.addSource("B", cls2);
36+
Map<String,Class<?>> compiled = compiler.compileAll();
37+
;
38+
Assert.assertNotNull(compiled.get("A"));
39+
Assert.assertNotNull(compiled.get("B"));
40+
41+
Class<?> aClass = compiled.get("A");
42+
Object a = aClass.newInstance();
43+
Assert.assertEquals("B!", aClass.getMethod("b").invoke(a).toString());
44+
}
45+
46+
@Test
47+
public void compile_filesWithInnerClasses() throws Exception {
48+
StringBuffer sourceCode = new StringBuffer();
49+
50+
sourceCode.append("package org.mdkt;\n");
51+
sourceCode.append("public class HelloClass {\n");
52+
sourceCode.append(" private static class InnerHelloWorld { int inner; }\n");
53+
sourceCode.append(" public String hello() { return \"hello\"; }");
54+
sourceCode.append("}");
55+
56+
Class<?> helloClass = InMemoryJavaCompiler.compile("org.mdkt.HelloClass", sourceCode.toString());
57+
Assert.assertNotNull(helloClass);
58+
Assert.assertEquals(1, helloClass.getDeclaredMethods().length);
59+
}
2460
}

0 commit comments

Comments
 (0)