Java Class Dynamic Compilation and Reloading
When Texai learns a skill from a mentor, it will compose a Java method, format its source code, compile that class containing the method, and reload the revised class into the Java Virtual Machine (JVM) that is executing the local Texai instance. From various code samples and tutorials found on the web, I have assembled Java code that performs these operations. Because this is generally useful for Java developers, I am describing the steps in this post.
Dynamic compilation
The Java class to be compiled in this example is contained in the StringBuilder object stringBuilder. This set of code writes the stringBuilder to a file:
final String emittedSourceCodePath = “../data/generated/GeneratedTestImpl.java”;
final String formattedSourceCodePath = “../src/org/texai/bl/generated/GeneratedTestImpl.java”;BufferedWriter bufferedWriter = null;
try {
bufferedWriter = new BufferedWriter(new FileWriter(emittedSourceCodePath));
bufferedWriter.write(stringBuilder.toString());
} catch (IOException ex) {
fail(ex.getMessage());
} finally {
try {
bufferedWriter.close();
} catch (IOException ex) {
fail(ex.getMessage());
}
}
This bit of code formats the source code with the Jalopy code formatter.
final Jalopy jalopy = new Jalopy();
try {
jalopy.setInput(new File(emittedSourceCodePath));
jalopy.setOutput(new File(formattedSourceCodePath));
} catch (final FileNotFoundException ex) {
fail(ex.getMessage());
}
jalopy.format();
This code compiles the formatted source code and writes the class file to the destination directory:
final JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
final DiagnosticCollector<JavaFileObject> diagnosticCollector = new DiagnosticCollector<JavaFileObject>();final StandardJavaFileManager fileManager = javaCompiler.getStandardFileManager(
diagnosticCollector,
null, // default locale
null); // default character setIterable<? extends JavaFileObject> compilationUnits =
fileManager.getJavaFileObjectsFromStrings(Arrays.asList(”/home/reed/svn/BehaviorLanguage/src/org/texai/bl/generated/GeneratedTestImpl.java”));JavaCompiler.CompilationTask compilationTask = javaCompiler.getTask(
null, // output writer
fileManager,
diagnosticCollector,
Arrays.asList(”-verbose”, “-d”, “/home/reed/svn/BehaviorLanguage/build/classes”), // options
null, // classes
compilationUnits);
boolean success = compilationTask.call();
try {
fileManager.close();
} catch (final IOException ex) {
fail(ex.getMessage());
}
This code reloads the compiled class. The source code for ClassReloader.java is here.
Class clazz = null;
try {
clazz = classReloader.reloadClass(
“/home/reed/svn/BehaviorLanguage/build/classes/org/texai/bl/generated/GeneratedTestImpl.class”,
“org.texai.bl.generated.GeneratedTestImpl”);
} catch (final ClassNotFoundException ex) {
fail(ex.getMessage());
}
When designing a class for dynamic reloading, one should create an interface so that the dynamic reloaded class methods can be called without resorting to inefficient reflection. Otherwise the presence of the Class name in the source code will cause the system class loader to load the class when the containing class is loaded, not after dynamic recompilation as is desired. This code sample loads the test class and casts it to its interface. Then it is instantiated and a test method called. Note that these samples contain JUnit assertion statements that are not required in production code.
final Class[] constructorParameterTypes = {String.class};
Constructor constructor = null;
try {
constructor = clazz.getConstructor(constructorParameterTypes);
} catch (final NoSuchMethodException ex) {
fail(ex.getMessage());
}
assertNotNull(constructor);
GeneratedTest generatedTest = null;
final Object[] initargs = {”abc”};
try {
generatedTest = (GeneratedTest) constructor.newInstance(initargs);
} catch (final InstantiationException ex) {
fail(ex.getMessage());
} catch (final InvocationTargetException ex) {
fail(ex.getMessage());
} catch (final IllegalAccessException ex) {
fail(ex.getMessage());
}
assertNotNull(generatedTest);
assertEquals(”def47″, generatedTest.testMethod(”def”, 47));
ClassReloader.java source.
GeneratedTest.java source
GeneratedTestImpl.java source
BootstrapMethodsTest.java (JUnit test) source
Dave Whitten on 03 Jun 2008 at 1:50 am #
Hey Steve,
I looked this over, and wonder if your initial set of code that writes the stringBuilder to a file is able to “clean up” properly if FileWriter needs to do something in the finally clause. The scenario I’m imagining is that FileWriter is successful but BufferedWriter is not. you have a call to bufferedWriter.close(); in the finally clause, but nothing for FileWriter.
Otherwise,
Interesting bit of code. I like the way you think.
GRIN
Dave
713-870-3834