All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.fiolino.common.reflection.Methods Maven / Gradle / Ivy

Go to download

General structure to easily create dynamic logic via MethodHandles and others.

There is a newer version: 1.0.10
Show newest version
package org.fiolino.common.reflection;

import org.fiolino.common.analyzing.AmbiguousTypesException;
import org.fiolino.common.ioc.Instantiator;
import org.fiolino.common.util.Strings;
import org.fiolino.common.util.Types;

import javax.annotation.Nullable;
import java.lang.invoke.*;
import java.lang.reflect.*;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.*;
import java.util.concurrent.Semaphore;
import java.util.function.*;
import java.util.logging.Level;
import java.util.logging.Logger;

import static java.lang.invoke.MethodHandles.*;
import static java.lang.invoke.MethodType.methodType;

/**
 * Box of several arbitary utility methods around MethodHandles.
 * 

* Created by Michael Kuhlmann on 14.12.2015. */ public class Methods { private static final Logger logger = Logger.getLogger(Methods.class.getName()); /** * Finds the getter method for a given field. * Tries to find getters in this order: * 1. By looking up a method getFieldname with the certain return type * 2. If the field is of type boolean, tries to find a getter named isFieldname * 3. Looks for a getter method fieldname, i.e. with the exact same name as the field * 4. Tries to access the field directly through this lookup. *

* If none of these apply, then it returns null. * * @param field The field * @param additionalTypes The handle will accept these on top * @return The found {@link MethodHandle} of type (<owner>)<type>, or null otherwise */ @Nullable public static MethodHandle findGetter(Field field, Class... additionalTypes) { return findGetter(publicLookup().in(field.getDeclaringClass()), field, additionalTypes); } /** * Finds the getter method for a given field. * Tries to find getters in this order: * 1. By looking up a method getFieldname with the certain return type * 2. If the field is of type boolean, tries to find a getter named isFieldname * 3. Looks for a getter method fieldname, i.e. with the exact same name as the field * 4. Tries to access the field directly through this lookup. *

* If none of these apply, then it returns null. * * @param lookup The lookup * @param field The field * @param additionalTypes The handle will accept these on top * @return The found {@link MethodHandle} of type (<owner>)<type>, or null otherwise */ @Nullable public static MethodHandle findGetter(Lookup lookup, Field field, Class... additionalTypes) { if (Modifier.isStatic(field.getModifiers())) { return null; } Class type = field.getType(); Class owner = field.getDeclaringClass(); return findGetter(lookup, owner, field.getName(), type, additionalTypes); } /** * Finds the getter method for a given field in the given owner class. * Tries to find getters in this order: * 1. By looking up a method getFieldname with the certain return type * 2. If the field is of type boolean, tries to find a getter named isFieldname * 3. Looks for a getter method fieldname, i.e. with the exact same name as the field * 4. Tries to access the field of that name directly through this lookup. *

* If none of these apply, then it returns null. * * @param owner Where to look for getters * @param fieldName The plain field name * @param type The getter's expected return type * @param additionalTypes The handle will accept these on top * @return The found {@link MethodHandle} of type (<owner>)<type>, or null otherwise */ @Nullable public static MethodHandle findGetter(Class owner, String fieldName, Class type, Class... additionalTypes) { return findGetter(publicLookup().in(owner), owner, fieldName, type, additionalTypes); } /** * Finds the getter method for a given field in the given owner class. * Tries to find getters in this order: * 1. By looking up a method getFieldname with the certain return type * 2. If the field is of type boolean, tries to find a getter named isFieldname * 3. Looks for a getter method fieldname, i.e. with the exact same name as the field * 4. Tries to access the field of that name directly through this lookup. *

* If none of these apply, then it returns null. * * @param lookup The lookup * @param owner Where to look for getters * @param fieldName The plain field name * @param type The getter's expected return type * @param additionalTypes The handle will accept these on top * @return The found {@link MethodHandle} of type (<owner>)<type>, or null otherwise */ @Nullable public static MethodHandle findGetter(Lookup lookup, Class owner, String fieldName, Class type, Class... additionalTypes) { String name = Strings.addLeading(fieldName, "get"); return attachTo(onTop -> { String n = name; MethodHandle h = findGetter0(lookup, owner, n, type, onTop); if (h == null && (type == boolean.class || type == Boolean.class)) { n = fieldName; if (!n.startsWith("is") || n.length() > 2 && Character.isLowerCase(n.charAt(2))) { n = Strings.addLeading(fieldName, "is"); } h = findGetter0(lookup, owner, n, type, onTop); } if (h == null) { // Look for a method with the exact same name h = findGetter0(lookup, owner, fieldName, type, onTop); } return h; }, additionalTypes, () -> { // Look for the direct field getter try { return lookup.findGetter(owner, fieldName, type); } catch (NoSuchFieldException | IllegalAccessException ex) { // Then the field's just not accessible, or there is no such field return null; } }); } private static MethodHandle attachTo(Function[], MethodHandle> handleFactory, Class[] additionalTypes, Supplier alternative) { int missing = 0; MethodHandle handle; int n = additionalTypes.length; do { Class[] onTop = Arrays.copyOf(additionalTypes, n-missing); handle = handleFactory.apply(onTop); } while (handle == null && missing++ < n); if (handle != null) { return handle; } return alternative.get(); } /** * Finds the setter method for a given field. * Tries to find setters in this order: * 1. By looking up a method setFieldname with the certain argument type * 2. Looks for a setter method fieldname, i.e. with the exact same name as the field * 3. Tries to access the field directly through this lookup. *

* It will only look up virtual methods with void return type and the field's type as the * sole argument. *

* If none of these apply, then it returns null. * * @param field The field to look for * @param additionalTypes The handle will accept these on top * @return The found {@link MethodHandle} of type (<owner>,<type>)void, or null otherwise */ @Nullable public static MethodHandle findSetter(Field field, Class... additionalTypes) { return findSetter(publicLookup().in(field.getDeclaringClass()), field, additionalTypes); } /** * Finds the setter method for a given field. * Tries to find setters in this order: * 1. By looking up a method setFieldname with the certain argument type * 2. Looks for a setter method fieldname, i.e. with the exact same name as the field * 3. Tries to access the field directly through this lookup. *

* It will only look up virtual methods with void return type and the field's type as the * sole argument. *

* If none of these apply, then it returns null. * * @param lookup The lookup * @param field The field to look for * @param additionalTypes The handle will accept these on top * @return The found {@link MethodHandle} of type (<owner>,<type>)void, or null otherwise */ @Nullable public static MethodHandle findSetter(Lookup lookup, Field field, Class... additionalTypes) { if (Modifier.isStatic(field.getModifiers())) { return null; } int n = additionalTypes.length; Class[] types = new Class[n + 1]; types[0] = field.getType(); System.arraycopy(additionalTypes, 0, types, 1, n); Class owner = field.getDeclaringClass(); return findSetter(lookup, owner, field.getName(), types); } /** * Finds the setter method for a given field in the given owner class. * Tries to find setters in this order: * 1. By looking up a method setFieldname with the certain argument type * 2. Looks for a setter method fieldname, i.e. with the exact same name as the field * 3. Tries to access the field of that name directly through this lookup. *

* It will only look up virtual methods with void return type and the field's type as the * sole argument. *

* If none of these apply, then it returns null. * * @param owner Where to look for setters * @param fieldName The plain field name * @param types The setter's expected argument types (can be multiple or none as well) * @return The found {@link MethodHandle} of type (<owner>,<type>)void, or null otherwise */ @Nullable public static MethodHandle findSetter(Class owner, String fieldName, Class... types) { return findSetter(publicLookup().in(owner), owner, fieldName, types); } /** * Finds the setter method for a given field in the given owner class. * Tries to find setters in this order: * 1. By looking up a method setFieldname with the certain argument type * 2. Looks for a setter method fieldname, i.e. with the exact same name as the field * 3. Tries to access the field of that name directly through this lookup. *

* It will only look up virtual methods with void return type and the field's type as the * sole argument. *

* If none of these apply, then it returns null. * * @param lookup The lookup * @param owner Where to look for setters * @param fieldName The plain field name * @param types The setter's expected argument types (can be multiple or none as well) * @return The found {@link MethodHandle} of type (<owner>,<type>)void, or null otherwise */ @Nullable public static MethodHandle findSetter(Lookup lookup, Class owner, String fieldName, Class... types) { String name = Strings.addLeading(fieldName, "set"); return attachTo(params -> { String n = name; MethodHandle h = findSetter0(lookup, owner, n, params); if (h != null) { // Method is setFieldname return h; } if (types.length >= 1 && (types[0] == boolean.class || types[0] == Boolean.class)) { // Check whether the name starts with "is", but the setter just starts with "set" n = Strings.removeLeading(fieldName, "is"); if (!n.equals(fieldName)) { n = Strings.addLeading(n, "set"); h = findSetter0(lookup, owner, n, params); if (h != null) { // Method is isFieldname return h; } } } // Look for a method with the exact same name return findSetter0(lookup, owner, fieldName, params); }, types, () -> { // Try to find the direct field setter try { return lookup.findSetter(owner, fieldName, types[0]); } catch (NoSuchFieldException | IllegalAccessException ex) { // Then the field's just not accessible, or there is no such field return null; } }); } @Nullable private static MethodHandle findSetter0(Lookup lookup, Class owner, String name, Class... types) { try { return lookup.findVirtual(owner, name, methodType(void.class, types)); } catch (NoSuchMethodException ex) { // Then there is none return null; } catch (IllegalAccessException ex) { throw new AssertionError("Cannot access setter for " + name, ex); } } @Nullable private static MethodHandle findGetter0(Lookup lookup, Class owner, String name, Class type, Class[] additionalTypes) { try { return lookup.findVirtual(owner, name, methodType(type, additionalTypes)); } catch (NoSuchMethodException ex) { // Then try next return null; } catch (IllegalAccessException ex) { throw new AssertionError("Cannot access getter for " + name, ex); } } /** * Finds the method from an interface that is referenced by the finder argument. * * @param type The interface type to look in * @param finder Used to identify the method * @param The interface type * @return The found MethodHandle, or null if there is no such */ public static MethodHandle findUsing(Class type, MethodFinderCallback finder) { return findUsing(publicLookup().in(type), type, finder); } /** * Finds the method from an interface that is referenced by the finder argument. * * @param lookup The lookup * @param type The interface type to look in * @param finder Used to identify the method * @param The interface type * @return The found MethodHandle, or null if there is no such */ public static MethodHandle findUsing(Lookup lookup, Class type, MethodFinderCallback finder) { if (!type.isInterface()) { throw new IllegalArgumentException("Can only find in interfaces."); } Object proxy = createProxy(type); Method found = fromMethodByProxy(finder, type.cast(proxy)); return unreflectMethod(lookup, found); } private static Method fromMethodByProxy(MethodFinderCallback finder, T proxy) { Method found = null; try { finder.callMethodFrom(proxy); } catch (MethodFoundException ex) { found = ex.getMethod(); } catch (Throwable ex) { throw new IllegalStateException("Prototype " + finder + " threw exception.", ex); } return found; } private static MethodHandle unreflectMethod(Lookup lookup, Method method) { if (method == null) { return null; } try { return lookup.unreflect(method); } catch (IllegalAccessException ex) { throw new AssertionError("Cannot access " + method, ex); } } /** * Finds the method from all implemented interfaces of some particular argument instance. * The result will already be bound to that instance. * * @param target The instance * @param finder Used to identify the method * @return The found MethodHandle, or null if there is no such */ public static MethodHandle bindUsing(T target, MethodFinderCallback finder) { return bindUsing(publicLookup().in(target.getClass()), target, finder); } /** * Finds the method from all implemented interfaces of some particular argument instance. * The result will already be bound to that instance. * * @param lookup The lookup * @param target The instance * @param finder Used to identify the method * @return The found MethodHandle, or null if there is no such */ public static MethodHandle bindUsing(Lookup lookup, T target, MethodFinderCallback finder) { Class[] interfaces = target.getClass().getInterfaces(); Object proxy = createProxy(interfaces); @SuppressWarnings("unchecked") Method found = fromMethodByProxy(finder, (T) proxy); MethodHandle handle = unreflectMethod(lookup, found); return handle == null ? null : handle.bindTo(target); } private static Object createProxy(Class... types) { if (types.length == 0) { throw new IllegalArgumentException("No interface implemented!"); } return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), types, (p, m, a) -> { throw new MethodFoundException(m, a); }); } /** * Finds all methods that are annotated either with {@link MethodFinder}, {@link ExecuteDirect} or {@link MethodHandleFactory}. * * @param prototype Contains all methods * @param visitor The callback for each found method */ public static V findUsing(Object prototype, V initialValue, MethodVisitor visitor) { return findUsing(publicLookup().in(prototype.getClass()), initialValue, visitor); } /** * Finds all methods that are annotated either with {@link MethodFinder}, {@link ExecuteDirect} or {@link MethodHandleFactory}. * * @param lookup The lookup * @param prototype Contains all methods * @param visitor The callback for each found method */ public static V findUsing(Lookup lookup, Object prototype, V initialValue, MethodVisitor visitor) { return visitMethodsWithStaticContext(lookup, prototype, initialValue, (value, m, handleSupplier) -> { if (m.isAnnotationPresent(MethodFinder.class)) { return visitor.visit(value, m, () -> findHandleByProxy(lookup, handleSupplier.get())); } if (m.isAnnotationPresent(ExecuteDirect.class) || m.isAnnotationPresent(MethodHandleFactory.class)) { return visitor.visit(value, m, handleSupplier); } return value; }); } /** * Creates the method handle of some interface that is invoked in a finder method. * * @param lookup The used lookup * @param finder This method should accept exactly one argument which must be an interface. * The return type is irrelevant. * @return The first called method with that finder, or null if there is no such method called. */ private static MethodHandle findHandleByProxy(Lookup lookup, MethodHandle finder) { if (finder.type().parameterCount() != 1) { throw new IllegalArgumentException(finder + " should accept exactly one argument."); } Class proxyType = finder.type().parameterType(0); if (!proxyType.isInterface()) { throw new IllegalArgumentException(finder + " should only accept one interface."); } Object proxy = createProxy(proxyType); Method found = null; try { finder.invoke(proxy); } catch (MethodFoundException ex) { found = ex.getMethod(); } catch (Throwable t) { throw new IllegalStateException("Prototype " + finder + " threw exception.", t); } if (found == null) { return null; } try { return lookup.unreflect(found); } catch (IllegalAccessException ex) { throw new AssertionError("Cannot access " + found, ex); } } /** * Checks whether some method or field would be visible in the given {@link Lookup}, * i.e. a call to unreflect() would be successful even without setting the * assignable flag in the method. * * @param lookup The lookup * @param memberType The Method, Field, or Constructor * @return true if unreflect() can be called without overwriting m.setAssignable() */ public static boolean wouldBeVisible(Lookup lookup, AccessibleObject memberType) { Class declaringClass = getDeclaringClassOf(memberType); int modifiers = getModifiersOf(memberType); if (!Modifier.isPublic(declaringClass.getModifiers())) { modifiers &= ~Modifier.PUBLIC; } int allowed = lookup.lookupModes(); if (allowed == 0) { return false; } if (Modifier.isPublic(modifiers)) { return true; } if (allowed == Lookup.PUBLIC) { return false; } Class lookupClass = lookup.lookupClass(); if ((allowed & Lookup.PACKAGE) != 0 && !Modifier.isPrivate(modifiers) && isSamePackage(declaringClass, lookupClass)) { return true; } if ((allowed & Lookup.PROTECTED) != 0) { if (Modifier.isProtected(modifiers)) { // Also allow protected access // This means ALL_MODES return declaringClass.isAssignableFrom(lookupClass); } } if ((allowed & Lookup.PRIVATE) != 0) { // Private methods allowed if (declaringClass == lookupClass) { return true; } } return false; } private static int getModifiersOf(AccessibleObject memberType) { if (memberType instanceof Executable) { return ((Executable) memberType).getModifiers(); } if (memberType instanceof Field) { return ((Field) memberType).getModifiers(); } throw new IllegalArgumentException("" + memberType); } private static Class getDeclaringClassOf(AccessibleObject memberType) { if (memberType instanceof Executable) { return ((Executable) memberType).getDeclaringClass(); } if (memberType instanceof Field) { return ((Field) memberType).getDeclaringClass(); } throw new IllegalArgumentException("" + memberType); } private static boolean isSamePackage(Class a, Class b) { return a.getPackage().equals(b.getPackage()); } /** * Iterates over all visible methods of the given type. * The visitor's MethodHandle will be the plain converted method, without any modifications. *

* This will visit all methods that the given lookup is able to find. * If some method overrides another, then only the overriding method gets visited. * * @param type The type to look up, including all super types * @param initialValue The visitor's first value * @param visitor The method visitor * @return The return value of the last visitor's run */ public static V visitAllMethods(Class type, V initialValue, MethodVisitor visitor) { return visitAllMethods(publicLookup().in(type), type, initialValue, visitor); } /** * Iterates over all visible methods of the given type. * The visitor's MethodHandle will be the plain converted method, without any modifications. *

* This will visit all methods that the given lookup is able to find. * If some method overrides another, then only the overriding method gets visited. * * @param lookup The lookup * @param type The type to look up, including all super types * @param initialValue The visitor's first value * @param visitor The method visitor * @return The return value of the last visitor's run */ public static V visitAllMethods(Lookup lookup, Class type, V initialValue, MethodVisitor visitor) { return iterateOver(lookup, type, initialValue, visitor, (m) -> { try { return lookup.unreflect(m); } catch (IllegalAccessException ex) { throw new AssertionError(m + " was tested for visibility; huh? Lookup: " + lookup, ex); } }); } /** * Iterates over all methods of the given type. * The visitor's MethodHandle will be based on instances, that means, each MethodHandle has an instance * of the given class type as the first parameter, which will be the instance. *

* If the Method is an instance method, then the MethodHandle will just be used, except that the * instance argument will be casted to the given type. *

* If the Method is static, then the MethodHandle will still expect an instance of type as the first * parameter. If the static method has a parameter of that type as the first argument, then it will * be kept, otherwise it will be discarded. *

* That means, the visitor doesn't have to care whether the method is static or not. *

* This will visit all methods that the given lookup is able to find. * If some method overrides another, then only the overriding method gets visited. * * @param type Te type to look up, including all super types * @param initialValue The visitor's first value * @param visitor The method visitor * @return The return value of the last visitor's run */ public static V visitMethodsWithInstanceContext(Class type, V initialValue, MethodVisitor visitor) { return visitMethodsWithInstanceContext(publicLookup().in(type), type, initialValue, visitor); } /** * Iterates over all methods of the given type. * The visitor's MethodHandle will be based on instances, that means, each MethodHandle has an instance * of the given class type as the first parameter, which will be the instance. *

* If the Method is an instance method, then the MethodHandle will just be used, except that the * instance argument will be casted to the given type. *

* If the Method is static, then the MethodHandle will still expect an instance of type as the first * parameter. If the static method has a parameter of that type as the first argument, then it will * be kept, otherwise it will be discarded. *

* That means, the visitor doesn't have to care whether the method is static or not. *

* This will visit all methods that the given lookup is able to find. * If some method overrides another, then only the overriding method gets visited. * * @param lookup The lookup * @param type Te type to look up, including all super types * @param initialValue The visitor's first value * @param visitor The method visitor * @return The return value of the last visitor's run */ public static V visitMethodsWithInstanceContext(Lookup lookup, Class type, V initialValue, MethodVisitor visitor) { return iterateOver(lookup, type, initialValue, visitor, (m) -> { MethodHandle handle; try { handle = lookup.unreflect(m); } catch (IllegalAccessException ex) { throw new AssertionError(m + " is not accessible in " + lookup, ex); } if (Modifier.isStatic(m.getModifiers()) && (m.getParameterCount() == 0 || !m.getParameterTypes()[0].isAssignableFrom(type))) { return MethodHandles.dropArguments(handle, 0, type); } return handle.asType(handle.type().changeParameterType(0, type)); }); } /** * Iterates over all methods of the given type. * The visitor's MethodHandle will be static, that means, all MethodHandles are exactly of the Method's type * without any instance context. *

* If the Method is an instance method, then the given reference is bound to the handle. *

* That means, the visitor doesn't have to care whether the method is static or not. *

* The reference can either by a Class; in that case, the visitor creates a new instance as a reference to * the MethodHandle as soon as the first instance method is unreflected. That means, the class needs an * empty constructor visible from the given lookup unless all visited methods are static. *

* Or it can be any other object, which is the method's context then. *

* This will visit all methods that the given lookup is able to find. * If some method overrides another, then only the overriding method gets visited. * * @param reference Either a Class or a prototype instance * @param initialValue The visitor's first value * @param visitor The method visitor * @return The return value of the last visitor's run */ public static V visitMethodsWithStaticContext(Object reference, V initialValue, MethodVisitor visitor) { return visitMethodsWithStaticContext0(null, reference, initialValue, visitor); } /** * Iterates over all methods of the given type. * The visitor's MethodHandle will be static, that means, all MethodHandles are exactly of the Method's type * without any instance context. *

* If the Method is an instance method, then the given reference is bound to the handle. *

* That means, the visitor doesn't have to care whether the method is static or not. *

* The reference can either by a Class; in that case, the visitor creates a new instance as a reference to * the MethodHandle as soon as the first instance method is unreflected. That means, the class needs an * empty constructor visible from the given lookup unless all visited methods are static. *

* Or it can be any other object, which is the method's context then. *

* This will visit all methods that the given lookup is able to find. * If some method overrides another, then only the overriding method gets visited. * * @param lookup The lookup * @param reference Either a Class or a prototype instance * @param initialValue The visitor's first value * @param visitor The method visitor * @return The return value of the last visitor's run */ public static V visitMethodsWithStaticContext(Lookup lookup, Object reference, V initialValue, MethodVisitor visitor) { return visitMethodsWithStaticContext0(lookup, reference, initialValue, visitor); } private static V visitMethodsWithStaticContext0(Lookup lookup, T reference, V initialValue, MethodVisitor visitor) { Class type; Supplier factory; if (reference instanceof Class) { @SuppressWarnings("unchecked") Class t = (Class) reference; factory = Instantiator.getDefault().createSupplierFor(t); type = t; } else { @SuppressWarnings("unchecked") Class t = (Class) reference.getClass(); type = t; factory = () -> reference; } Lookup l = lookup == null ? publicLookup().in(type) : lookup; return visitMethodsWithStaticContext(l, type, factory, initialValue, visitor); } /** * Iterates over all methods of the given type. * The visitor's MethodHandle will be static, that means, all MethodHandles are exactly of the Method's type * without any instance context. *

* If the Method is an instance method, then the given factory should create an instance of the given type. * This is bound to that handle then. *

* That means, the visitor doesn't have to care whether the method is static or not. *

* This will visit all methods that the given lookup is able to find. * If some method overrides another, then only the overriding method gets visited. * * @param type Iterates over this type's methods * @param factory Used to instantiate a member if the created method handle is of an instance method * @param initialValue The visitor's first value * @param visitor The method visitor * @return The return value of the last visitor's run */ public static V visitMethodsWithStaticContext(Class type, Supplier factory, V initialValue, MethodVisitor visitor) { return visitMethodsWithStaticContext(publicLookup().in(type), type, factory, initialValue, visitor); } /** * Iterates over all methods of the given type. * The visitor's MethodHandle will be static, that means, all MethodHandles are exactly of the Method's type * without any instance context. *

* If the Method is an instance method, then the given factory should create an instance of the given type. * This is bound to that handle then. *

* That means, the visitor doesn't have to care whether the method is static or not. *

* This will visit all methods that the given lookup is able to find. * If some method overrides another, then only the overriding method gets visited. * * @param lookup The lookup * @param type Iterates over this type's methods * @param factory Used to instantiate a member if the created method handle is of an instance method * @param initialValue The visitor's first value * @param visitor The method visitor * @return The return value of the last visitor's run */ public static V visitMethodsWithStaticContext(Lookup lookup, Class type, Supplier factory, V initialValue, MethodVisitor visitor) { Object[] instance = new Object[1]; return iterateOver(lookup, type, initialValue, visitor, (m) -> { MethodHandle handle; try { handle = lookup.unreflect(m); } catch (IllegalAccessException ex) { throw new AssertionError(m + " is not accessible in " + lookup, ex); } if (Modifier.isStatic(m.getModifiers())) { return handle; } Object ref = instance[0]; if (ref == null) { ref = factory.get(); instance[0] = ref; } return handle.bindTo(ref); }); } /** * Iterates over all methods of the given type. */ private static V iterateOver(Lookup lookup, Class type, V initialValue, MethodVisitor visitor, Function handleFactory) { Class c = type; List usedMethods = new ArrayList<>(); V value = initialValue; do { Method[] methods = AccessController.doPrivileged((PrivilegedAction) c::getDeclaredMethods); outer: for (final Method m : methods) { for (Method used : usedMethods) { if (isOverriding(used, m)) { continue outer; } } usedMethods.add(m); if (wouldBeVisible(lookup, m)) { value = visitor.visit(value, m, () -> handleFactory.apply(m)); } } if (c.isInterface()) { for (Class extended : c.getInterfaces()) { value = iterateOver(lookup, extended, value, visitor, handleFactory); } return value; } } while ((c = c.getSuperclass()) != null); return value; } private static final int INVOKE_STATIC = Modifier.STATIC | Modifier.PRIVATE; private static boolean isOverriding(Method fromSub, Method fromSuper) { if ((fromSub.getModifiers() & INVOKE_STATIC) != 0) { return false; } if (!fromSub.getName().equals(fromSuper.getName())) { return false; } if (fromSub.getParameterCount() != fromSuper.getParameterCount()) { return false; } Class[] subParameterTypes = fromSub.getParameterTypes(); Class[] superParameterTypes = fromSuper.getParameterTypes(); for (int i = 0; i < subParameterTypes.length; i++) { if (!subParameterTypes[i].equals(superParameterTypes[i])) { return false; } } return true; } private static final MethodHandle nullCheck; private static final MethodHandle notNullCheck; static { try { nullCheck = publicLookup().findStatic(Objects.class, "isNull", methodType(boolean.class, Object.class)); notNullCheck = publicLookup().findStatic(Objects.class, "nonNull", methodType(boolean.class, Object.class)); } catch (NoSuchMethodException | IllegalAccessException ex) { throw new InternalError("Objects.isNull()", ex); } } /** * Returns a method handle that accepts the given type and returns true if this is null. * * @return (<toCheck>)boolean */ public static MethodHandle nullCheck(Class toCheck) { if (toCheck.isPrimitive()) { return acceptThese(FALSE, toCheck); } return nullCheck.asType(methodType(boolean.class, toCheck)); } /** * Returns a method handle that accepts the given type and returns true if this is not null. * * @return (<toCheck>)boolean */ public static MethodHandle notNullCheck(Class toCheck) { if (toCheck.isPrimitive()) { return acceptThese(TRUE, toCheck); } return notNullCheck.asType(methodType(boolean.class, toCheck)); } /** * Creates a handle that compares two values for equality if it's an Object, or by identity if it's a primitive or enum. * * @param type The type of the objects to compare * @return (<type>,<type>)boolean */ public static MethodHandle equalsComparator(Class type) { if (type.isPrimitive()) { return getIdentityComparator(type); } if (type.isEnum()) { return getIdentityComparator(Enum.class).asType(methodType(boolean.class, type, type)); } MethodHandle handle; try { handle = publicLookup().findStatic(Objects.class, "equals", methodType(boolean.class, Object.class, Object.class)); } catch (NoSuchMethodException | IllegalAccessException ex) { throw new AssertionError("Objects.equals(Object,Object)"); } return handle.asType(methodType(boolean.class, type, type)); } private static MethodHandle getIdentityComparator(Class primitiveOrEnum) { MethodHandles.Lookup lookup = lookup(); try { return lookup.findStatic(lookup.lookupClass(), "isIdentical", methodType(boolean.class, primitiveOrEnum, primitiveOrEnum)); } catch (NoSuchMethodException | IllegalAccessException ex) { throw new AssertionError("boolean isIdentical(" + primitiveOrEnum.getName() + "," + primitiveOrEnum.getName() + ")"); } } @SuppressWarnings("unused") private static boolean isIdentical(int a, int b) { return a == b; } @SuppressWarnings("unused") private static boolean isIdentical(byte a, byte b) { return a == b; } @SuppressWarnings("unused") private static boolean isIdentical(short a, short b) { return a == b; } @SuppressWarnings("unused") private static boolean isIdentical(long a, long b) { return a == b; } @SuppressWarnings("unused") private static boolean isIdentical(double a, double b) { return a == b; } @SuppressWarnings("unused") private static boolean isIdentical(float a, float b) { return a == b; } @SuppressWarnings("unused") private static boolean isIdentical(char a, char b) { return a == b; } @SuppressWarnings("unused") private static boolean isIdentical(Enum a, Enum b) { return a == b; } /** * Creates a MethodHandle which accepts a String as the only parameter and returns an enum of type enumType. * It already contains a null check: If the input string is null, then the return value will be null as well. * * @param type The enum type * @param specialHandler Can return a special value for some field and its value. * If it returns null, name() is used. * @param The enum * @throws IllegalArgumentException If the input string is invalid, i.e. none of the accepted enumeration values */ public static > MethodHandle convertStringToEnum(Class type, BiFunction specialHandler) { assert type.isEnum(); Map map = new HashMap<>(); if (!type.isEnum()) { throw new IllegalArgumentException(type.getName() + " should be an enum."); } Field[] fields = AccessController.doPrivileged((PrivilegedAction) type::getFields); boolean isSpecial = false; for (java.lang.reflect.Field f : fields) { int m = f.getModifiers(); if (!Modifier.isStatic(m) || !Modifier.isPublic(m)) { continue; } if (!type.isAssignableFrom(f.getType())) { continue; } E v; try { v = type.cast(f.get(null)); } catch (IllegalAccessException ex) { throw new AssertionError("Cannot access " + f, ex); } String value = specialHandler.apply(f, v); if (value == null) { // The default handling. put(map, v.name(), v); continue; } isSpecial = true; put(map, value, v); } if (!isSpecial) { // No special handling return convertStringToNormalEnum(type); } // There are annotations, bind to Map.get() method MethodHandle getFromMap; Lookup lookup = lookup(); try { getFromMap = lookup.findStatic(lookup.lookupClass(), "getIfNotNull", methodType(Object.class, Map.class, String.class)); } catch (NoSuchMethodException | IllegalAccessException ex) { throw new InternalError("getIfNotNull", ex); } getFromMap = getFromMap.bindTo(map); return getFromMap.asType(methodType(type, String.class)); } @SuppressWarnings("unused") private static Object getIfNotNull(Map map, String key) { if (key == null) return null; Object result = map.get(key); if (result == null) throw new IllegalArgumentException(key); return result; } private static MethodHandle convertStringToNormalEnum(Class enumType) { Lookup lookup = publicLookup().in(enumType); MethodHandle valueOf; try { valueOf = lookup.findStatic(enumType, "valueOf", methodType(enumType, String.class)); } catch (NoSuchMethodException | IllegalAccessException ex) { throw new InternalError("No valueOf in " + enumType.getName() + "?", ex); } return secureNull(valueOf); } private static void put(Map map, String key, Object value) { if (map.containsKey(key)) { logger.log(Level.WARNING, () -> "Key " + key + " was already defined for " + map.get(key)); return; } map.put(key, value); } /** * Creates a handle that converts some enum to a String. * Instead of just calling name(), it is checked whether some fields should be treated specifically; in that case, * those values will be used. * * An example would be to check whether some enum field is annotated somehow. * * @param type The enum type * @param specialHandler Can return a special value for some field and its value. * If it returns null, name() is used. If it returns an empty String, null will be returned. * @param The enum * @return (E)String */ public static > MethodHandle convertEnumToString(Class type, BiFunction specialHandler) { Map map = new EnumMap<>(type); if (!type.isEnum()) { throw new IllegalArgumentException(type.getName() + " should be an enum."); } boolean isSpecial = false; Field[] fields = AccessController.doPrivileged((PrivilegedAction) type::getFields); for (java.lang.reflect.Field f : fields) { int m = f.getModifiers(); if (!Modifier.isStatic(m) || !Modifier.isPublic(m)) { continue; } if (!type.isAssignableFrom(f.getType())) { continue; } E v; try { v = type.cast(f.get(null)); } catch (IllegalAccessException ex) { throw new AssertionError("Cannot access " + f, ex); } String value = specialHandler.apply(f, v); if (value == null) { // The default handling. map.put(v, v.name()); continue; } isSpecial = true; map.put(v, value.isEmpty() ? null : value); } if (isSpecial) { MethodHandle getFromMap; try { getFromMap = publicLookup().bind(map, "get", methodType(Object.class, Object.class)); } catch (NoSuchMethodException | IllegalAccessException ex) { throw new InternalError("Map::get", ex); } return getFromMap.asType(methodType(String.class, type)); } else { // No special handling try { return publicLookup().findVirtual(type, "name", methodType(String.class)); } catch (NoSuchMethodException | IllegalAccessException ex) { throw new InternalError(type.getName() + "::name", ex); } } } @SuppressWarnings("unchecked") private static final MethodHandle exceptionHandlerCaller = findUsing(ExceptionHandler.class, h -> h.handle(null, null)); /** * Wraps the given method handle by an exception handler that is called in case. * * The given exception handler gets an array of objects, including all static injection parameters plus all parameters * of the failed target handle call. * * So if 0..k are the indexes of the injection parameters, and 0..n are the indexes of the target's parameters of the * failed call, then you can refer to these values in the exception handler via parameters [0]..[k[ for the injection values, * and [k+1]..[k+n+1] for the target call's parameters. * * @param target The method handle to wrap. * @param catchedExceptionType Which type shall be catched. * @param exceptionHandler The handler. * @param injections These will be the first static values of the given parameter array in the exception handler. * Each one can will be called lazily * @param The exception type * @return A method handle of the same type as the target, which won't throw E but call the handler instead. */ public static MethodHandle wrapWithExceptionHandler(MethodHandle target, Class catchedExceptionType, ExceptionHandler exceptionHandler, Supplier... injections) { return wrapWithExceptionHandler(target, catchedExceptionType, exceptionHandlerCaller.bindTo(exceptionHandler), (Object[]) injections); } /** * Wraps the given method handle by an exception handler that is called in case. * * The given exception handler gets an array of objects, including all static injection parameters plus all parameters * of the failed target handle call. * * So if 0..k are the indexes of the injection parameters, and 0..n are the indexes of the target's parameters of the * failed call, then you can refer to these values in the exception handler via parameters [0]..[k[ for the injection values, * and [k+1]..[k+n+1] for the target call's parameters. * * @param target The method handle to wrap. * @param catchedExceptionType Which type shall be catched. * @param exceptionHandler The handler. * @param injections These will be the first static values of the given parameter array in the exception handler. * Each one can be any object, which is inserted directly, or a {@link Supplier}, which will be called lazily * @param The exception type * @return A method handle of the same type as the target, which won't throw E but call the handler instead. */ public static MethodHandle wrapWithExceptionHandler(MethodHandle target, Class catchedExceptionType, ExceptionHandler exceptionHandler, Object... injections) { return wrapWithExceptionHandler(target, catchedExceptionType, exceptionHandlerCaller.bindTo(exceptionHandler), injections); } /** * Wraps the given method handle by an exception handler that is called in case. The exception handle gets the catched * exception and an Object array of the injections and all called parameter values. * * @param target The method handle to wrap. * @param catchedExceptionType Which type shall be catched. * @param exceptionHandler The handler: (<? super catchedExceptionType>,Object[])<? super target return type> * @param injections These will be the first static values of the given parameter array in the exception handler. * Each one can be any object, which is inserted directly, or a {@link Supplier}, which will be called lazily * @return A method handle of the same type as the target, which won't throw the exception but call the handler instead. */ public static MethodHandle wrapWithExceptionHandler(MethodHandle target, Class catchedExceptionType, MethodHandle exceptionHandler, Object... injections) { MethodType targetType = target.type(); MethodHandle handlerHandle = exceptionHandler.asCollector(Object[].class, injections.length + targetType.parameterCount()); handlerHandle = insertArgumentsOrSuppliers(handlerHandle, 1, injections); handlerHandle = changeNullSafeReturnType(handlerHandle, targetType.returnType()); handlerHandle = handlerHandle.asType(targetType.insertParameterTypes(0, catchedExceptionType)); return MethodHandles.catchException(target, catchedExceptionType, handlerHandle); } private static MethodHandle insertArgumentsOrSuppliers(MethodHandle handle, int pos, Object[] injections) { for (Object i : injections) { if (i instanceof Supplier) { return insertSuppliers(handle, pos, injections); } } return MethodHandles.insertArguments(handle, pos, injections); } private static final MethodHandle SUPPLIER_GET = findUsing(Supplier.class, Supplier::get); private static MethodHandle insertSuppliers(MethodHandle handle, int pos, Object[] injections) { MethodHandle h = handle; for (Object i : injections) { if (i instanceof Supplier) { MethodHandle get = SUPPLIER_GET.bindTo(i); h = MethodHandles.collectArguments(h, pos, get); } else { h = MethodHandles.insertArguments(h, pos, i); } } return h; } static void checkArgumentLength(MethodType targetType, int argumentNumber) { if (targetType.parameterCount() <= argumentNumber) { throw new IllegalArgumentException("Index " + argumentNumber + " is out of range: Target " + targetType + " has only " + targetType.parameterCount() + " arguments."); } } /** * Returns the given target with a different return type. * Returning null in the target handle will be safe even if the new return type is a primitive, which would fail * in case of a normal asType() conversion. So it is safe to return null from target in all cases. * * The returned value will be zero-like if null was originally returned. * * @param target The handle to call * @param returnType The new return type * @return A handle of the same type as the target type, modified to the new return type */ public static MethodHandle changeNullSafeReturnType(MethodHandle target, Class returnType) { MethodType targetType = target.type(); Class existingType = targetType.returnType(); if (existingType.equals(returnType)) return target; if (existingType == void.class) { return returnEmptyValue(target, returnType); } if (!returnType.isPrimitive() || returnType == void.class || existingType.isPrimitive()) { return target.asType(targetType.changeReturnType(returnType)); } MethodHandle caseNull = constantNullHandle(methodType(returnType, existingType)); MethodHandle caseNotNull = MethodHandles.identity(existingType).asType(methodType(returnType, existingType)); MethodHandle checkReturnValueForNull = MethodHandles.guardWithTest(nullCheck(existingType), caseNull, caseNotNull); return MethodHandles.filterReturnValue(target, checkReturnValueForNull); } /** * Converts the type of the target handle to accept all given parameters. * * Let's be n the target type's parameter count, and k the length of the expected parameter types, * then n<=k, and the resulting handle will accept all k given parameters, where all parameters at index * i > n are being skipped. * * @param target This will be executed * @param expectedParameterTypes Describes the returning handle's parameter types; the return type stays untouched * @return A handle that calls target but drops all trailing arguments if expectedParameterTypes are more than in target */ public static MethodHandle acceptThese(MethodHandle target, Class... expectedParameterTypes) { int n = target.type().parameterCount(); MethodType resultType = methodType(target.type().returnType(), expectedParameterTypes); int k = expectedParameterTypes.length; if (n == k) { return MethodHandles.explicitCastArguments(target, resultType); } if (n > k) { throw new IllegalArgumentException(target + " has less parameters than " + Arrays.toString(expectedParameterTypes)); } Class[] parameterTypes; if (n == 0) { parameterTypes = expectedParameterTypes; } else { parameterTypes = new Class[k - n]; System.arraycopy(expectedParameterTypes, n, parameterTypes, 0, parameterTypes.length); } return MethodHandles.explicitCastArguments(MethodHandles.dropArguments(target, n, parameterTypes), resultType); } /** * Modifies the given target handle so that it will return a value of the given type which will always be null or zero. * * @param target Some target to execute * @param returnType The type to return * @return A handle with the same arguments as the target, but which returns a null value, or a zero-like value if returnType is a primitive */ public static MethodHandle returnEmptyValue(MethodHandle target, Class returnType) { MethodHandle h = target.asType(target.type().changeReturnType(void.class)); if (returnType == void.class) { return h; } MethodHandle returnHandle = constantNullHandle(returnType); return MethodHandles.filterReturnValue(h, returnHandle); } /** * A handle that checks the given argument (which is of type Object) for being an instance of the given type. * * @param check The type to check * @return A handle of type (Object)boolean */ public static MethodHandle instanceCheck(Class check) { if (check.isPrimitive()) { return MethodHandles.dropArguments(FALSE, 0, Object.class); } if (Object.class.equals(check)) { return notNullCheck; } try { return publicLookup().in(check).bind(check, "isInstance", methodType(boolean.class, Object.class)); } catch (NoSuchMethodException | IllegalAccessException ex) { throw new InternalError("No isInstance for " + check.getName() + "?"); } } public static final MethodHandle FALSE = MethodHandles.constant(boolean.class, false); public static final MethodHandle TRUE = MethodHandles.constant(boolean.class, true); /** * Combines a list of conditions by and. * Given is an array of MethodHandle instances that return boolean, and whose parameter types are identical * except that leading handles may have fewer types than following handles. *

* The returned handle will accept the maximum number of input parameters, which is the parameters of the last * given handle. If no handle was given at all, then a MethodHandle returning a constant TRUE value without any * incoming parameters, is returned. *

* Each handle starting from the second one is invoked only if all previous handles returned true. */ public static MethodHandle and(MethodHandle... handles) { if (handles.length == 0) { return TRUE; } return and(handles, 0); } private static MethodHandle and(MethodHandle[] handles, int start) { if (start == handles.length - 1) { return handles[start]; } MethodHandle remaining = and(handles, start + 1); return MethodHandles.guardWithTest(handles[start], remaining, acceptThese(FALSE, remaining.type().parameterArray())); } /** * Combines a list of conditions by or. * Given is an array of MethodHandle instances that return boolean, and whose parameter types are identical * except that leading handles may have fewer types than following handles. *

* The returned handle will accept the maximum number of input parameters, which is the parameters of the last * given handle. If no handle was given at all, then a MethodHandle returning a constant FALSE value without any * incoming parameters, is returned. *

* Each handle starting from the second one is invoked only if all previous handles returned false. */ public static MethodHandle or(MethodHandle... handles) { if (handles.length == 0) { return FALSE; } return or(handles, 0); } private static MethodHandle or(MethodHandle[] handles, int start) { if (start == handles.length - 1) { return handles[start]; } MethodHandle remaining = or(handles, start + 1); return MethodHandles.guardWithTest(handles[start], acceptThese(TRUE, remaining.type().parameterArray()), remaining); } /** * Secures a given MethodHandle so that it will only invoked if none of its parameters is null. * The returned MethodHandle is of the same type as the given target. If the return type is an Object, * then the return value is null if any input value was null. If the return type is a primitive, then * the return value will be 0 or false in that case. If the target handle is of type void, the it will * simply not being called at all if there is a null value. */ public static MethodHandle secureNull(MethodHandle target) { return secureNull(target, i -> true); } /** * Secures a given MethodHandle so that it will only invoked if none of its specified parameters is null. * The returned MethodHandle is of the same type as the given target. If the return type is an Object, * then the return value is null if any input value was null. If the return type is a primitive, then * the return value will be 0 or false in that case. If the target handle is of type void, the it will * simply not being called at all if there is a null value. */ public static MethodHandle secureNull(MethodHandle target, int... argumentIndexes) { return secureNull(target, argumentTester(target.type(), argumentIndexes)); } private static IntPredicate argumentTester(MethodType type, int... argumentIndexes) { switch (argumentIndexes.length) { case 0: return i -> true; case 1: int index = argumentIndexes[0]; checkArgumentLength(type, index); return i -> i == index; default: BitSet toCheck = new BitSet(type.parameterCount()); for (int i : argumentIndexes) { checkArgumentLength(type, i); toCheck.set(i); } return toCheck::get; } } private static MethodHandle secureNull(MethodHandle target, IntPredicate toCheck) { return checkForNullValues(target, toCheck, Methods::rejectIf); } /** * Creates a handle that executes the given target, but validates the given arguments before by checking for null. * The handle will throw the given exception if some of these values is null. * * @param target The target to execute; all specified arguments are guaranteed to be non-null * @param exceptionTypeToThrow This exception will be thrown. Its public constructor must accept the message as the single argument * @param message This is the message in the new exception * @param argumentIndexes The arguments to check. If none is given, then all arguments are being checked * @return The null-safe handle */ public static MethodHandle assertNotNull(MethodHandle target, Class exceptionTypeToThrow, String message, int... argumentIndexes) { MethodType type = target.type(); Lookup l = publicLookup().in(exceptionTypeToThrow); MethodHandle exceptionConstructor; try { exceptionConstructor = l.findConstructor(exceptionTypeToThrow, methodType(void.class, String.class)); } catch (NoSuchMethodException | IllegalAccessException ex) { throw new IllegalArgumentException(exceptionTypeToThrow.getName() + " should have a public constructor accepting a String", ex); } exceptionConstructor = exceptionConstructor.bindTo(message); MethodHandle throwException = MethodHandles.throwException(type.returnType(), exceptionTypeToThrow); throwException = MethodHandles.collectArguments(throwException, 0, exceptionConstructor); MethodHandle nullCase = MethodHandles.dropArguments(throwException, 0, type.parameterArray()); return checkForNullValues(target, argumentTester(type, argumentIndexes), (t, g) -> MethodHandles.guardWithTest(g, nullCase, t)); } /** * Creates a handle that executes the given target, but validates the given argument before by checking for null. * The handle will throw a null pointer exception if the parameter is null. * * @param target The target to execute; all specified arguments are guaranteed to be non-null * @param argument The argument to check * @param argumentNames This is used as a message in the exception. Use null values for parameters that shall not be checked. * @return The null-safe handle */ public static MethodHandle assertNotNull(MethodHandle target, int argument, String... argumentNames) { if (argumentNames.length == 0) { throw new IllegalArgumentException("No names given"); } MethodHandle h = target; int i = argument; for (String n : argumentNames) { if (n == null) { i++; continue; } h = assertNotNull(h, NullPointerException.class, n + " must not be null", i++); } return h; } private static MethodHandle checkForNullValues(MethodHandle target, IntPredicate toCheck, BinaryOperator resultBuilder) { MethodType targetType = target.type(); Class[] parameterArray = targetType.parameterArray(); MethodHandle[] checks = new MethodHandle[parameterArray.length]; int checkCount = 0; MethodType nullCheckType = targetType.changeReturnType(boolean.class); int i = 0; for (Class p : parameterArray) { if (!toCheck.test(i++) || p.isPrimitive()) { continue; } MethodHandle nullCheck = nullCheck(p); MethodHandle checkArgI = MethodHandles.permuteArguments(nullCheck, nullCheckType, i - 1); checks[checkCount++] = checkArgI; } if (checkCount == 0) { // All parameters were primitives return target; } if (checkCount < i) { // Not all parameters need to get checked checks = Arrays.copyOf(checks, checkCount); } MethodHandle checkNull = or(checks); return resultBuilder.apply(target, checkNull); } /** * A handle that accepts nothing, returns nothing, and does nothing. */ public static final MethodHandle DO_NOTHING = MethodHandles.constant(Void.class, null).asType(methodType(void.class)); /** * Creates a method handle that invokes its target only if the guard returns false. * Guard and target must have the same argument types. Guard must return a boolean, * while the target may return any type. *

* The resulting handle has exactly the same type, and if it returns a value, it will return null * or a zero-like value if the guard returned false. */ public static MethodHandle rejectIf(MethodHandle target, MethodHandle guard) { MethodHandle constantNull = constantNullHandle(target.type()); return MethodHandles.guardWithTest(guard, constantNull, target); } /** * Creates a method handle that invokes its target only if the guard returns true. * Guard and target must have the same argument types. Guard must return a boolean, * while the target may return any type. *

* The resulting handle has exactly the same type, and if it returns a value, it will return null * or a zero-like value if the guard returned false. */ public static MethodHandle invokeOnlyIf(MethodHandle target, MethodHandle guard) { MethodHandle constantNull = constantNullHandle(target.type()); return MethodHandles.guardWithTest(guard, target, constantNull); } /** * Gets a method handle that has the same type as the given argument, * but does nothing and just returns a default value if the argument has a return type. * * @param type The expected type of the returned handle. * @return The no-op handle */ public static MethodHandle constantNullHandle(MethodType type) { Class returnType = type.returnType(); MethodHandle constantNull = constantNullHandle(returnType); return acceptThese(constantNull, type.parameterArray()); } private static MethodHandle constantNullHandle(Class returnType) { if (returnType == void.class) { return DO_NOTHING; } else if (returnType == int.class || returnType == long.class || returnType == short.class || returnType == byte.class || returnType == float.class || returnType == double.class) { return MethodHandles.constant(returnType, 0); } else if (returnType == char.class) { return MethodHandles.constant(returnType, (char) Character.UNASSIGNED); } else if (returnType == boolean.class) { return MethodHandles.constant(returnType, false); } else if (returnType.isPrimitive()) { throw new AssertionError("Huh? Forgot type " + returnType.getName()); } else { return MethodHandles.constant(returnType, null); } } /** * Creates a method handle that doesn't execute its target if at least one of the guards returns true. *

* The guard must accept exactly one argument with the type of the given argument of the target, and return a * boolean value. *

* The resulting handle has the same type as the target, and returns a null value or zero if the guard doesn't * allow to execute. */ public static MethodHandle rejectIfArgument(MethodHandle target, int argumentNumber, MethodHandle... guards) { return doOnArguments(target, argumentNumber, Methods::rejectIf, guards); } /** * Creates a method handle that executes its target only if all the guard return true. *

* This methods is just the exact opposite to rejectIfArgument(). *

* The guard must accept exactly one argument with the type of the given argument of the target, and return a * boolean value. *

* The resulting handle has the same type as the target, and returns a null value or zero if the guard doesn't * allow to execute. */ public static MethodHandle invokeOnlyIfArgument(MethodHandle target, int argumentNumber, MethodHandle... guards) { return doOnArguments(target, argumentNumber, Methods::invokeOnlyIf, guards); } private static MethodHandle doOnArguments(MethodHandle target, int argumentNumber, BinaryOperator action, MethodHandle... guards) { int n = guards.length; if (n == 0) { return target; } MethodType targetType = target.type(); checkArgumentLength(targetType, argumentNumber + n - 1); MethodHandle handle = target; int a = argumentNumber; for (MethodHandle g : guards) { if (g == null) { a++; continue; } MethodHandle argumentGuard = argumentGuard(g, targetType, a++); handle = action.apply(handle, argumentGuard); } return handle; } private static MethodHandle argumentGuard(MethodHandle guard, MethodType targetType, int argumentNumber) { MethodHandle argumentGuard = guard.asType(guard.type().changeParameterType(0, targetType.parameterType(argumentNumber))); if (argumentNumber > 0) { argumentGuard = MethodHandles.dropArguments(argumentGuard, 0, Arrays.copyOf(targetType.parameterArray(), argumentNumber)); } return argumentGuard; } /** * Creates a method handle that executes its target, and then returns the argument at the given index. *

* The resulting handle will have the same type as the target except that the return type is the same as the * one of the given argument. *

* If the target returns a value, then this simply is discarded. */ public static MethodHandle returnArgument(MethodHandle target, int argumentNumber) { MethodType targetType = target.type(); checkArgumentLength(targetType, argumentNumber); MethodHandle dontReturnAnything = target.asType(targetType.changeReturnType(void.class)); Class parameterType = targetType.parameterType(argumentNumber); MethodHandle identityOfPos = MethodHandles.identity(parameterType); if (targetType.parameterCount() > 1) { identityOfPos = MethodHandles.permuteArguments(identityOfPos, targetType.changeReturnType(parameterType), argumentNumber); } return MethodHandles.foldArguments(identityOfPos, dontReturnAnything); } public static MethodHandle permute(MethodHandle target, MethodType type) { MethodType targetType = target.type(); int parameterCount = targetType.parameterCount(); int[] indexes = new int[parameterCount]; Class[] newParameterTypes = new Class[parameterCount]; for (int i = 0; i < parameterCount; i++) { Class p = targetType.parameterType(i); int pos = findBestMatchingParameter(type, p, i); indexes[i] = pos; Class original = type.parameterType(pos); newParameterTypes[i] = original; } return MethodHandles.permuteArguments(target.asType(methodType(type.returnType(), newParameterTypes)), type, indexes); } private static int findBestMatchingParameter(MethodType type, Class lookFor, int pos) { int match = -1; int perfectMatch = -1; for (int i = 0; i < type.parameterCount(); i++) { Class p = type.parameterType(i); if (p.equals(lookFor)) { if (i == pos) { return i; } if (perfectMatch >= 0) { throw new AmbiguousTypesException("Type " + lookFor.getName() + " is appearing twice in " + type); } perfectMatch = i; match = i; } else if (Types.isAssignableFrom(lookFor, p)) { if (i != pos && match >= 0) { throw new AmbiguousTypesException("Type " + lookFor.getName() + " is matching twice in " + type); } match = i; } } if (match < 0) { throw new IllegalArgumentException(type + " does not contain parameter of type " + lookFor.getName()); } return match; } /** * Compares an expected method type (the reference) with some other method type to check. * * @param reference The expected type * @param toCheck This is checked * @return The comparison (@see {@link Comparison}) */ public static Comparison compare(MethodType reference, MethodType toCheck) { return compare(reference.returnType(), toCheck.returnType(), reference.parameterArray(), toCheck.parameterArray()); } /** * Compares an expected method type (the reference) with some other method to check. * * @param reference The expected type * @param toCheck This is checked * @return The comparison (@see {@link Comparison}) */ public static Comparison compare(MethodType reference, Method toCheck) { return compare(reference.returnType(), toCheck.getReturnType(), reference.parameterArray(), toCheck.getParameterTypes()); } private static Comparison compare(Class expectedReturnType, Class checkedReturnType, Class[] expectedArguments, Class[] checkedArguments) { int expectedArgumentCount = expectedArguments.length; int checkedArgumentCount = checkedArguments.length; if (expectedArgumentCount > checkedArgumentCount) { return Comparison.LESS_ARGUMENTS; } if (expectedArgumentCount < checkedArgumentCount) { return Comparison.MORE_ARGUMENTS; } Comparison comparison; if (checkedReturnType.equals(expectedReturnType)) { comparison = Comparison.EQUAL; } else if (expectedReturnType == void.class || checkedReturnType == void.class) { return Comparison.INCOMPATIBLE; } else if (Types.isAssignableFrom(expectedReturnType, checkedReturnType) && !expectedReturnType.isPrimitive()) { comparison = Comparison.MORE_GENERIC; } else if (Types.isAssignableFrom(checkedReturnType, expectedReturnType)) { comparison = Comparison.MORE_SPECIFIC; } else { return Comparison.INCOMPATIBLE; } for (int i = 0; i < expectedArgumentCount; i++) { Class expectedArgument = expectedArguments[i]; Class checkedArgument = checkedArguments[i]; if (expectedArgument.equals(checkedArgument)) { continue; } if (Types.isAssignableFrom(expectedArgument, checkedArgument) && !expectedArgument.isPrimitive()) { switch (comparison) { case MORE_GENERIC: comparison = Comparison.CONVERTABLE; break; case EQUAL: comparison = Comparison.MORE_SPECIFIC; } } else if (Types.isAssignableFrom(checkedArgument, expectedArgument)) { switch (comparison) { case MORE_SPECIFIC: comparison = Comparison.CONVERTABLE; break; case EQUAL: comparison = Comparison.MORE_GENERIC; } } else { return Comparison.INCOMPATIBLE; } } return comparison; } /** * Finds the method handle to the only unique method that fits to the given method type. *

* A method is a unique method if it is the only one with the given type within one class. If there are more * in its super classes, then it doesn't matter. *

* A method matches if their arguments are either equal to the given method type, or more generic, or more specific, * in that order. A method is unique if it is unique within the best matching comparison. For instance, * if there is one method that is more generic, and another one is more specific, then uniqueness still * is given and the more generic one is chosen. *

* The object to look up may either be an instance: Then its class type will be searched. If the found method then * is static, it will simply be used, otherwise the given instance is bound to it. *

* If the object is a Class, and the found method is an instance method, then an empty constructor is expected * and a new instance is created now. *

* As a result, the returned handle will have exactly the given reference method type, without any additional * object instance. * * @param object The object (Class or some instance) to investigate * @param reference The method type to look for * @return The found method handle * @throws AmbiguousMethodException If there are multiple methods matching the searched type * @throws NoSuchMethodError If no method was found */ public static MethodHandle findMethodHandleOfType(Object object, MethodType reference) { return findMethodHandleOfType(null, object, reference); } /** * Finds the method handle to the only unique method that fits to the given method type. *

* A method is a unique method if it is the only one with the given type within one class. If there are more * in its super classes, then it doesn't matter. *

* A method matches if their arguments are either equal to the given method type, or more generic, or more specific, * in that order. A method is unique if it is unique within the best matching comparison. For instance, * if there is one method that is more generic, and another one is more specific, then uniqueness still * is given and the more generic one is chosen. *

* The object to look up may either be an instance: Then its class type will be searched. If the found method then * is static, it will simply be used, otherwise the given instance is bound to it. *

* If the object is a Class, and the found method is an instance method, then an empty constructor is expected * and a new instance is created now. *

* As a result, the returned handle will have exactly the given reference method type, without any additional * object instance. * * @param lookup The lookup * @param object The object (Class or some instance) to investigate * @param reference The method type to look for * @return The found method handle * @throws AmbiguousMethodException If there are multiple methods matching the searched type * @throws NoSuchMethodError If no method was found */ public static MethodHandle findMethodHandleOfType(Lookup lookup, Object object, MethodType reference) { MethodHandle handle = findSingleMethodHandle(lookup, object, reference); return handle.asType(reference); } /** * Finds the method to the only unique method that fits to the given method type. *

* A method is a unique method if it is the only one with the given type within one class. If there are more * in its super classes, then it doesn't matter. *

* A method matches if their arguments are either equal to the given method type, or more generic, or more specific, * in that order. A method is unique if it is unique within the best matching comparison. For instance, * if there is one method that is more generic, and another one is more specific, then uniqueness still * is given and the more generic one is chosen. * * @param type The reference type to investigate * @param reference The method type to look for * @return The found method handle * @throws AmbiguousMethodException If there are multiple methods matching the searched type * @throws NoSuchMethodError If no method was found */ public static Method findMethodOfType(Class type, MethodType reference) { return findMethodOfType(null, type, reference); } /** * Finds the method to the only unique method that fits to the given method type. *

* A method is a unique method if it is the only one with the given type within one class. If there are more * in its super classes, then it doesn't matter. *

* A method matches if their arguments are either equal to the given method type, or more generic, or more specific, * in that order. A method is unique if it is unique within the best matching comparison. For instance, * if there is one method that is more generic, and another one is more specific, then uniqueness still * is given and the more generic one is chosen. * * @param lookup The lookup * @param type The reference type to investigate * @param reference The method type to look for * @return The found method handle * @throws AmbiguousMethodException If there are multiple methods matching the searched type * @throws NoSuchMethodError If no method was found */ public static Method findMethodOfType(Lookup lookup, Class type, MethodType reference) { Method m = findSingleMethodIn(lookup, type, reference); if (m == null) { throw new NoSuchMethodError("No method " + reference + " in " + type.getName()); } return m; } private static MethodHandle findSingleMethodHandle(Lookup lookup, Object object, MethodType reference) { Class type; if (object instanceof Class) { type = (Class) object; } else { if (MethodHandleProxies.isWrapperInstance(object)) { return MethodHandleProxies.wrapperInstanceTarget(object); } type = object.getClass(); } Lookup l = lookup == null ? publicLookup().in(type) : lookup; Method m = findMethodOfType(l, type, reference); MethodHandle handle; try { handle = l.unreflect(m); } catch (IllegalAccessException ex) { throw new AssertionError(m + " should be visible.", ex); } if (Modifier.isStatic(m.getModifiers())) { return handle; } if (object instanceof Class) { // Then inject a new instance now object = Instantiator.getDefault().instantiate((Class) object); } return handle.bindTo(object); } /** * Iterates over the full class hierarchy, but without Object itself. * Starts with the given type and then traverses over the superclass hierarchy until some value is found. * * @param type At which class to start * @param action What to do for each class - will continue with the next superclass only if this returns null * @return The return value of the last action call, or null if all actions returned null */ @Nullable public static T doInClassHierarchy(Class type, Function, T> action) { Class c = type; while (c != null && c != Object.class) { T result = action.apply(c); if (result != null) { return result; } c = c.getSuperclass(); } return null; } /** * Guards a method handle with a semaphore to assure bounded access to one single thread at a time. * * On each call to the target handle, a single permit is acquired, and it's released afterwards. * * The permit is acquired uninterruptibly, meaning that it will wait even if Thread.interrupt() is called. * Therefore the caller doesn't need to check for InterruptedExceptions. * * @param target The target handle to call * @return A handle with the same type as the target handle */ public static MethodHandle synchronize(MethodHandle target) { return synchronize(target, 1); } /** * Guards a method handle with a semaphore to assure bounded access. * * On each call to the target handle, a single permit is acquired, and it's released afterwards. * * The permit is acquired uninterruptibly, meaning that it will wait even if Thread.interrupt() is called. * Therefore the caller doesn't need to check for InterruptedExceptions. * * @param target The target handle to call * @param concurrentAccesses How many threads may access this operation concurrently * @return A handle with the same type as the target handle */ public static MethodHandle synchronize(MethodHandle target, int concurrentAccesses) { return synchronizeWith(target, new Semaphore(concurrentAccesses)); } /** * Guards a method handle with a semaphore to assure bounded access. * * On each call to the target handle, a single permit is acquired, and it's released afterwards. * * The permit is acquired uninterruptibly, meaning that it will wait even if Thread.interrupt() is called. * Therefore the caller doesn't need to check for InterruptedExceptions. * * @param target The target handle to call * @param semaphore This is used to block gthe operation * @return A handle with the same type as the target handle */ public static MethodHandle synchronizeWith(MethodHandle target, Semaphore semaphore) { MethodType type = target.type(); Class returnType = type.returnType(); MethodHandle acquire, release; try { acquire = publicLookup().bind(semaphore, "acquireUninterruptibly", methodType(void.class)); release = publicLookup().bind(semaphore, "release", methodType(void.class)); } catch (NoSuchMethodException | IllegalAccessException ex) { throw new InternalError(ex); } MethodHandle executeAndRelease = doFinally(target, release); MethodHandle synced = MethodHandles.foldArguments(executeAndRelease, acquire); if (target.isVarargsCollector()) { synced = synced.asVarargsCollector(type.parameterType(type.parameterCount() - 1)); } return synced; } /** * Creates a handle that *

    *
  1. executes the target
  2. *
  3. executes the finallyBlock even if target threw some exception
  4. *
  5. and then return target's value or finally throws target's exception
  6. *
* * @param target The main block which is guarded by a catch clause * @param finallyBlock The block to execute after target; must not return a value, and may accept the same or less parameters than the target * @return A handle of the exact same type as target */ public static MethodHandle doFinally(MethodHandle target, MethodHandle finallyBlock) { MethodType finallyBlockType = finallyBlock.type(); if (finallyBlockType.returnType() != void.class) { throw new IllegalArgumentException(finallyBlock + " should not return some value"); } MethodType type = target.type(); Class returnType = type.returnType(); Class[] finallyBlockAccepts = Arrays.copyOf(type.parameterArray(), finallyBlockType.parameterCount()); MethodHandle transformedFinallyBlock = finallyBlock.asType(methodType(void.class, finallyBlockAccepts)); MethodHandle throwException = MethodHandles.throwException(returnType, Throwable.class); if (finallyBlockAccepts.length > 0) { transformedFinallyBlock = MethodHandles.dropArguments(transformedFinallyBlock, 0, Throwable.class); throwException = MethodHandles.dropArguments(throwException, 1, finallyBlockAccepts); } MethodHandle catchedBlock = MethodHandles.foldArguments(throwException, transformedFinallyBlock); MethodHandle catchedTarget = MethodHandles.catchException(target, Throwable.class, catchedBlock); if (returnType == void.class) { return MethodHandles.foldArguments(acceptThese(finallyBlock, type.parameterArray()), catchedTarget); } if (finallyBlockType.parameterCount() == 0) { MethodHandle returnValue = MethodHandles.identity(returnType); MethodHandle doFinallyAndReturn = MethodHandles.foldArguments(returnValue, finallyBlock); return MethodHandles.filterReturnValue(catchedTarget, doFinallyAndReturn); } else { MethodHandle doFinallyAndReturn = MethodHandles.dropArguments(acceptThese(finallyBlock, type.parameterArray()), 0, returnType); doFinallyAndReturn = returnArgument(doFinallyAndReturn, 0); return MethodHandles.foldArguments(doFinallyAndReturn, catchedTarget); } } private static Method findSingleMethodIn(Lookup lookup, Class type, MethodType reference) { return doInClassHierarchy(type, c -> { Method bestMatch = null; Comparison matchingRank = null; boolean ambiguous = false; Method[] methods = AccessController.doPrivileged((PrivilegedAction) c::getDeclaredMethods); loop: for (Method m : methods) { if (!wouldBeVisible(lookup, m)) { continue; } Comparison compare = compare(reference, m); switch (compare) { case EQUAL: if (matchingRank == Comparison.EQUAL) { ambiguous = true; break loop; } matchingRank = Comparison.EQUAL; bestMatch = m; ambiguous = false; break; case MORE_SPECIFIC: if (matchingRank != null && matchingRank != Comparison.MORE_SPECIFIC) { break; } case MORE_GENERIC: // or MORE_SPECIFIC if (matchingRank != compare) { matchingRank = compare; ambiguous = false; } else { ambiguous = bestMatch != null; } bestMatch = m; break; default: // Do nothing then } } if (ambiguous) { throw new AmbiguousMethodException("Multiple methods found with type " + reference + " in " + type.getName()); } return bestMatch; }); } /** * Finds the only method in the given interface that doesn't have a default implementation, i.e. it's the functional interface's only method. * Fail if the given type is not a functional interface. * * @param lambdaType The interface type * @return The found method */ public static Method findLambdaMethodOrFail(Class lambdaType) { return findLambdaMethod(lambdaType).orElseThrow(() -> new IllegalArgumentException(lambdaType.getName() + " is not a functional interface.")); } /** * Finds the only method in the given interface that doesn't have a default implementation, i.e. it's the functional interface's only method. * Will return only one or no methods at all. * * @param lambdaType The interface type * @return The found method */ public static Optional findLambdaMethod(Class lambdaType) { if (!lambdaType.isInterface()) { throw new IllegalArgumentException(lambdaType.getName() + " should be an interface!"); } Method found = null; for (Method m : lambdaType.getMethods()) { int modifiers = m.getModifiers(); if (Modifier.isStatic(modifiers) || !Modifier.isAbstract(modifiers)) { continue; } if (found == null) { found = m; } else { return Optional.empty(); } } return Optional.ofNullable(found); } /** * Internal interface to check lambda access. */ public interface LambdaMarker { } /** * Creates a lambda factory for the given type which will then call the given target method. * * @param lookup Must be the caller's lookup according to LambdaMetaFactory's documentation * @param targetMethod This will be called in the created lambda - it must be a direct handle, otherwise this * method will return null * @param lambdaType The interface that specifies the lambda. The lambda method's argument size n must not exceed * the target's argument size t, and their types must be convertible to the last n arguments of the * target. * @param markerInterfaces Some interfaces without methods that will be implemented by the created lambda instance * @return A MethodHandle that accepts the first (t-n) arguments of the target and returns an instance of lambdaType. */ @Nullable public static MethodHandle createLambdaFactory(Lookup lookup, MethodHandle targetMethod, Class lambdaType, Class... markerInterfaces) { Method m = findLambdaMethodOrFail(lambdaType); String name = m.getName(); Class[] calledParameters = m.getParameterTypes(); MethodType calledType = methodType(m.getReturnType(), calledParameters); MethodType targetType = targetMethod.type(); Class[] targetParameters = targetType.parameterArray(); int methodParamSize = calledParameters.length; if (targetParameters.length < methodParamSize) { throw new IllegalArgumentException("targetMethod has too few parameters: Expected at least " + methodParamSize + ", actual: " + targetParameters.length); } int additional = targetParameters.length - methodParamSize; Class[] additionalParameters = Arrays.copyOf(targetParameters, additional); MethodType factoryType = methodType(lambdaType, additionalParameters); MethodType instantiatedType; if (additional == 0) { instantiatedType = targetType; } else { Class[] params = new Class[methodParamSize]; System.arraycopy(targetParameters, additional, params, 0, methodParamSize); instantiatedType = methodType(targetType.returnType(), params); } Object[] metaArguments = new Object[6 + markerInterfaces.length]; metaArguments[0] = calledType; metaArguments[1] = targetMethod; metaArguments[2] = instantiatedType; metaArguments[3] = LambdaMetafactory.FLAG_MARKERS; metaArguments[4] = markerInterfaces.length + 1; metaArguments[5] = LambdaMarker.class; System.arraycopy(markerInterfaces, 0, metaArguments, 6, markerInterfaces.length); CallSite callSite; try { callSite = LambdaMetafactory.altMetafactory(lookup, name, factoryType, metaArguments); } catch (LambdaConversionException ex) { if (ex.getMessage().contains("Unsupported MethodHandle kind")) { // Ugly check, but how to do better? return null; } throw new IllegalArgumentException("Cannot call " + targetMethod + " on " + m, ex); } catch (IllegalArgumentException ex) { if (ex.getMessage().contains("not a direct method handle") || ex.getMessage().contains(" is private:")) { // Ugly check, but how to do better? return null; } throw ex; } return callSite.getTarget(); } /** * Creates an instance of the given lambda interface that will call the given target method. *

* You can see this as an abbreviation to first creating a factory handle via createLambdaFactory() and then * creating that instance by inserting the initializer values. But this will also work when the target handle is * not a direct one by falling back to a proxy implementation. * * @param targetMethod This method will be called. It must accept at least that many arguments as the lambda method. * @param lambdaType The instantiated type. Must be a functional interface. * @param initializers These can be used to fill up the first target handle's arguments * @param The type * @return An instance of the lambda type */ public static T lambdafy(MethodHandle targetMethod, Class lambdaType, Object... initializers) { return lambdafy(lookup(), targetMethod, lambdaType, initializers); } /** * Creates an instance of the given lambda interface that will call the given target method. *

* You can see this as an abbreviation to first creating a factory handle via createLambdaFactory() and then * creating that instance by inserting the initializer values. But this will also work when the target handle is * not a direct one by falling back to a proxy implementation. * * @param lookup The caller's lookup * @param targetMethod This method will be called. It must accept at least that many arguments as the lambda method. * @param lambdaType The instantiated type. Must be a functional interface. * @param initializers These can be used to fill up the first target handle's arguments * @param The type * @return An instance of the lambda type */ public static T lambdafy(Lookup lookup, MethodHandle targetMethod, Class lambdaType, Object... initializers) { MethodHandle lambdaFactory = createLambdaFactory(lookup, targetMethod, lambdaType); MethodHandle handle; if (lambdaFactory == null) { // Not a direct handle - use normal interface creation if (initializers.length == 0) { handle = targetMethod; } else { handle = MethodHandles.insertArguments(targetMethod, 0, initializers); } return MethodHandleProxies.asInterfaceInstance(lambdaType, handle); } try { return lambdaType.cast(lambdaFactory.invokeWithArguments(initializers)); } catch (RuntimeException | Error e) { throw e; } catch (Throwable t) { throw new InternalError("Creating lambda " + lambdaType.getName() + " failed.", t); } } /** * Checks whether the given lambda instance was created from a factory returned from createLambdaFactory(). * * @param instance The lambda to check * @return true is it was successfully created from a direct handle */ public static boolean wasLambdafiedDirect(Object instance) { return instance instanceof LambdaMarker; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy