Compiling HelloWorld
succeeds because it only needs its own source. When you try to compile ExtendedHelloWorld
it fails because it requires its own source and HelloWorld
's source. This can be achieved by storing each class in a HashMap<String, String>
where the key
is the class name and the value
is the class' source code.
I'd advise a couple of changes to your code.
I would deflate your compile
method and break it into two different compile methods. The first would be used when you want to compile a class that does not extend a class compiled from memory. The second would be used when you do want to compile a class that extends a class compiled from memory.
/*
* Method to compile a class which doesn't extend a class that's been compiled from memory.
*/
public static Class<?> compile(String className, String sourceCode, URLClassLoader classLoader) throws Exception {
return compileHelper(className, classLoader, Arrays.asList(new JavaSourceFromString(className, sourceCode)));
}
/*
* Method to compile a class which extends a class that's been compiled from
* memory.
*
* This method takes in the class name, a Set of Map.Entry<String, String>,
* which contains class names and their sources, and a class loader. This
* method iterates over the entries in the Set, creates JavaFileObjects from
* the class names and their sources and adds each JavaFileObject to an
* ArrayList which will be used in the 'compileHelper' method.
*/
public static Class<?> compile(String className, Set<Map.Entry<String, String>> nameAndSource, URLClassLoader classLoader) throws Exception {
List<JavaFileObject> compilationUnits = new ArrayList<>();
for(Entry<String, String> entry : nameAndSource) {
compilationUnits.add(new JavaSourceFromString(entry.getKey(), entry.getValue()));
}
return compileHelper(className, classLoader, compilationUnits);
}
The above methods then call a helper method which which actually compiles the class(es). This method closely resembles your compile
method, but the output of diagnostics have been moved to a separate method, printDiagnostics(diagnostics)
.
/*
* Helper method that actually does the compiling.
*/
private static Class<?> compileHelper(String className, URLClassLoader classLoader, Iterable<? extends JavaFileObject> compilationUnits) throws Exception {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();
CompilationTask task = null;
boolean success = false;
// debug compilation units section
System.out.println("Compiling " + className);
System.out.println(" - compilationUnits ");
for(JavaFileObject o : compilationUnits) {
System.out.println(" + " + o.toString());
}
// end debug
task = compiler.getTask(null, null, diagnostics, null, null, compilationUnits);
success = task.call();
if (success) {
System.out.println("Successful compilation of " + className);
return Class.forName(className.replace(".", "/"), true, classLoader);
} else {
System.out.println("Failed while compiling " + className);
printDiagnostics(diagnostics);
throw new Exception("It didn't work!");
}
}
In order to use the methods above you'll need to use a HashMap<String, String>
to store the class names and source codes of each class you wish to compile. Then when you're ready to compile make a call to compile
passing in the entrySet()
from the HashMap
, for instance: compile(className, nameAndSource.entrySet(), classLoader)
For example:
public static void main(String args[]) throws Exception {
Map<String, String> nameAndSource = new HashMap<>();
URLClassLoader classLoader = URLClassLoader.newInstance(new URL[] { new File("").toURI().toURL() });
String className;
StringBuilder sourceCode;
// HelloWorld class
className = "HelloWorld";
sourceCode = new StringBuilder();
sourceCode.append("public class HelloWorld {");
sourceCode.append(" public static void main(String args[]) {");
sourceCode.append(" System.out.append(\"This is in another java file\");");
sourceCode.append(" }");
sourceCode.append("}");
// pass the class name and source code to 'compile'
Class<?> helloWorld = compile(className, sourceCode.toString(), classLoader);
// add HelloWorld class name and source code to HashMap
nameAndSource.put(className, sourceCode.toString());
// ExtendedHelloWorldClass
className = "ExtendedHelloWorld";
sourceCode = new StringBuilder();
sourceCode.append("public class ExtendedHelloWorld extends HelloWorld {");
sourceCode.append(" public int num = 2;");
sourceCode.append("}");
// add ExtendedHelloWorld class name and source code to HashMap
nameAndSource.put(className, sourceCode.toString());
// here's where we pass in the nameAndSource entrySet()
Class<?> extendedHelloWorld = compile(className, nameAndSource.entrySet(), classLoader);
return;
}
Here's is the complete source code of what I tried to describe above:
public class CompileSourceInMemory {
/*
* Method to compile a class which extends a class that's been compiled from
* memory.
*
* This method takes in the class name, a Set of Map.Entry<String, String>,
* which contains class names and their sources, and a class loader. This
* method iterates over the entries in the Set, creates JavaFileObjects from
* the class names and their sources and adds each JavaFileObject to an
* ArrayList which will be used the private compile method.
*/
public static Class<?> compile(String className, Set<Map.Entry<String, String>> nameAndSource, URLClassLoader classLoader) throws Exception {
List<JavaFileObject> compilationUnits = new ArrayList<>();
for (Entry<String, String> entry : nameAndSource) {
compilationUnits.add(newJavaSourceFromString(entry.getKey(), entry.getValue()));
}
return compileHelper(className, classLoader, compilationUnits);
}
/*
* Method to compile a class which doesn't extend a class that's been
* compiled from memory.
*/
public static Class<?> compile(String className, String sourceCode, URLClassLoader classLoader) throws Exception {
return compileHelper(className, classLoader, Arrays.asList(new JavaSourceFromString(className, sourceCode)));
}
/*
* Helper method that actually does the compiling.
*/
private static Class<?> compileHelper(String className, URLClassLoader classLoader, Iterable<? extends JavaFileObject> compilationUnits) throws Exception {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();
CompilationTask task = null;
boolean success = false;
// debug compilation units section
System.out.println("Compiling " + className);
System.out.println(" - compilationUnits ");
for (JavaFileObject o : compilationUnits) {
System.out.println(" + " + o.toString());
}
// end debug
task = compiler.getTask(null, null, diagnostics, null, null, compilationUnits);
success = task.call();
if (success) {
System.out.println("Successful compilation of " + className);
return Class.forName(className.replace(".", "/"), true, classLoader);
} else {
System.out.println("Failed while compiling " + className);
printDiagnostics(diagnostics);
throw new Exception("It didn't work!");
}
}
public static void main(String args[]) throws Exception {
Map<String, String> nameAndSource = new HashMap<>();
URLClassLoader classLoader = URLClassLoader.newInstance(new URL[] { new File("").toURI().toURL() });
String className;
StringBuilder sourceCode;
// HelloWorld Class
className = "HelloWorld";
sourceCode = new StringBuilder();
sourceCode.append("public class HelloWorld {");
sourceCode.append(" public static void main(String args[]) {");
sourceCode.append(" System.out.append(\"This is in another java file\");");
sourceCode.append(" }");
sourceCode.append("}");
// pass the class name and source code to 'compile'
Class<?> helloWorld = compile(className, sourceCode.toString(), classLoader);
// add HelloWorld class name and source code to HashMap
nameAndSource.put(className, sourceCode.toString());
// ExtendedHelloWorld Class
className = "ExtendedHelloWorld";
sourceCode = new StringBuilder();
sourceCode.append("public class ExtendedHelloWorld extends HelloWorld {");
sourceCode.append(" public int num = 2;");
sourceCode.append("}");
// add ExtendedHelloWorld class name and source code to HashMap
nameAndSource.put(className, sourceCode.toString());
// pass the nameAndSource entrySet() to 'compile'
Class<?> extendedHelloWorld = compile(className, nameAndSource.entrySet(), classLoader);
// ExtendedExtendedHelloWorld Class
className = "ExtendedExtendedHelloWorld";
sourceCode = new StringBuilder();
sourceCode.append("public class ExtendedExtendedHelloWorld extends ExtendedHelloWorld {");
sourceCode.append(" public void printNum() { System.out.println(num); }");
sourceCode.append("}");
// add ExtendedExtendedHelloWorld class name and source code to HashMap
nameAndSource.put(className, sourceCode.toString());
// pass the nameAndSource entrySet() to 'compile'
Class<?> extendedExtendedHelloWorld = compile(className, nameAndSource.entrySet(), classLoader);
Object eehw = extendedExtendedHelloWorld.newInstance();
return;
}
private static void printDiagnostics(DiagnosticCollector<JavaFileObject> diagnostics) {
StringBuilder sb = new StringBuilder("-- Diagnostics --\n");
for (Diagnostic<?> d : diagnostics.getDiagnostics()) {
sb.append(String
.format("d.getCode() - %s%nd.getKind() - %s%nd.getPosition() - %d%nd.getStartPosition() - %d%nd.getEndPosition() - %d%nd.getSource() - %s%nd.getMessage(null) - %s%n",
d.getCode(), d.getKind().toString(),
d.getPosition(), d.getStartPosition(),
d.getEndPosition(), d.getSource().toString(),
d.getMessage(null)));
}
System.out.println(sb.append("--").toString());
}
}
class JavaSourceFromString extends SimpleJavaFileObject {
final String code;
JavaSourceFromString(String name, String code) {
super(URI.create("string:///" + name.replace('.', '/')
+ Kind.SOURCE.extension), Kind.SOURCE);
this.code = code;
}
@Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
return code;
}
}
Here is the output of running the code above:
Compiling HelloWorld
- compilationUnits
+ JavaSourceFromString[string:///HelloWorld.java]
Successful compilation of HelloWorld
Compiling ExtendedHelloWorld
- compilationUnits
+ JavaSourceFromString[string:///ExtendedHelloWorld.java]
+ JavaSourceFromString[string:///HelloWorld.java]
Successful compilation of ExtendedHelloWorld
Compiling ExtendedExtendedHelloWorld
- compilationUnits
+ JavaSourceFromString[string:///ExtendedHelloWorld.java]
+ JavaSourceFromString[string:///ExtendedExtendedHelloWorld.java]
+ JavaSourceFromString[string:///HelloWorld.java]
Successful compilation of ExtendedExtendedHelloWorld