DynamicClassCompiler { public synchronized byte[] javaBytecodeFor(String qualifiedName, String sourceContent) { return javaBytecodeFor(qualifiedName, sourceContent, new HashMap<String, byte[]>()); } DynamicClassCompiler(URL[] classpath, int compliance);  DynamicClassCompiler(URL[] classpath);  DynamicClassCompiler();  DynamicClassCompiler(int compliance); synchronized byte[] javaBytecodeFor(String qualifiedName, String sourceContent); synchronized byte[] javaBytecodeFor(String qualifiedName, String sourceContent, Map<String, byte[]> compiledDependencies); synchronized Map<String, byte[]> javaBytecodeFor(Map<String, String> qualifiedNameAndContent); synchronized Map<String, byte[]> javaBytecodeFor(Map<String, String> qualifiedNameAndContent, Map<String, byte[]> compiledDependencies);  }
@Test public void compileFileWithDependencyBytecodes() throws Exception { DynamicClassCompiler dependencyCompiler = new DynamicClassCompiler(); String dependencyQualifiedName ="test.dynamic.compiler.dependency.Echo"; String dependencyCode = "package test.dynamic.compiler.dependency;" + "public class Echo {" + " public static String echo(String message) {" + " return \"ECHO \" + message;" + " }" + "}"; DynamicClassCompiler clientCompiler = new DynamicClassCompiler(); String clientQualifiedName = "test.dynamic.compiler.client.Client"; String clientCode = "package test.dynamic.compiler.client;" + "import static test.dynamic.compiler.dependency.Echo.*;" + "public class Client {" + " @Override" + " public String toString() {" + " return echo(\"response\");" + " }" + "}"; byte[] dependencyCompilation = dependencyCompiler.javaBytecodeFor(dependencyQualifiedName, dependencyCode); Map<String, byte[]> compiledDependencies = adHocMap(dependencyQualifiedName, dependencyCompilation); Map<String, String> sourceToCompile = adHocMap(clientQualifiedName, clientCode); Map<String, byte[]> clientCompilation = clientCompiler.javaBytecodeFor(sourceToCompile, compiledDependencies); assertEquals(1, clientCompilation.size()); assertFalse(clientCompilation.containsKey(dependencyQualifiedName)); clientCompilation.putAll(compiledDependencies); BytecodeClassLoader loader = BytecodeClassLoaderBuilder.loaderWith(clientCompilation); Class<?> clientClass = loader.loadClass(clientQualifiedName); Object clientInstance = clientClass.newInstance(); assertEquals("ECHO response", clientInstance.toString()); }
@Test public void compileModifiyAndRecompileWithSameCompiler() throws Exception { DynamicClassCompiler compiler = new DynamicClassCompiler(); String qualifiedName ="test.dynamic.compiler.Translator"; String sourceCode = "package test.dynamic.compiler;" + "public class Translator {" + " public static String translate(String message) {" + " if (message.equalsIgnoreCase(\"hola\"))" + " return \"hello\";" + " return \"unknown word\";" + " }" + "}"; byte[] compilation = compiler.javaBytecodeFor(qualifiedName, sourceCode); BytecodeClassLoader loader = BytecodeClassLoaderBuilder.loaderWith(qualifiedName, compilation); Class<?> translatorClass = loader.loadClass(qualifiedName); Object translator = translatorClass.newInstance(); assertEquals("hello", translatorClass.getMethod("translate", String.class).invoke(translator, "hola")); assertEquals("unknown word", translatorClass.getMethod("translate", String.class).invoke(translator, "saludos")); sourceCode = "package test.dynamic.compiler;" + "public class Translator {" + " public static String translate(String message) {" + " if (message.equalsIgnoreCase(\"hola\"))" + " return \"hello\";" + " if (message.equalsIgnoreCase(\"saludos\"))" + " return \"greetings\";" + " return \"unknown word\";" + " }" + "}"; compilation = compiler.javaBytecodeFor(qualifiedName, sourceCode); loader = BytecodeClassLoaderBuilder.loaderWith(qualifiedName, compilation); translatorClass = loader.loadClass(qualifiedName); translator = translatorClass.newInstance(); assertEquals("hello", translatorClass.getMethod("translate", String.class).invoke(translator, "hola")); assertEquals("greetings", translatorClass.getMethod("translate", String.class).invoke(translator, "saludos")); }
@Test public void jarCreationFromCompilation() throws Exception { String qualifiedName = "test.dynamic.compiler.HelloWorld"; String code = "package test.dynamic.compiler;" + "public class HelloWorld {" + " @Override" + " public String toString() {" + " return \"Hello World!\";" + " }" + "}"; DynamicClassCompiler compiler = new DynamicClassCompiler(); byte[] compilation = compiler.javaBytecodeFor(qualifiedName, code); File jarFile = jarFileFor(adHocMap(qualifiedName, compilation), "HelloWorld"); URLClassLoader loader = new URLClassLoader(new URL[] { jarFile.toURI().toURL() }); Class<?> newClass = loader.loadClass(qualifiedName); loader.close(); jarFile.delete(); Object newInstance = newClass.newInstance(); assertEquals("Hello World!", newInstance.toString()); }
@Test public void jarCreationAndLoadingInSystem() throws Exception { String qualifiedName = "test.dynamic.compiler.DynamicClass"; String code = "package test.dynamic.compiler;" + "public class DynamicClass {" + " @Override" + " public String toString() {" + " return \"Successfully loaded in system\";" + " }" + "}"; DynamicClassCompiler compiler = new DynamicClassCompiler(); byte[] compilation = compiler.javaBytecodeFor(qualifiedName, code); File jarFile = jarFileFor(adHocMap(qualifiedName, compilation), "DynamicClass"); URL[] newClasspath = new URL[] { jarFile.toURI().toURL() }; ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); checkException(true, systemClassLoader, qualifiedName); String originalClasspath = JavaLibrary.systemClasspath(); JavaLibrary.extendSystemClasspathWith(newClasspath); checkException(true, systemClassLoader, qualifiedName); JavaLibrary.setClasspath(originalClasspath); JavaLibrary.extendSystemClassLoaderClasspathWith(newClasspath); Class<?> loaded = checkException(false, systemClassLoader, qualifiedName); jarFile.delete(); Object newInstance = loaded.newInstance(); assertEquals("Successfully loaded in system", newInstance.toString()); }
@Test public void jarCreationWithDependency() throws Exception { String dependencyName = "test.dynamic.newjar.Printer"; String dependencyCode = "package test.dynamic.newjar;" + "public class Printer {" + " public String print(Object object) {" + " return \"An object printed by a dependency\";" + " }" + "}"; String qualifiedName = "test.dynamic.compiler.HelloWorld"; String code = "package test.dynamic.compiler;" + "import test.dynamic.newjar.Printer;" + "public class HelloWorld {" + " @Override" + " public String toString() {" + " return new Printer().print(this);" + " }" + "}"; DynamicClassCompiler compiler = new DynamicClassCompiler(); Map<String, String> qualifiedNameAndContent = adHocMap(asList(dependencyName, qualifiedName), asList(dependencyCode, code)); Map<String, byte[]> bytecodes = compiler.javaBytecodeFor(qualifiedNameAndContent); File jarFile = jarFileFor(bytecodes, "HelloWorldAndPrinter"); URLClassLoader loader = new URLClassLoader(new URL[] { jarFile.toURI().toURL() }); Class<?> newClass = loader.loadClass(qualifiedName); Object newInstance = newClass.newInstance(); assertEquals("An object printed by a dependency", newInstance.toString()); loader.close(); jarFile.delete(); }
@Test public void compilationFromClasspath() throws Exception { String dependencyName = "test.dynamic.greetings.Greet"; String dependencyCode = "package test.dynamic.greetings;" + "public class Greet {" + " private Object object;" + " public Greet(Object object) {" + " this.object = object;" + " }" + " @Override" + " public String toString() {" + " return \"Hello my dearest, \" + object;" + " }" + "}"; String qualifiedName = "test.dynamic.compiler.PoliteObject"; String code = "package test.dynamic.compiler;" + "import test.dynamic.greetings.Greet;" + "public class PoliteObject {" + " public String greet(Object object) {" + " return new Greet(object).toString();" + " }" + "}"; DynamicClassCompiler compiler = new DynamicClassCompiler(); byte[] dependency = compiler.javaBytecodeFor(dependencyName, dependencyCode); File jarFile = jarFileFor(adHocMap(dependencyName, dependency), "Greet"); URL[] classpath = new URL[] { jarFile.toURI().toURL() }; DynamicClassCompiler otherCompiler = new DynamicClassCompiler(classpath); byte[] otherCompilation = otherCompiler.javaBytecodeFor(qualifiedName, code); BytecodeClassLoader bytecodeLoader = BytecodeClassLoaderBuilder.loaderWith(qualifiedName, otherCompilation, classpath); Class<?> newClass = bytecodeLoader.loadClass(qualifiedName); Object newInstance = newClass.newInstance(); assertEquals("Hello my dearest, 1", newClass.getMethod("greet", Object.class).invoke(newInstance, 1)); assertEquals("Hello my dearest, true", newClass.getMethod("greet", Object.class).invoke(newInstance, true)); jarFile.delete(); }
@Test public void compilingWithDependenciesComesBeforeClasspath() throws Exception { String dependencyName = "test.dynamic.math.Calculator"; String dependencyCode = "package test.dynamic.math;" + "public class Calculator {" + " public int sum(int a, int b) {" + " return a + b;" + " }"; String newDependencyCode = dependencyCode + " public int multiply(int a, int b) {" + " return a * b;" + " }" + "}"; DynamicClassCompiler compiler = new DynamicClassCompiler(); byte[] firstDependencyVersion = compiler.javaBytecodeFor(dependencyName, dependencyCode + "}"); byte[] secondDependencyVersion = compiler.javaBytecodeFor(dependencyName, newDependencyCode); File firstVersionJar = jarFileFor(adHocMap(dependencyName, firstDependencyVersion), "Calculator"); File secondVersionJar = jarFileFor(adHocMap(dependencyName, secondDependencyVersion), "CalculatorExtended"); String qualifiedName = "test.dynamic.math.Mathematician"; String code = "package test.dynamic.math;" + "import test.dynamic.math.Calculator;" + "public class Mathematician {" + " private Calculator calculator;" + " public Mathematician(Calculator calculator) {" + " this.calculator = calculator;" + " }" + " public int sum(int a, int b) {" + " return calculator.sum(a, b);" + " }" + " public int multiply(int a, int b) {" + " return calculator.multiply(a, b);" + " }" + "}"; DynamicClassCompiler otherCompiler = new DynamicClassCompiler(new URL[] { secondVersionJar.toURI().toURL() }); checkException(true, otherCompiler, qualifiedName, code, adHocMap(dependencyName, firstDependencyVersion)); otherCompiler = new DynamicClassCompiler(new URL[] { firstVersionJar.toURI().toURL() }); byte[] compilation = checkException(false, otherCompiler, qualifiedName, code, adHocMap(dependencyName, secondDependencyVersion)); URLClassLoader parentLoader = new URLClassLoader(new URL[] { secondVersionJar.toURI().toURL() }); BytecodeClassLoader loader = BytecodeClassLoaderBuilder.loaderWith(qualifiedName, compilation, parentLoader); Class<?> dependencyClass = parentLoader.loadClass(dependencyName); Class<?> newClass = loader.loadClass(qualifiedName); Object newInstance = newClass.getConstructor(dependencyClass).newInstance(dependencyClass.newInstance()); assertEquals(10, newClass.getMethod("sum", int.class, int.class).invoke(newInstance, 4, 6)); assertEquals(24, newClass.getMethod("multiply", int.class, int.class).invoke(newInstance, 4, 6)); parentLoader.close(); firstVersionJar.delete(); secondVersionJar.delete(); }
@Test public void resolveClassNameClashesByPackage() throws Exception { String qualifiedNameA = "a.b.c.Clash"; String qualifiedNameB = "x.y.z.Clash"; String srcClassA = "package a.b.c;" + "public class Clash {}"; String srcClassB = "package x.y.z;" + "public class Clash {}"; DynamicClassCompiler compiler = new DynamicClassCompiler(); byte[] compilationA = compiler.javaBytecodeFor(qualifiedNameA, srcClassA); File jar = jarFileFor(adHocMap(qualifiedNameA, compilationA), "nameClash"); URL[] classpath = new URL[] { jar.toURI().toURL() }; String qualifiedName = "p.ack.age.Client"; String srcClass = "package p.ack.age;" + "import a.b.c.Clash;" + "public class Client {" + " public Clash field = new Clash();" + "}"; DynamicClassCompiler otherCompiler = new DynamicClassCompiler(classpath); otherCompiler.javaBytecodeFor(qualifiedNameB, srcClassB); byte[] compilation = null; try { compilation = otherCompiler.javaBytecodeFor(qualifiedName, srcClass); } catch (Exception e) { jar.delete(); fail("Should not happen: compilation failed because of two classes with same simple name but different package"); } ClassLoader loader = BytecodeClassLoaderBuilder.loaderWith(qualifiedName, compilation, classpath); Class<?> cLass = loader.loadClass(qualifiedName); Field field = cLass.getField("field"); jar.delete(); assertEquals(qualifiedNameA, field.getType().getName()); }