[jOOQ/jOOQ#15499] Upgrade jOOR dependency to 0.9.15

This commit is contained in:
Lukas Eder 2023-08-23 12:08:38 +02:00
parent 47a71a8254
commit 05973af494
3 changed files with 164 additions and 35 deletions

View File

@ -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<String, byte[]> classes() {
if (classes == null) {
classes = new HashMap<>();
classes = new LinkedHashMap<>();
for (Entry<String, JavaFileObject> entry : fileObjectMap.entrySet())
classes.put(entry.getKey(), entry.getValue().getBytes());
@ -250,10 +261,35 @@ class Compile {
Class<?> loadAndReturnMainClass(String mainClassName, ThrowingBiFunction<String, byte[], Class<?>> definer) throws Exception {
Class<?> result = null;
for (Entry<String, byte[]> 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<Entry<String, byte[]>> 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<String, byte[]> 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;

View File

@ -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<? extends Processor> processors;
final List<String> options;
final List<String> options;
final ClassLoader classLoader;
public CompileOptions() {
this(
Collections.emptyList(),
Collections.emptyList()
Collections.emptyList(),
null
);
}
private CompileOptions(
List<? extends Processor> processors,
List<String> options
List<String> 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<? extends Processor> 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<String> 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);
}
}

View File

@ -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 {
* <p>
* For example:
* <pre><code>
* Supplier&lt;String&gt; supplier = Reflect.compile(
* "org.joor.Test",
* "package org.joor;\n" +
* "class Test implements java.util.function.Supplier&lt;String&gt; {\n" +
* " public String get() {\n" +
* " return \"Hello World!\";\n" +
* " }\n" +
* "}\n").create().get();
* Supplier&lt;String&gt; supplier = Reflect.compile("org.joor.Test", """
* package org.joor;
* class Test implements java.util.function.Supplier&lt;String&gt; {
* public String get() {
* return "Hello World!";
* }
* }
* """).create().get();
* </code></pre>
*
* @param name The qualified class name
@ -85,14 +87,14 @@ public class Reflect {
* <p>
* For example:
* <pre><code>
* Supplier&lt;String&gt; supplier = Reflect.compile(
* "org.joor.Test",
* "package org.joor;\n" +
* "class Test implements java.util.function.Supplier&lt;String&gt; {\n" +
* " public String get() {\n" +
* " return \"Hello World!\";\n" +
* " }\n" +
* "}\n").create().get();
* Supplier&lt;String&gt; supplier = Reflect.compile("org.joor.Test", """
* package org.joor;
* class Test implements java.util.function.Supplier&lt;String&gt; {
* public String get() {
* return "Hello World!";
* }
* }
* """).create().get();
* </code></pre>
*
* @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.
* <p>
* This works like {@link #compile(String, String)}, but adds the
* <code>-proc:only</code> {@link CompileOptions} and thus does not produce any
* compilation output.
* <p>
* For example:
* <pre><code>
* Supplier&lt;String&gt; supplier = Reflect.compile("org.joor.Test", """
* package org.joor;
* @MyAnnotation
* class Test implements java.util.function.Supplier&lt;String&gt; {
* public String get() {
* return "Hello World!";
* }
* }
* """).create().get();
* </code></pre>
*
* @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.
* <p>
* This works like {@link #compile(String, String)}, but adds the
* <code>-proc:only</code> {@link CompileOptions} and thus does not produce any
* compilation output.
* <p>
* For example:
* <pre><code>
* Supplier&lt;String&gt; supplier = Reflect.compile("org.joor.Test", """
* package org.joor;
* @MyAnnotation
* class Test implements java.util.function.Supplier&lt;String&gt; {
* public String get() {
* return "Hello World!";
* }
* }
* """).create().get();
* </code></pre>
*
* @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<String> 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 {}
}