From 0031a344c6e4235f615cc7078f7b8933490dd3df Mon Sep 17 00:00:00 2001 From: lukaseder Date: Wed, 4 Apr 2018 22:14:31 +0300 Subject: [PATCH] [#7385] Upgrade jOOR dependency to 0.9.8 --- .../java/org/jooq/tools/reflect/Compile.java | 164 +++++++++++++ .../java/org/jooq/tools/reflect/Reflect.java | 220 ++++++++++++++---- 2 files changed, 336 insertions(+), 48 deletions(-) create mode 100644 jOOQ/src/main/java/org/jooq/tools/reflect/Compile.java diff --git a/jOOQ/src/main/java/org/jooq/tools/reflect/Compile.java b/jOOQ/src/main/java/org/jooq/tools/reflect/Compile.java new file mode 100644 index 0000000000..a0da75b402 --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/tools/reflect/Compile.java @@ -0,0 +1,164 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jooq.tools.reflect; + + + +import java.io.ByteArrayOutputStream; +import java.io.OutputStream; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodHandles.Lookup; +import java.net.URI; +import java.util.ArrayList; +import java.util.List; + +import javax.tools.FileObject; +import javax.tools.ForwardingJavaFileManager; +import javax.tools.JavaCompiler; +import javax.tools.JavaFileManager; +import javax.tools.SimpleJavaFileObject; +import javax.tools.StandardJavaFileManager; +import javax.tools.ToolProvider; + +// import static java.lang.StackWalker.Option.RETAIN_CLASS_REFERENCE; + + +/** + * A utility that simplifies in-memory compilation of new classes. + * + * @author Lukas Eder + */ +class Compile { + + static Class compile(String className, String content) { + Lookup lookup = MethodHandles.lookup(); + + try { + return lookup.lookupClass().getClassLoader().loadClass(className); + } + catch (ClassNotFoundException ignore) { + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + + try { + ClassFileManager fileManager = new ClassFileManager(compiler.getStandardFileManager(null, null, null)); + + List files = new ArrayList(); + files.add(new CharSequenceJavaFileObject(className, content)); + + compiler.getTask(null, fileManager, null, null, null, files).call(); + Class result = null; + + // This works if we have private-access to the interfaces in the class hierarchy + // if (Reflect.CACHED_LOOKUP_CONSTRUCTOR != null) { + ClassLoader cl = lookup.lookupClass().getClassLoader(); + byte[] b = fileManager.o.getBytes(); + result = Reflect.on(cl).call("defineClass", className, b, 0, b.length).get(); + // } + /* [java-9] */ + + // Lookup.defineClass() has only been introduced in Java 9. It is + // required to get private-access to interfaces in the class hierarchy + // else { + // + // // This method is called by client code from two levels up the current stack frame + // // We need a private-access lookup from the class in that stack frame in order to get + // // private-access to any local interfaces at that location. + // Class caller = StackWalker + // .getInstance(RETAIN_CLASS_REFERENCE) + // .walk(s -> s + // .skip(2) + // .findFirst() + // .get() + // .getDeclaringClass()); + // + // // If the compiled class is in the same package as the caller class, then + // // we can use the private-access Lookup of the caller class + // if (className.startsWith(caller.getPackageName() + ".")) { + // result = MethodHandles + // .privateLookupIn(caller, lookup) + // .defineClass(fileManager.o.getBytes()); + // } + // + // // Otherwise, use an arbitrary class loader. This approach doesn't allow for + // // loading private-access interfaces in the compiled class's type hierarchy + // else { + // result = new ClassLoader() { + // @Override + // protected Class findClass(String name) throws ClassNotFoundException { + // byte[] b = fileManager.o.getBytes(); + // return defineClass(className, b, 0, b.length); + // } + // }.loadClass(className); + // } + // } + /* [/java-9] */ + + return result; + } + catch (Exception e) { + throw new ReflectException("Error while compiling " + className, e); + } + } + } + + static final class JavaFileObject extends SimpleJavaFileObject { + final ByteArrayOutputStream os = new ByteArrayOutputStream(); + + JavaFileObject(String name, JavaFileObject.Kind kind) { + super(URI.create("string:///" + name.replace('.', '/') + kind.extension), kind); + } + + byte[] getBytes() { + return os.toByteArray(); + } + + @Override + public OutputStream openOutputStream() { + return os; + } + } + + static final class ClassFileManager extends ForwardingJavaFileManager { + JavaFileObject o; + + ClassFileManager(StandardJavaFileManager standardManager) { + super(standardManager); + } + + @Override + public JavaFileObject getJavaFileForOutput( + JavaFileManager.Location location, + String className, + JavaFileObject.Kind kind, + FileObject sibling + ) { + return o = new JavaFileObject(className, kind); + } + } + + static final class CharSequenceJavaFileObject extends SimpleJavaFileObject { + final CharSequence content; + + public CharSequenceJavaFileObject(String className, CharSequence content) { + super(URI.create("string:///" + className.replace('.', '/') + JavaFileObject.Kind.SOURCE.extension), JavaFileObject.Kind.SOURCE); + this.content = content; + } + + @Override + public CharSequence getCharContent(boolean ignoreEncodingErrors) { + return content; + } + } +} + 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 492afdc77b..b6ea6d4c8c 100644 --- a/jOOQ/src/main/java/org/jooq/tools/reflect/Reflect.java +++ b/jOOQ/src/main/java/org/jooq/tools/reflect/Reflect.java @@ -1,6 +1,4 @@ /* - * Copyright (c) 2011-2017, Data Geekery GmbH (http://www.datageekery.com) - * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -15,6 +13,7 @@ */ package org.jooq.tools.reflect; +import java.lang.invoke.MethodHandles; import java.lang.reflect.AccessibleObject; import java.lang.reflect.Constructor; import java.lang.reflect.Field; @@ -45,6 +44,7 @@ import java.util.Map; * * @author Lukas Eder * @author Irek Matysiewicz + * @author Thomas Darimont */ public class Reflect { @@ -52,6 +52,32 @@ public class Reflect { // Static API used as entrance points to the fluent API // --------------------------------------------------------------------- + + /** + * Compile a class at runtime and reflect on it. + *

+ * 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();
+     * 
+ * + * @param name The qualified class name + * @param content The source code for the class + * @return A wrapped {@link Class} + * @throws ReflectException if anything went wrong compiling the class. + */ + public static Reflect compile(String name, String content) throws ReflectException { + return on(Compile.compile(name, content)); + } + + /** * Wrap a class name. *

@@ -66,6 +92,23 @@ public class Reflect { return on(forName(name)); } + /** + * Wrap a class name, loading it via a given class loader. + *

+ * This is the same as calling + * on(Class.forName(name, classLoader)) + * + * @param name A fully qualified class name. + * @param classLoader The class loader in whose context the class should be + * loaded. + * @return A wrapped class object, to be used for further reflection. + * @throws ReflectException If any reflection exception occurred. + * @see #on(Class) + */ + public static Reflect on(String name, ClassLoader classLoader) throws ReflectException { + return on(forName(name, classLoader)); + } + /** * Wrap a class. *

@@ -90,7 +133,11 @@ public class Reflect { * @return A wrapped object, to be used for further reflection. */ public static Reflect on(Object object) { - return new Reflect(object); + return new Reflect(object == null ? Object.class : object.getClass(), object); + } + + private static Reflect on(Class type, Object object) { + return new Reflect(type, object); } /** @@ -118,9 +165,8 @@ public class Reflect { } // [jOOQ #3392] The accessible flag is set to false by default, also for public members. - if (!accessible.isAccessible()) { + if (!accessible.isAccessible()) accessible.setAccessible(true); - } return accessible; } @@ -129,30 +175,54 @@ public class Reflect { // Members // --------------------------------------------------------------------- - /** - * The wrapped object - */ - private final Object object; + + static final Constructor CACHED_LOOKUP_CONSTRUCTOR; + + static { + Constructor result; + + /* [java-9] */ + // if (true) + // result = null; + // else + /* [/java-9] */ + try { + result = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class); + + if (!result.isAccessible()) + result.setAccessible(true); + } + + // Can no longer access the above in JDK 9 + catch (Throwable ignore) { + result = null; + } + + CACHED_LOOKUP_CONSTRUCTOR = result; + } + /** - * A flag indicating whether the wrapped object is a {@link Class} (for - * accessing static fields and methods), or any other type of {@link Object} - * (for accessing instance fields and methods). + * The type of the wrapped object. */ - private final boolean isClass; + private final Class type; + + /** + * The wrapped object. + */ + private final Object object; // --------------------------------------------------------------------- // Constructors // --------------------------------------------------------------------- private Reflect(Class type) { - this.object = type; - this.isClass = true; + this(type, type); } - private Reflect(Object object) { + private Reflect(Class type, Object object) { + this.type = type; this.object = object; - this.isClass = false; } // --------------------------------------------------------------------- @@ -176,6 +246,20 @@ public class Reflect { * wrapped object is a {@link Class}, then this will set a value to a static * member field. If the wrapped object is any other {@link Object}, then * this will set a value to an instance member field. + *

+ * This method is also capable of setting the value of (static) final + * fields. This may be convenient in situations where no + * {@link SecurityManager} is expected to prevent this, but do note that + * (especially static) final fields may already have been inlined by the + * javac and/or JIT and relevant code deleted from the runtime verison of + * your program, so setting these fields might not have any effect on your + * execution. + *

+ * For restrictions of usage regarding setting values on final fields check: + * http://stackoverflow.com/questions/3301635/change-private-static-final-field-using-java-reflection + * ... and http://pveentjer.blogspot.co.at/2017/01/final-static-boolean-jit.html * * @param name The field name * @param value The new field value @@ -185,6 +269,18 @@ public class Reflect { public Reflect set(String name, Object value) throws ReflectException { try { Field field = field0(name); + + if ((field.getModifiers() & Modifier.FINAL) == Modifier.FINAL) { + try { + Field modifiersField = Field.class.getDeclaredField("modifiers"); + modifiersField.setAccessible(true); + modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); + } + + // [#48] E.g. Android doesn't have this field + catch (NoSuchFieldException ignore) {} + } + field.set(object, unwrap(value)); return this; } @@ -228,7 +324,7 @@ public class Reflect { public Reflect field(String name) throws ReflectException { try { Field field = field0(name); - return on(field.get(object)); + return on(field.getType(), field.get(object)); } catch (Exception e) { throw new ReflectException(e); @@ -236,24 +332,24 @@ public class Reflect { } private Field field0(String name) throws ReflectException { - Class type = type(); + Class t = type(); // Try getting a public field try { - return type.getField(name); + return accessible(t.getField(name)); } // Try again, getting a non-public field catch (NoSuchFieldException e) { do { try { - return accessible(type.getDeclaredField(name)); + return accessible(t.getDeclaredField(name)); } catch (NoSuchFieldException ignore) {} - type = type.getSuperclass(); + t = t.getSuperclass(); } - while (type != null); + while (t != null); throw new ReflectException(e); } @@ -276,11 +372,11 @@ public class Reflect { */ public Map fields() { Map result = new LinkedHashMap(); - Class type = type(); + Class t = type(); do { - for (Field field : type.getDeclaredFields()) { - if (!isClass ^ Modifier.isStatic(field.getModifiers())) { + for (Field field : t.getDeclaredFields()) { + if (type != object ^ Modifier.isStatic(field.getModifiers())) { String name = field.getName(); if (!result.containsKey(name)) @@ -288,9 +384,9 @@ public class Reflect { } } - type = type.getSuperclass(); + t = t.getSuperclass(); } - while (type != null); + while (t != null); return result; } @@ -379,24 +475,24 @@ public class Reflect { * If no exact match could be found, we let the {@code NoSuchMethodException} pass through. */ private Method exactMethod(String name, Class[] types) throws NoSuchMethodException { - Class type = type(); + Class t = type(); // first priority: find a public method with exact signature match in class hierarchy try { - return type.getMethod(name, types); + return t.getMethod(name, types); } // second priority: find a private method with exact signature match on declaring class catch (NoSuchMethodException e) { do { try { - return type.getDeclaredMethod(name, types); + return t.getDeclaredMethod(name, types); } catch (NoSuchMethodException ignore) {} - type = type.getSuperclass(); + t = t.getSuperclass(); } - while (type != null); + while (t != null); throw new NoSuchMethodException(); } @@ -411,11 +507,11 @@ public class Reflect { * returned, otherwise a {@code NoSuchMethodException} is thrown. */ private Method similarMethod(String name, Class[] types) throws NoSuchMethodException { - Class type = type(); + Class t = type(); // first priority: find a public method with a "similar" signature in class hierarchy // similar interpreted in when primitive argument types are converted to their wrappers - for (Method method : type.getMethods()) { + for (Method method : t.getMethods()) { if (isSimilarSignature(method, name, types)) { return method; } @@ -423,15 +519,15 @@ public class Reflect { // second priority: find a non-public method with a "similar" signature on declaring class do { - for (Method method : type.getDeclaredMethods()) { + for (Method method : t.getDeclaredMethods()) { if (isSimilarSignature(method, name, types)) { return method; } } - type = type.getSuperclass(); + t = t.getSuperclass(); } - while (type != null); + while (t != null); throw new NoSuchMethodException("No similar method " + name + " with params " + Arrays.toString(types) + " could be found on type " + type() + "."); } @@ -515,17 +611,17 @@ public class Reflect { * @return A proxy for the wrapped object */ @SuppressWarnings("unchecked") - public

P as(Class

proxyType) { + public

P as(final Class

proxyType) { final boolean isMap = (object instanceof Map); final InvocationHandler handler = new InvocationHandler() { - @SuppressWarnings("null") @Override + @SuppressWarnings("null") public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String name = method.getName(); // Actual method name matches always come first try { - return on(object).call(name, args).get(); + return on(type, object).call(name, args).get(); } // [#14] Emulate POJO behaviour on wrapped map objects @@ -546,6 +642,30 @@ public class Reflect { } } + + if (method.isDefault()) { + + /* [java-9] */ + // Java 9 version + // if (CACHED_LOOKUP_CONSTRUCTOR == null) { + // return MethodHandles + // .privateLookupIn(proxyType, MethodHandles.lookup()) + // .in(proxyType) + // .unreflectSpecial(method, proxyType) + // .bindTo(proxy) + // .invokeWithArguments(args); + // } + /* [/java-9] */ + + // Java 8 version + return CACHED_LOOKUP_CONSTRUCTOR + .newInstance(proxyType) + .unreflectSpecial(method, proxyType) + .bindTo(proxy) + .invokeWithArguments(args); + } + + throw e; } } @@ -635,7 +755,7 @@ public class Reflect { */ private static Reflect on(Constructor constructor, Object... args) throws ReflectException { try { - return on(accessible(constructor).newInstance(args)); + return on(constructor.getDeclaringClass(), accessible(constructor).newInstance(args)); } catch (Exception e) { throw new ReflectException(e); @@ -707,18 +827,22 @@ public class Reflect { } } + private static Class forName(String name, ClassLoader classLoader) throws ReflectException { + try { + return Class.forName(name, true, classLoader); + } + catch (Exception e) { + throw new ReflectException(e); + } + } + /** * Get the type of the wrapped object. * * @see Object#getClass() */ public Class type() { - if (isClass) { - return (Class) object; - } - else { - return object.getClass(); - } + return type; } /**