Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<String,Class<?>> compiled = compiler.compileAll();

Class<?> aClass = compiled.get("A");


Artifact is pushed to Sonatype OSS Releases Repository

https://oss.sonatype.org/content/repositories/releases/
Expand Down
6 changes: 6 additions & 0 deletions src/main/java/org/mdkt/compiler/CompiledCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/org/mdkt/compiler/DynamicClassLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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<JavaFileManager> {

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<JavaFileManager> {

private List<CompiledCode> compiledCode = new ArrayList<CompiledCode>();
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;
}
}
83 changes: 70 additions & 13 deletions src/main/java/org/mdkt/compiler/InMemoryJavaCompiler.java
Original file line number Diff line number Diff line change
@@ -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<? extends JavaFileObject> 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<String, SourceCode> clazzCode = new HashMap<String, SourceCode>();

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<String, Class<?>> compileAll() throws Exception {
Collection<SourceCode> compilationUnits = clazzCode.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());
}

ExtendedStandardJavaFileManager fileManager = new ExtendedStandardJavaFileManager(
javac.getStandardFileManager(null, null, null), classLoader);
JavaCompiler.CompilationTask task = javac.getTask(null, fileManager,
null, null, null, compilationUnits);
boolean result = task.call();
if (!result)
throw new RuntimeException("Unknown error during compilation.");
Map<String, Class<?>> classes = new HashMap<String, Class<?>>();
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<String, Class<?>> clzzes = comp.compileAll();
Class<?> result = clzzes.get(className);
return result;
}

public DynamicClassLoader getClassLoader() {
return classLoader;
}
}
24 changes: 16 additions & 8 deletions src/main/java/org/mdkt/compiler/SourceCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
36 changes: 36 additions & 0 deletions src/test/java/org/mdkt/compiler/InMemoryJavaCompilerTest.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package org.mdkt.compiler;

import java.io.StringWriter;
import java.util.Map;

import org.junit.Assert;
import org.junit.Test;

Expand All @@ -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<String,Class<?>> 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);
}
}