diff --git a/jOOQ/src/main/java/org/jooq/tools/reflect/Compile.java b/jOOQ/src/main/java/org/jooq/tools/reflect/Compile.java index 62938b6b3c..5075e6773c 100644 --- a/jOOQ/src/main/java/org/jooq/tools/reflect/Compile.java +++ b/jOOQ/src/main/java/org/jooq/tools/reflect/Compile.java @@ -26,11 +26,12 @@ import java.lang.invoke.MethodHandles.Lookup; import java.net.URI; import java.net.URL; import java.net.URLClassLoader; -import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; -import java.util.HashMap; +import java.util.Deque; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -53,8 +54,14 @@ import javax.tools.ToolProvider; class Compile { static Class compile(String className, String content, CompileOptions compileOptions) { + return compile(className, content, compileOptions, true); + } + + static Class compile(String className, String content, CompileOptions compileOptions, boolean expectResult) { Lookup lookup = MethodHandles.lookup(); - ClassLoader cl = lookup.lookupClass().getClassLoader(); + ClassLoader cl = compileOptions.classLoader != null + ? compileOptions.classLoader + : lookup.lookupClass().getClassLoader(); try { return cl.loadClass(className); @@ -62,6 +69,9 @@ class Compile { catch (ClassNotFoundException ignore) { JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + if (compiler == null) + throw new ReflectException("No compiler was provided by ToolProvider.getSystemJavaCompiler(). Make sure the jdk.compiler module is available."); + try { ClassFileManager fileManager = new ClassFileManager(compiler.getStandardFileManager(null, null, null)); @@ -101,21 +111,22 @@ class Compile { task.call(); - if (fileManager.isEmpty()) + if (fileManager.isEmpty()) { + if (!expectResult) + return null; + throw new ReflectException("Compilation error: " + out); + } Class result = null; // This works if we have private-access to the interfaces in the class hierarchy - if (Reflect.CACHED_LOOKUP_CONSTRUCTOR != null) { - result = fileManager.loadAndReturnMainClass(className, (name, bytes) -> Reflect.on(cl).call("defineClass", name, bytes, 0, bytes.length).get()); - - } + // Lookup.defineClass() has only been introduced in Java 9. It is // required to get private-access to interfaces in the class hierarchy else { @@ -217,7 +228,7 @@ class Compile { ClassFileManager(StandardJavaFileManager standardManager) { super(standardManager); - fileObjectMap = new HashMap<>(); + fileObjectMap = new LinkedHashMap<>(); } @Override @@ -238,7 +249,7 @@ class Compile { Map classes() { if (classes == null) { - classes = new HashMap<>(); + classes = new LinkedHashMap<>(); for (Entry entry : fileObjectMap.entrySet()) classes.put(entry.getKey(), entry.getValue().getBytes()); @@ -250,10 +261,35 @@ class Compile { Class loadAndReturnMainClass(String mainClassName, ThrowingBiFunction> definer) throws Exception { Class result = null; - for (Entry entry : classes().entrySet()) { - Class c = definer.apply(entry.getKey(), entry.getValue()); - if (mainClassName.equals(entry.getKey())) - result = c; + // [#117] We don't know the subclass hierarchy of the top level + // classes in the compilation unit, and we can't find out + // without either: + // + // - class loading them (which fails due to NoClassDefFoundError) + // - using a library like ASM (which is a big and painful dependency) + // + // Simple workaround: try until it works, in O(n^2), where n + // can be reasonably expected to be small. + Deque> queue = new ArrayDeque<>(classes().entrySet()); + int n1 = queue.size(); + + // Try at most n times + for (int i1 = 0; i1 < n1 && !queue.isEmpty(); i1++) { + int n2 = queue.size(); + + for (int i2 = 0; i2 < n2; i2++) { + Entry entry = queue.pop(); + + try { + Class c = definer.apply(entry.getKey(), entry.getValue()); + + if (mainClassName.equals(entry.getKey())) + result = c; + } + catch (ReflectException e) { + queue.offer(entry); + } + } } return result; diff --git a/jOOQ/src/main/java/org/jooq/tools/reflect/CompileOptions.java b/jOOQ/src/main/java/org/jooq/tools/reflect/CompileOptions.java index 06a5637d77..e486fd6db5 100644 --- a/jOOQ/src/main/java/org/jooq/tools/reflect/CompileOptions.java +++ b/jOOQ/src/main/java/org/jooq/tools/reflect/CompileOptions.java @@ -15,6 +15,16 @@ package org.jooq.tools.reflect; +import java.util.Arrays; + +import java.util.Collections; +import java.util.List; + +import javax.annotation.processing.Processor; + +/** + * @author Lukas Eder + */ import java.util.Arrays; import java.util.Collections; @@ -28,21 +38,25 @@ import javax.annotation.processing.Processor; public final class CompileOptions { final List processors; - final List options; + final List options; + final ClassLoader classLoader; public CompileOptions() { this( Collections.emptyList(), - Collections.emptyList() + Collections.emptyList(), + null ); } private CompileOptions( List processors, - List options + List options, + ClassLoader classLoader ) { this.processors = processors; this.options = options; + this.classLoader = classLoader; } public final CompileOptions processors(Processor... newProcessors) { @@ -50,7 +64,7 @@ public final class CompileOptions { } public final CompileOptions processors(List newProcessors) { - return new CompileOptions(newProcessors, options); + return new CompileOptions(newProcessors, options, classLoader); } public final CompileOptions options(String... newOptions) { @@ -58,7 +72,19 @@ public final class CompileOptions { } public final CompileOptions options(List newOptions) { - return new CompileOptions(processors, newOptions); + return new CompileOptions(processors, newOptions, classLoader); + } + + final boolean hasOption(String opt) { + for (String option : options) + if (option.equalsIgnoreCase(opt)) + return true; + + return false; + } + + public final CompileOptions classLoader(ClassLoader newClassLoader) { + return new CompileOptions(processors, options, newClassLoader); } } diff --git a/jOOQ/src/main/java/org/jooq/tools/reflect/Reflect.java b/jOOQ/src/main/java/org/jooq/tools/reflect/Reflect.java index e7bff7bf2c..e11ab16a4e 100644 --- a/jOOQ/src/main/java/org/jooq/tools/reflect/Reflect.java +++ b/jOOQ/src/main/java/org/jooq/tools/reflect/Reflect.java @@ -23,8 +23,10 @@ import java.lang.reflect.Member; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.Proxy; +import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.Optional; @@ -61,14 +63,14 @@ public class Reflect { *

* For example: *


-     * Supplier<String> supplier = Reflect.compile(
-     *   "org.joor.Test",
-     *   "package org.joor;\n" +
-     *   "class Test implements java.util.function.Supplier<String> {\n" +
-     *   "  public String get() {\n" +
-     *   "    return \"Hello World!\";\n" +
-     *   "  }\n" +
-     *   "}\n").create().get();
+     * Supplier<String> supplier = Reflect.compile("org.joor.Test", """
+     *   package org.joor;
+     *   class Test implements java.util.function.Supplier<String> {
+     *     public String get() {
+     *       return "Hello World!";
+     *     }
+     *   }
+     *   """).create().get();
      * 
* * @param name The qualified class name @@ -85,14 +87,14 @@ public class Reflect { *

* For example: *


-     * Supplier<String> supplier = Reflect.compile(
-     *   "org.joor.Test",
-     *   "package org.joor;\n" +
-     *   "class Test implements java.util.function.Supplier<String> {\n" +
-     *   "  public String get() {\n" +
-     *   "    return \"Hello World!\";\n" +
-     *   "  }\n" +
-     *   "}\n").create().get();
+     * Supplier<String> supplier = Reflect.compile("org.joor.Test", """
+     *   package org.joor;
+     *   class Test implements java.util.function.Supplier<String> {
+     *     public String get() {
+     *       return "Hello World!";
+     *     }
+     *   }
+     *   """).create().get();
      * 
* * @param name The qualified class name @@ -105,6 +107,70 @@ public class Reflect { return onClass(Compile.compile(name, content, options)); } + /** + * Annotation-process a class at runtime. + *

+ * This works like {@link #compile(String, String)}, but adds the + * -proc:only {@link CompileOptions} and thus does not produce any + * compilation output. + *

+ * For example: + *


+     * Supplier<String> supplier = Reflect.compile("org.joor.Test", """
+     *   package org.joor;
+     *   @MyAnnotation
+     *   class Test implements java.util.function.Supplier<String> {
+     *     public String get() {
+     *       return "Hello World!";
+     *     }
+     *   }
+     *   """).create().get();
+     * 
+ * + * @param name The qualified class name + * @param content The source code for the class + * @throws ReflectException if anything went wrong compiling the class. + */ + public static void process(String name, String content) throws ReflectException { + process(name, content, new CompileOptions()); + } + + /** + * Annotation-process a class at runtime. + *

+ * This works like {@link #compile(String, String)}, but adds the + * -proc:only {@link CompileOptions} and thus does not produce any + * compilation output. + *

+ * For example: + *


+     * Supplier<String> supplier = Reflect.compile("org.joor.Test", """
+     *   package org.joor;
+     *   @MyAnnotation
+     *   class Test implements java.util.function.Supplier<String> {
+     *     public String get() {
+     *       return "Hello World!";
+     *     }
+     *   }
+     *   """).create().get();
+     * 
+ * + * @param name The qualified class name + * @param content The source code for the class + * @param options compiler options + * @return A wrapped {@link Class} + * @throws ReflectException if anything went wrong compiling the class. + */ + public static void process(String name, String content, CompileOptions options) throws ReflectException { + if (!options.hasOption("-proc:only")) { + List o = new ArrayList<>(options.options); + o.add("-proc:only"); + options = options.options(o); + } + + Compile.compile(name, content, options, false); + } + /** * Wrap a class name. @@ -1013,3 +1079,4 @@ public class Reflect { private static class NULL {} } +