How to Generate and Compile Sources at Runtime in Java
With ClassFactory, it is possible to generate classes at runtime, but how can we do that if we only need to compile sources without loading them or just generating sources?
Here come the source code generator components and the JavaMemoryCompiler of Burningwave Core to our aid. With source code generators, we can generate source code and store it on the drive or compile it via the JavaMemoryCompiler.
Now, let's try to generate and store the following class:
x
package source.generation.test;
import java.util.Arrays;
import java.util.List;
public class MyClass {
private List<String> words;
public MyClass(String... words) {
this.words = Arrays.asList(words);
}
public void print() {
System.out.print(String.join(" ", words));
}
public static void main(String[] args) {
new MyClass(args).print();
}
}
With the source code generators:
xxxxxxxxxx
package source.generation.test;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.List;
import org.burningwave.core.classes.ClassSourceGenerator;
import org.burningwave.core.classes.FunctionSourceGenerator;
import org.burningwave.core.classes.GenericSourceGenerator;
import org.burningwave.core.classes.TypeDeclarationSourceGenerator;
import org.burningwave.core.classes.UnitSourceGenerator;
import org.burningwave.core.classes.VariableSourceGenerator;
public class SourceGenerationTester {
public static UnitSourceGenerator generate() {
return UnitSourceGenerator.create(SourceGenerationTester.class.getPackage().getName())
.addClass(
ClassSourceGenerator.create(TypeDeclarationSourceGenerator.create("MyClass"))
.addField(
VariableSourceGenerator.create(
TypeDeclarationSourceGenerator.create(List.class)
.addGeneric(GenericSourceGenerator.create(String.class)),
"words"
)
)
.addConstructor(
FunctionSourceGenerator.create().addParameter(
VariableSourceGenerator.create(
TypeDeclarationSourceGenerator.create(String.class)
.setAsVarArgs(true),
"words"
)
).addBodyCodeLine("this.words = Arrays.asList(words);").useType(Arrays.class)
)
.addMethod(
FunctionSourceGenerator.create("print")
.addModifier(Modifier.PUBLIC).setReturnType(void.class)
.addBodyCodeLine(
"System.out.println(\"\\n\\t\" + String.join(\" \", words) + \"\\n\");"
)
)
.addMethod(
FunctionSourceGenerator.create("main")
.addModifier(Modifier.PUBLIC | Modifier.STATIC)
.setReturnType(void.class)
.addParameter(VariableSourceGenerator.create(String[].class, "args"))
.addBodyCodeLine("new MyClass(args).print();")
)
);
}
public static void main(String[] args) {
UnitSourceGenerator unitSG = SourceGenerationTester.generate();
unitSG.storeToClassPath(System.getProperty("user.home") + "/Desktop/sources");
System.out.println("\nGenerated code:\n" + unitSG);
}
}
And now let's try to compile the sources and store the compiled files with JavaMemoryCompiler:
x
package source.compilation.test;
import org.burningwave.core.assembler.ComponentContainer;
import org.burningwave.core.classes.JavaMemoryCompiler;
import org.burningwave.core.classes.JavaMemoryCompiler.Compilation;
import org.burningwave.core.concurrent.QueuedTasksExecutor.ProducerTask;
import org.burningwave.core.io.FileSystemItem;
import source.generation.test.SourceGenerationTester;
public class SourceCompilationTester {
public static void main(String[] args) throws ClassNotFoundException {
ComponentContainer componentContainer = ComponentContainer.getInstance();
JavaMemoryCompiler javaMemoryCompiler = componentContainer.getJavaMemoryCompiler();
ProducerTask<Compilation.Result> compilationTask = javaMemoryCompiler.compile(
Compilation.Config.forUnitSourceGenerator(
SourceGenerationTester.generate()
)
.storeCompiledClassesTo(
System.getProperty("user.home") + "/Desktop/classes"
)
);
Compilation.Result compilationResult = compilationTask.join();
System.out.println("\n\tAbsolute path of compiled file: " +
compilationResult.getClassPath()
.findFirstInAllChildren(
FileSystemItem.Criteria.forAllFileThat(FileSystemItem::isFile)
).getAbsolutePath() + "\n"
);
}
}
And now let's try to load the compiled file:
xxxxxxxxxx
package source.compilation.test;
import static org.burningwave.core.assembler.StaticComponentContainer.ClassLoaders;
import static org.burningwave.core.assembler.StaticComponentContainer.Methods;
import org.burningwave.core.assembler.ComponentContainer;
import org.burningwave.core.classes.JavaMemoryCompiler;
import org.burningwave.core.classes.JavaMemoryCompiler.Compilation;
import org.burningwave.core.concurrent.QueuedTasksExecutor.ProducerTask;
import source.generation.test.SourceGenerationTester;
public class SourceCompilationTester {
public static void main(String[] args) throws ClassNotFoundException {
ComponentContainer componentContainer = ComponentContainer.getInstance();
JavaMemoryCompiler javaMemoryCompiler = componentContainer.getJavaMemoryCompiler();
ProducerTask<Compilation.Result> compilationTask = javaMemoryCompiler.compile(
Compilation.Config.forUnitSourceGenerator(
SourceGenerationTester.generate()
)
.storeCompiledClassesTo(
System.getProperty("user.home") + "/Desktop/classes"
)
);
Compilation.Result compilattionResult = compilationTask.join();
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
ClassLoaders.addClassPaths(classLoader, compilattionResult.getDependencies());
ClassLoaders.addClassPath(classLoader, compilattionResult.getClassPath().getAbsolutePath());
Class<?> cls = classLoader.loadClass("source.generation.test.MyClass");
Methods.invokeStaticDirect(cls, "main", new Object[] {new String[] {"Hello", "world!"}});
}
}
Conclusion
In this tutorial, we learned how to generate and compile sources and how to load the generated class files. You can do a lot of things that are not mentioned here like, for example, compile sources that references classes located outside the runtime class paths. If you are curious or need further help, you can request it at the official forum.