/* * Copyright 2008-2009 Sun Microsystems, Inc. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Sun designates this * particular file as subject to the "Classpath" exception as provided * by Sun in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, * CA 95054 USA or visit www.sun.com if you need additional information or * have any questions. */ package java.dyn; import java.lang.reflect.Constructor; import sun.dyn.Access; import sun.dyn.MemberName; import sun.dyn.MethodHandleImpl; import sun.dyn.util.VerifyAccess; import sun.dyn.util.Wrapper; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; import sun.dyn.Invokers; import sun.dyn.MethodTypeImpl; import sun.reflect.Reflection; import static sun.dyn.MemberName.newIllegalArgumentException; import static sun.dyn.MemberName.newNoAccessException; /** * Fundamental operations and utilities for MethodHandle. *

* API Note: The matching of method types in this API cannot * be completely checked by Java's generic type system for three reasons: *

    *
  1. Method types range over all possible arities, * from no arguments to an arbitrary number of arguments. * Generics are not variadic, and so cannot represent this.
  2. *
  3. Method types can specify arguments of primitive types, * which Java generic types cannot range over.
  4. *
  5. Method types can optionally specify varargs (ellipsis).
  6. *
* @author John Rose, JSR 292 EG */ public class MethodHandles { private MethodHandles() { } // do not instantiate private static final Access IMPL_TOKEN = Access.getToken(); private static final MemberName.Factory IMPL_NAMES = MemberName.getFactory(IMPL_TOKEN); static { MethodHandleImpl.initStatics(); } // See IMPL_LOOKUP below. //// Method handle creation from ordinary methods. public static Lookup lookup() { return new Lookup(); } /** * PROVISIONAL API, WORK IN PROGRESS: * A factory object for creating method handles, when the creation * requires access checking. Method handles do not perform * access checks when they are called; this is a major difference * from reflective {@link Method}, which performs access checking * against every caller, on every call. Method handle access * restrictions are enforced when a method handle is created. * The caller class against which those restrictions are enforced * is known as the "lookup class". {@link Lookup} embodies an * authenticated lookup class, and can be used to create any number * of access-checked method handles, all checked against a single * lookup class. *

* A class which needs to create method handles will call * {@code MethodHandles.lookup()} to create a factory for itself. * It may then use this factory to create method handles on * all of its methods, including private ones. * It may also delegate the lookup (e.g., to a metaobject protocol) * by passing the {@code Lookup} object to other code. * If this other code creates method handles, they will be access * checked against the original lookup class, and not with any higher * privileges. *

* Note that access checks only apply to named and reflected methods. * Other method handle creation methods, such as {@link #convertArguments}, * do not require any access checks, and can be done independently * of any lookup class. *

* A note about error conditions: A lookup can fail, because * the containing class is not accessible to the lookup class, or * because the desired class member is missing, or because the * desired class member is not accessible to the lookup class. * It can also fail if a security manager is installed and refuses * access. In any of these cases, an exception will be * thrown from the attempted lookup. * In general, the conditions under which a method handle may be * created for a method {@code M} are exactly as restrictive as the conditions * under which the lookup class could have compiled a call to {@code M}. * At least some of these error conditions are likely to be * represented by checked exceptions in the final version of this API. */ public static final class Lookup { private final Class lookupClass; /** Which class is performing the lookup? It is this class against * which checks are performed for visibility and access permissions. *

* This value is null if and only if this lookup is {@link #PUBLIC_LOOKUP}. */ public Class lookupClass() { return lookupClass; } /** Embody the current class (the lookupClass) as a lookup class * for method handle creation. * Must be called by from a method in this package, * which in turn is called by a method not in this package. * Also, don't make it private, lest javac interpose * an access$N method. */ Lookup() { Class caller = getCallerClassAtEntryPoint(); // make sure we haven't accidentally picked up this class: checkUnprivilegedlookupClass(caller); this.lookupClass = caller; } private Lookup(Class lookupClass) { this.lookupClass = lookupClass; } private static final Class PUBLIC_ONLY = sun.dyn.empty.Empty.class; /** Version of lookup which is trusted minimally. * It can only be used to create method handles to * publicly accessible members. */ public static final Lookup PUBLIC_LOOKUP = new Lookup(PUBLIC_ONLY); /** Package-private version of lookup which is trusted. */ static final Lookup IMPL_LOOKUP = new Lookup(null); static { MethodHandleImpl.initLookup(IMPL_TOKEN, IMPL_LOOKUP); } private static void checkUnprivilegedlookupClass(Class lookupClass) { String name = lookupClass.getName(); if (name.startsWith("java.dyn.") || name.startsWith("sun.dyn.")) throw newIllegalArgumentException("illegal lookupClass: "+lookupClass); } @Override public String toString() { if (lookupClass == PUBLIC_ONLY) return "public"; if (lookupClass == null) return "privileged"; return lookupClass.getName(); } // call this from an entry point method in Lookup with extraFrames=0. private static Class getCallerClassAtEntryPoint() { final int CALLER_DEPTH = 4; // 0: Reflection.getCC, 1: getCallerClassAtEntryPoint, // 2: Lookup., 3: MethodHandles.*, 4: caller // Note: This should be the only use of getCallerClass in this file. return Reflection.getCallerClass(CALLER_DEPTH); } /** * Produce a method handle for a static method. * The type of the method handle will be that of the method. * The method and all its argument types must be accessible to the lookup class. * If the method's class has not yet been initialized, that is done * immediately, before the method handle is returned. * @param defc the class from which the method is accessed * @param name the name of the method * @param type the type of the method * @return the desired method handle * @exception SecurityException TBD * @exception NoAccessException if the method does not exist or access checking fails */ public MethodHandle findStatic(Class defc, String name, MethodType type) throws NoAccessException { MemberName method = IMPL_NAMES.resolveOrFail(new MemberName(defc, name, type, Modifier.STATIC), true, lookupClass); checkStatic(true, method, lookupClass); //throw NoSuchMethodException return MethodHandleImpl.findMethod(IMPL_TOKEN, method, false, lookupClass); } /** * Produce a method handle for a virtual method. * The type of the method handle will be that of the method, * with the receiver type ({@code defc}) prepended. * The method and all its argument types must be accessible to the lookup class. *

* (BUG NOTE: The type {@code Object} may be prepended instead * of the receiver type, if the receiver type is not on the boot class path. * This is due to a temporary JVM limitation, in which MethodHandle * claims to be unable to access such classes. To work around this * bug, use {@code convertArguments} to normalize the type of the leading * argument to a type on the boot class path, such as {@code Object}.) *

* When called, the handle will treat the first argument as a receiver * and dispatch on the receiver's type to determine which method * implementation to enter. * (The dispatching action is identical with that performed by an * {@code invokevirtual} or {@code invokeinterface} instruction.) * @param defc the class or interface from which the method is accessed * @param name the name of the method * @param type the type of the method, with the receiver argument omitted * @return the desired method handle * @exception SecurityException TBD * @exception NoAccessException if the method does not exist or access checking fails */ public MethodHandle findVirtual(Class defc, String name, MethodType type) throws NoAccessException { MemberName method = IMPL_NAMES.resolveOrFail(new MemberName(defc, name, type), true, lookupClass); checkStatic(false, method, lookupClass); return MethodHandleImpl.findMethod(IMPL_TOKEN, method, true, lookupClass); } /** * Produce an early-bound method handle for a virtual method, * as if called from an {@code invokespecial} * instruction from {@code caller}. * The type of the method handle will be that of the method, * with a suitably restricted receiver type (such as {@code caller}) prepended. * The method and all its argument types must be accessible * to the caller. *

* When called, the handle will treat the first argument as a receiver, * but will not dispatch on the receiver's type. * (This direct invocation action is identical with that performed by an * {@code invokespecial} instruction.) *

* If the explicitly specified caller class is not identical with the * lookup class, a security check TBD is performed. * @param defc the class or interface from which the method is accessed * @param name the name of the method, or "" for a constructor * @param type the type of the method, with the receiver argument omitted * @param specialCaller the proposed calling class to perform the {@code invokespecial} * @return the desired method handle * @exception SecurityException TBD * @exception NoAccessException if the method does not exist or access checking fails */ public MethodHandle findSpecial(Class defc, String name, MethodType type, Class specialCaller) throws NoAccessException { checkSpecialCaller(specialCaller, lookupClass); MemberName method = IMPL_NAMES.resolveOrFail(new MemberName(defc, name, type), false, specialCaller); checkStatic(false, method, lookupClass); if (name.equals("")) { throw newNoAccessException("cannot directly invoke a constructor", method, null); } else if (defc.isInterface() || !defc.isAssignableFrom(specialCaller)) { throw newNoAccessException("method must be in a superclass of lookup class", method, lookupClass); } return MethodHandleImpl.findMethod(IMPL_TOKEN, method, false, specialCaller); } /** * Produce an early-bound method handle for a non-static method. * The receiver must have a supertype {@code defc} in which a method * of the given name and type is accessible to the lookup class. * The method and all its argument types must be accessible to the lookup class. * The type of the method handle will be that of the method. * The given receiver will be bound into the method handle. *

* Equivalent to the following expression: * * {@link #insertArgument}({@link #findVirtual}(defc, name, type), receiver) * * @param receiver the object from which the method is accessed * @param name the name of the method * @param type the type of the method, with the receiver argument omitted * @return the desired method handle * @exception SecurityException TBD * @exception NoAccessException if the method does not exist or access checking fails */ public MethodHandle bind(Object receiver, String name, MethodType type) throws NoAccessException { Class rcvc = receiver.getClass(); // may get NPE MemberName reference = new MemberName(rcvc, name, type); MemberName method = IMPL_NAMES.resolveOrFail(reference, true, lookupClass); checkStatic(false, method, lookupClass); MethodHandle dmh = MethodHandleImpl.findMethod(IMPL_TOKEN, method, true, lookupClass); MethodHandle bmh = MethodHandleImpl.bindReceiver(IMPL_TOKEN, dmh, receiver); if (bmh == null) throw newNoAccessException(method, lookupClass); return bmh; } /** * Make a direct method handle to m, if the lookup class has permission. * If m is non-static, the receiver argument is treated as an initial argument. * If m is virtual, overriding is respected on every call. * Unlike the Core Reflection API, exceptions are not wrapped. * The type of the method handle will be that of the method, * with the receiver type prepended (but only if it is non-static). * If the method's {@code accessible} flag is not set, * access checking is performed immediately on behalf of the lookup class. * If m is not public, do not share the resulting handle with untrusted parties. * @param m the reflected method * @return a method handle which can invoke the reflected method * @exception NoAccessException if access checking fails */ public MethodHandle unreflect(Method m) throws NoAccessException { return unreflectImpl(new MemberName(m), m.isAccessible(), true, lookupClass); } /** * Produce a method handle for a reflected method. * It will bypass checks for overriding methods on the receiver, * as if by the {@code invokespecial} instruction. * The type of the method handle will be that of the method, * with the receiver type prepended. * If the method's {@code accessible} flag is not set, * access checking is performed immediately on behalf of the lookup class, * as if {@code invokespecial} instruction were being linked. * @param m the reflected method * @return a method handle which can invoke the reflected method * @exception NoAccessException if access checking fails */ public MethodHandle unreflectSpecial(Method m, Class specialCaller) throws NoAccessException { checkSpecialCaller(specialCaller, lookupClass); MemberName mname = new MemberName(m); checkStatic(false, mname, lookupClass); return unreflectImpl(mname, m.isAccessible(), false, specialCaller); } /** * Produce a method handle for a reflected constructor. * The type of the method handle will be that of the constructor. * The method handle will perform a {@code newInstance} operation, * creating a new instance of the constructor's class on the * arguments passed to the method handle. *

* If the constructor's {@code accessible} flag is not set, * access checking is performed immediately on behalf of the lookup class, * as if {@code invokespecial} instruction were being linked. * @param ctor the reflected constructor * @return a method handle which can invoke the reflected constructor * @exception NoAccessException if access checking fails */ public MethodHandle unreflectConstructor(Constructor ctor) throws NoAccessException { MemberName m = new MemberName(ctor); return unreflectImpl(m, ctor.isAccessible(), false, lookupClass); } /** * PROVISIONAL API, WORK IN PROGRESS: * Produce a method handle giving read access to a reflected field. * The type of the method handle will have a return type of the field's * value type. Its sole argument will be the field's containing class * (but only if it is non-static). * If the method's {@code accessible} flag is not set, * access checking is performed immediately on behalf of the lookup class. * @param f the reflected field * @return a method handle which can load values from the reflected field * @exception NoAccessException if access checking fails */ public MethodHandle unreflectGetter(Field f) throws NoAccessException { return MethodHandleImpl.accessField(IMPL_TOKEN, new MemberName(f), false, lookupClass); } /** * PROVISIONAL API, WORK IN PROGRESS: * Produce a method handle giving write access to a reflected field. * The type of the method handle will have a void return type. * Its last argument will be the field's value type. * Its other argument will be the field's containing class * (but only if it is non-static). * If the method's {@code accessible} flag is not set, * access checking is performed immediately on behalf of the lookup class. * @param f the reflected field * @return a method handle which can store values into the reflected field * @exception NoAccessException if access checking fails */ public MethodHandle unreflectSetter(Field f) throws NoAccessException { return MethodHandleImpl.accessField(IMPL_TOKEN, new MemberName(f), true, lookupClass); } } static /*must not be public*/ MethodHandle findStaticFrom(Class lookupClass, Class defc, String name, MethodType type) throws NoAccessException { MemberName method = IMPL_NAMES.resolveOrFail(new MemberName(defc, name, type, Modifier.STATIC), true, lookupClass); checkStatic(true, method, lookupClass); return MethodHandleImpl.findMethod(IMPL_TOKEN, method, false, lookupClass); } static void checkStatic(boolean wantStatic, MemberName m, Class lookupClass) { if (wantStatic != m.isStatic()) { String message = wantStatic ? "expected a static method" : "expected a non-static method"; throw newNoAccessException(message, m, lookupClass); } } static void checkSpecialCaller(Class specialCaller, Class lookupClass) { if (lookupClass == Lookup.IMPL_LOOKUP.lookupClass()) return; // privileged action if (lookupClass == null || // public-only access !VerifyAccess.isSamePackageMember(specialCaller, lookupClass)) throw newNoAccessException("no private access", new MemberName(specialCaller), lookupClass); } // Helper for creating handles on reflected methods and constructors. static MethodHandle unreflectImpl(MemberName m, boolean isAccessible, boolean doDispatch, Class lookupClass) { MethodType mtype = m.getInvocationType(); Class defc = m.getDeclaringClass(); int mods = m.getModifiers(); if (m.isStatic()) { if (!isAccessible && VerifyAccess.isAccessible(defc, mods, false, lookupClass) == null) throw newNoAccessException(m, lookupClass); } else { Class constraint; if (isAccessible) { // abbreviated access check for "unlocked" method constraint = doDispatch ? defc : lookupClass; } else { constraint = VerifyAccess.isAccessible(defc, mods, doDispatch, lookupClass); } if (constraint != defc && !constraint.isAssignableFrom(defc)) { if (!defc.isAssignableFrom(constraint)) throw newNoAccessException("receiver must be in caller class", m, lookupClass); mtype = mtype.changeParameterType(0, constraint); } } return MethodHandleImpl.findMethod(IMPL_TOKEN, m, doDispatch, lookupClass); } /** * PROVISIONAL API, WORK IN PROGRESS: * Produce a method handle giving read access to elements of an array. * The type of the method handle will have a return type of the array's * element type. Its first argument will be the array type, * and the second will be {@code int}. * @param arrayClass an array type * @return a method handle which can load values from the given array type * @throws IllegalArgumentException if arrayClass is not an array type */ public static MethodHandle arrayElementGetter(Class arrayClass) throws IllegalArgumentException { return MethodHandleImpl.accessArrayElement(IMPL_TOKEN, arrayClass, false); } /** * PROVISIONAL API, WORK IN PROGRESS: * Produce a method handle giving write access to elements of an array. * The type of the method handle will have a void return type. * Its last argument will be the array's element type. * The first and second arguments will be the array type and int. * @return a method handle which can store values into the array type * @throws IllegalArgumentException if arrayClass is not an array type */ public static MethodHandle arrayElementSetter(Class arrayClass) throws IllegalArgumentException { return MethodHandleImpl.accessArrayElement(IMPL_TOKEN, arrayClass, true); } /// method handle invocation (reflective style) /** * PROVISIONAL API, WORK IN PROGRESS: * Call the {@code invoke} method of a given method handle, * with arguments that exactly match the parameter types of the method handle. * The length of the arguments array must equal the parameter count * of the target's type. * The arguments array is spread into separate arguments, and * basic reference and unboxing conversions are applied. *

* In order to match the type of the target, the following argument * conversions are applied as necessary: *

* The following conversions are not applied: * * The result returned by the call is boxed if it is a primitive, * or forced to null if the return type is void. *

* This call is a convenience method for the following code: *

     *   MethodHandle invoker = MethodHandles.genericInvoker(target.type(), 0, true);
     *   Object result = invoker.invoke(arguments);
     * 
* @param target the method handle to invoke * @param arguments the arguments to pass to the target * @return the result returned by the target */ public static Object invoke(MethodHandle target, Object... arguments) { int argc = arguments == null ? 0 : arguments.length; MethodType type = target.type(); if (argc <= 4) { MethodHandle invoker = invokers(type).genericInvoker(); switch (argc) { case 0: return invoker.invoke(target); case 1: return invoker.invoke(target, arguments[0]); case 2: return invoker.invoke(target, arguments[0], arguments[1]); case 3: return invoker.invoke(target, arguments[0], arguments[1], arguments[2]); case 4: return invoker.invoke(target, arguments[0], arguments[1], arguments[2], arguments[3]); } } MethodHandle invoker = invokers(type).varargsInvoker(); return invoker.invoke(target, arguments); } public static Object invoke_0(MethodHandle target) { MethodHandle invoker = invokers(target.type()).genericInvoker(); return invoker.invoke(target); } public static Object invoke_1(MethodHandle target, Object a0) { MethodHandle invoker = invokers(target.type()).genericInvoker(); return invoker.invoke(target, a0); } public static Object invoke_2(MethodHandle target, Object a0, Object a1) { MethodHandle invoker = invokers(target.type()).genericInvoker(); return invoker.invoke(target, a0, a1); } public static Object invoke_3(MethodHandle target, Object a0, Object a1, Object a2) { MethodHandle invoker = invokers(target.type()).genericInvoker(); return invoker.invoke(target, a0, a1, a2); } public static Object invoke_4(MethodHandle target, Object a0, Object a1, Object a2, Object a3) { MethodHandle invoker = invokers(target.type()).genericInvoker(); return invoker.invoke(target, a0, a1, a2, a3); } /** * PROVISIONAL API, WORK IN PROGRESS: * Give a method handle which will invoke any method handle of the * given type on a standard set of {@code Object} type arguments. * The the resulting invoker will be a method handle with the following * arguments: *
    *
  • a single {@code MethodHandle} target *
  • zero or more {@code Object} values *
  • an optional {@code Object[]} array containing more arguments *
* The invoker will spread the varargs array (if present), apply * reference casts as necessary, and unbox primitive arguments. * The return value of the invoker will be an {@code Object} reference, * boxing a primitive value if the original type returns a primitive, * and always null if the original type returns void. *

* This is a convenience method equivalent to the following code: *

     * MethodHandle invoker = exactInvoker(type);
     * MethodType genericType = MethodType.makeGeneric(objectArgCount, varargs);
     * genericType = genericType.insertParameterType(0, MethodHandle.class);
     * if (!varargs)
     *     return convertArguments(invoker, genericType);
     * else
     *     return spreadArguments(invoker, genericType);
     * 
* @param type the desired target type * @param objectArgCount number of fixed (non-varargs) {@code Object} arguments * @param varargs if true, the invoker will accept a final {@code Object[]} argument * @return a method handle suitable for invoking any method handle of the given type */ static public MethodHandle genericInvoker(MethodType type, int objectArgCount, boolean varargs) { return invokers(type).genericInvoker(); } /** * PROVISIONAL API, WORK IN PROGRESS: * Give a method handle which will take a invoke any method handle of the * given type. The resulting invoker will have a type which is * exactly equal to the desired type, except that it will accept * an additional leading argument of type {@code MethodHandle}. *

* This is a convenience method equivalent to the following code: *

     *     MethodHandles.lookup().findVirtual(MethodHandle.class, "invoke", type);
     * 
* @param type the desired target type * @return a method handle suitable for invoking any method handle of the given type */ static public MethodHandle exactInvoker(MethodType type) { return invokers(type).exactInvoker(); } static private Invokers invokers(MethodType type) { return MethodTypeImpl.invokers(IMPL_TOKEN, type); } /** * WORK IN PROGRESS: * Perform value checking, exactly as if for an adapted method handle. * It is assumed that the given value is either null, of type T0, * or (if T0 is primitive) of the wrapper type corresponding to T0. * The following checks and conversions are made: *
    *
  • If T0 and T1 are references, then a cast to T1 is applied. * (The types do not need to be related in any particular way.) *
  • If T0 and T1 are primitives, then a widening or narrowing * conversion is applied, if one exists. *
  • If T0 is a primitive and T1 a reference, and * T0 has a wrapper type TW, a boxing conversion to TW is applied, * possibly followed by a reference conversion. * T1 must be TW or a supertype. *
  • If T0 is a reference and T1 a primitive, and * T1 has a wrapper type TW, an unboxing conversion is applied, * possibly preceded by a reference conversion. * T0 must be TW or a supertype. *
  • If T1 is void, the return value is discarded *
  • If T0 is void and T1 a reference, a null value is introduced. *
  • If T0 is void and T1 a primitive, a zero value is introduced. *
* If the value is discarded, null will be returned. * @param valueType * @param value * @return the value, converted if necessary * @throws java.lang.ClassCastException if a cast fails */ static T1 checkValue(Class t0, Class t1, Object value) throws ClassCastException { if (t0 == t1) { // no conversion needed; just reassert the same type if (t0.isPrimitive()) return Wrapper.asPrimitiveType(t1).cast(value); else return Wrapper.OBJECT.cast(value, t1); } boolean prim0 = t0.isPrimitive(), prim1 = t1.isPrimitive(); if (!prim0) { // check contract with caller Wrapper.OBJECT.cast(value, t0); if (!prim1) { return Wrapper.OBJECT.cast(value, t1); } // convert reference to primitive by unboxing Wrapper w1 = Wrapper.forPrimitiveType(t1); return w1.cast(value, t1); } // check contract with caller: Wrapper.asWrapperType(t0).cast(value); Wrapper w1 = Wrapper.forPrimitiveType(t1); return w1.cast(value, t1); } static Object checkValue(Class T1, Object value) throws ClassCastException { Class T0; if (value == null) T0 = Object.class; else T0 = value.getClass(); return checkValue(T0, T1, value); } /// method handle modification (creation from other method handles) /** * PROVISIONAL API, WORK IN PROGRESS: * Produce a method handle which adapts the type of the * given method handle to a new type, by pairwise argument conversion, * and/or varargs conversion. * The original type and new type must have the same number of * arguments, or else one or both them the must be varargs types. * The resulting method handle is guaranteed to confess a type * which is equal to the desired new type, with any varargs property erased. *

* If the original type and new type are equal, returns target. *

* The following conversions are applied as needed both to * arguments and return types. Let T0 and T1 be the differing * new and old parameter types (or old and new return types) * for corresponding values passed by the new and old method types. *

* If an ordinary (non-varargs) parameter of the new type is * to be boxed in a varargs parameter of the old type of type T1[], * then T1 is the element type of the varargs array. * Otherwise, if a varargs parameter of the new type of type T0[] * is to be spread into one or more outgoing old type parameters, * then T0 is the element type of the * If the new type is varargs and the old type is not, the varargs * argument will be checked and must be a non-null array of exactly * the right length. If there are no parameters in the old type * corresponding to the new varargs parameter, the varargs argument * is also allowed to be null. *

* Given those types T0, T1, one of the following conversions is applied * if possible: *

    *
  • If T0 and T1 are references, then a cast to T2 is applied, * where T2 is Object if T1 is an interface, else T1. * (The types do not need to be related in any particular way. * The treatment of interfaces follows the usage of the bytecode verifier.) *
  • If T0 and T1 are primitives, then a Java casting * conversion (JLS 5.5) is applied, if one exists. *
  • If T0 and T1 are primitives and one is boolean, * the boolean is treated as a one-bit unsigned integer. * (This treatment follows the usage of the bytecode verifier.) * A conversion from another primitive type behaves as if * it first converts to byte, and then masks all but the low bit. *
  • If T0 is a primitive and T1 a reference, a boxing * conversion is applied if one exists, possibly followed by * an reference conversion to a superclass. * T1 must be a wrapper class or a supertype of one. * If T1 is a wrapper class, T0 is converted if necessary * to T1's primitive type by one of the preceding conversions. * Otherwise, T0 is boxed, and its wrapper converted to T1. *
  • If T0 is a reference and T1 a primitive, an unboxing * conversion is applied if one exists, possibly preceded by * a reference conversion to a wrapper class. * T0 must be a wrapper class or a supertype of one. * If T0 is a wrapper class, its primitive value is converted * if necessary to T1 by one of the preceding conversions. * Otherwise, T0 is converted directly to the wrapper type for T1, * which is then unboxed. *
  • If T1 is void, any returned value is discarded *
  • If T0 is void and T1 a reference, a null value is introduced. *
  • If T0 is void and T1 a primitive, a zero value is introduced. *
* @param target the method handle to invoke after arguments are retyped * @param newType the expected type of the new method handle * @return a method handle which delegates to {@code target} after performing * any necessary argument conversions, and arranges for any * necessary return value conversions * @throws WrongMethodTypeException if the conversion cannot be made */ public static MethodHandle convertArguments(MethodHandle target, MethodType newType) { MethodType oldType = target.type(); if (oldType.equals(newType)) return target; MethodHandle res = MethodHandleImpl.convertArguments(IMPL_TOKEN, target, newType, oldType, null); if (res == null) throw newIllegalArgumentException("cannot convert to "+newType+": "+target); return res; } /** * PROVISIONAL API, WORK IN PROGRESS: * Produce a method handle which adapts the calling sequence of the * given method handle to a new type, by reordering the arguments. * The resulting method handle is guaranteed to confess a type * which is equal to the desired new type. *

* The given array controls the reordering. * Call {@code #I} the number of incoming parameters (the value * {@code newType.parameterCount()}, and call {@code #O} the number * of outgoing parameters (the value {@code target.type().parameterCount()}). * Then the length of the reordering array must be {@code #O}, * and each element must be a non-negative number less than {@code #I}. * For every {@code N} less than {@code #O}, the {@code N}-th * outgoing argument will be taken from the {@code I}-th incoming * argument, where {@code I} is {@code reorder[N]}. *

* The reordering array need not specify an actual permutation. * An incoming argument will be duplicated if its index appears * more than once in the array, and an incoming argument will be dropped * if its index does not appear in the array. *

* Pairwise conversions are applied as needed to arguments and return * values, as with {@link #convertArguments}. * @param target the method handle to invoke after arguments are reordered * @param newType the expected type of the new method handle * @param reorder a string which controls the reordering * @return a method handle which delegates to {@code target} after performing * any necessary argument motion and conversions, and arranges for any * necessary return value conversions */ public static MethodHandle permuteArguments(MethodHandle target, MethodType newType, int[] reorder) { MethodType oldType = target.type(); checkReorder(reorder, newType, oldType); return MethodHandleImpl.convertArguments(IMPL_TOKEN, target, newType, oldType, reorder); } private static void checkReorder(int[] reorder, MethodType newType, MethodType oldType) { if (reorder.length == oldType.parameterCount()) { int limit = newType.parameterCount(); boolean bad = false; for (int i : reorder) { if (i < 0 || i >= limit) { bad = true; break; } } if (!bad) return; } throw newIllegalArgumentException("bad reorder array"); } /** * PROVISIONAL API, WORK IN PROGRESS: * Produce a method handle which adapts the type of the * given method handle to a new type, by spreading the final argument. * The resulting method handle is guaranteed to confess a type * which is equal to the desired new type. *

* The final parameter type of the new type must be an array type T[]. * This is the type of what is called the spread argument. * All other arguments of the new type are called ordinary arguments. *

* The ordinary arguments of the new type are pairwise converted * to the initial parameter types of the old type, according to the * rules in {@link #convertArguments}. * Any additional arguments in the old type * are converted from the array element type T, * again according to the rules in {@link #convertArguments}. * The return value is converted according likewise. *

* The call verifies that the spread argument is in fact an array * of exactly the type length, i.e., the excess number of * arguments in the old type over the ordinary arguments in the new type. * If there are no excess arguments, the spread argument is also * allowed to be null. * @param target the method handle to invoke after the argument is prepended * @param newType the expected type of the new method handle * @return a new method handle which spreads its final argument, * before calling the original method handle */ public static MethodHandle spreadArguments(MethodHandle target, MethodType newType) { MethodType oldType = target.type(); int inargs = newType.parameterCount(); int outargs = oldType.parameterCount(); int spreadPos = inargs - 1; int numSpread = (outargs - spreadPos); MethodHandle res = null; if (spreadPos >= 0 && numSpread >= 0) { res = MethodHandleImpl.spreadArguments(IMPL_TOKEN, target, newType, spreadPos); } if (res == null) { throw newIllegalArgumentException("cannot spread "+newType+" to " +oldType); } return res; } /** * PROVISIONAL API, WORK IN PROGRESS: * Produce a method handle which adapts the type of the * given method handle to a new type, by collecting a series of * trailing arguments into an array. * The resulting method handle is guaranteed to confess a type * which is equal to the desired new type. *

* This method is inverse to {@link #spreadArguments}. * The final parameter type of the old type must be an array type T[], * which is the type of what is called the spread argument. * The trailing arguments of the new type which correspond to * the spread argument are all converted to type T and collected * into an array before the original method is called. *

* ISSUE: Unify this with combineArguments. CollectArguments * is combineArguments with (a) new Object[]{...} as a combiner, * and (b) the combined arguments dropped, in favor of the combined result. * @param target the method handle to invoke after the argument is prepended * @param newType the expected type of the new method handle * @return a new method handle which collects some trailings argument * into an array, before calling the original method handle */ public static MethodHandle collectArguments(MethodHandle target, MethodType newType) { MethodType oldType = target.type(); int inargs = newType.parameterCount(); int outargs = oldType.parameterCount(); int collectPos = outargs - 1; int numCollect = (inargs - collectPos); if (collectPos < 0 || numCollect < 0) throw newIllegalArgumentException("wrong number of arguments"); return MethodHandleImpl.collectArguments(IMPL_TOKEN, target, newType, collectPos); } /** * PROVISIONAL API, WORK IN PROGRESS: * Produce a method handle which calls the original method handle, * after inserting the given argument at the given position. * The type of the new method handle will drop the corresponding argument * type from the original handle's type. *

* The given argument object must match the dropped argument type. * If the dropped argument type is a primitive, the argument object * must be a wrapper, and is unboxed to produce the primitive. *

* The pos may range between zero and N (inclusively), * where N is the number of argument types in target, * meaning to insert the new argument as the first or last (respectively), * or somewhere in between. * @param target the method handle to invoke after the argument is inserted * @param pos where to insert the argument (zero for the first) * @param value the argument to insert * @return a new method handle which inserts an additional argument, * before calling the original method handle */ public static MethodHandle insertArgument(MethodHandle target, int pos, Object value) { MethodType oldType = target.type(); ArrayList> ptypes = new ArrayList>(oldType.parameterList()); int outargs = oldType.parameterCount(); int inargs = outargs - 1; if (pos < 0 || pos >= outargs) throw newIllegalArgumentException("no argument type to append"); Class valueType = ptypes.remove(pos); value = checkValue(valueType, value); if (pos == 0 && !valueType.isPrimitive()) { // At least for now, make bound method handles a special case. // This lets us get by with minimal JVM support, at the expense // of generating signature-specific adapters as Java bytecodes. MethodHandle bmh = MethodHandleImpl.bindReceiver(IMPL_TOKEN, target, value); if (bmh != null) return bmh; // else fall through to general adapter machinery } return MethodHandleImpl.bindArgument(IMPL_TOKEN, target, pos, value); } /** * PROVISIONAL API, WORK IN PROGRESS: * Produce a method handle which calls the original method handle, * after dropping the given argument(s) at the given position. * The type of the new method handle will insert the given argument * type(s), at that position, into the original handle's type. *

* The pos may range between zero and N-1, * where N is the number of argument types in target, * meaning to drop the first or last argument (respectively), * or an argument somewhere in between. * @param target the method handle to invoke after the argument is dropped * @param valueTypes the type(s) of the argument to drop * @param pos which argument to drop (zero for the first) * @return a new method handle which drops an argument of the given type, * before calling the original method handle */ public static MethodHandle dropArguments(MethodHandle target, int pos, Class... valueTypes) { if (valueTypes.length == 0) return target; MethodType oldType = target.type(); int outargs = oldType.parameterCount(); int inargs = outargs + valueTypes.length; if (pos < 0 || pos >= inargs) throw newIllegalArgumentException("no argument type to remove"); ArrayList> ptypes = new ArrayList>(oldType.parameterList()); ptypes.addAll(pos, Arrays.asList(valueTypes)); MethodType newType = MethodType.make(oldType.returnType(), ptypes); return MethodHandleImpl.dropArguments(IMPL_TOKEN, target, newType, pos); } /** * PROVISIONAL API, WORK IN PROGRESS: * Make a method handle which adapts a target method handle, * by guarding it with a test, a boolean-valued method handle. * If the guard fails, a fallback handle is called instead. * All three method handles must have the same corresponding * argument and return types, except that the return type * of the test must be boolean. *

Here is pseudocode for the resulting adapter: *

     * signature T(A...);
     * boolean test(A...);
     * T target(A...);
     * T fallback(A...);
     * T adapter(A... a) {
     *   if (test(a...))
     *     return target(a...);
     *   else
     *     return fallback(a...);
     * }
     * 
* @param test method handle used for test, must return boolean * @param target method handle to call if test passes * @param fallback method handle to call if test fails * @return method handle which incorporates the specified if/then/else logic * @throws IllegalArgumentException if {@code test} does not return boolean, * or if all three method types do not match (with the return * type of {@code test} changed to match that of {@code target}). */ public static MethodHandle guardWithTest(MethodHandle test, MethodHandle target, MethodHandle fallback) { if (target.type() != fallback.type()) throw newIllegalArgumentException("target and fallback types do not match"); if (target.type().changeReturnType(boolean.class) != test.type()) throw newIllegalArgumentException("target and test types do not match"); /* { MethodHandle invoke = findVirtual(MethodHandle.class, "invoke", target.type()); static MethodHandle choose(boolean z, MethodHandle t, MethodHandle f) { return z ? t : f; } static MethodHandle compose(MethodHandle f, MethodHandle g) { Class initargs = g.type().parameterArray(); f = dropArguments(f, 1, initargs); // ignore 2nd copy of args return combineArguments(f, g); } // choose = \z.(z ? target : fallback) MethodHandle choose = findVirtual(MethodHandles.class, "choose", MethodType.make(boolean.class, MethodHandle.class, MethodHandle.class)); choose = appendArgument(choose, target); choose = appendArgument(choose, fallback); MethodHandle dispatch = compose(choose, test); // dispatch = \(a...).(test(a...) ? target : fallback) return combineArguments(invoke, dispatch, 0); // return \(a...).((test(a...) ? target : fallback).invoke(a...)) } */ return MethodHandleImpl.makeGuardWithTest(IMPL_TOKEN, test, target, fallback); } /** * PROVISIONAL API, WORK IN PROGRESS: * Adapt a target method handle {@code target} by first processing * its arguments, and then calling the target. * The initial processing is performed by a second method handle, the {@code combiner}. * After this, control passes to the {@code target}, with the same arguments. *

* The return value of the {@code combiner} is inserted into the argument list * for the {@code target} at the indicated position {@code pos}, if it is non-negative. * Except for this inserted argument (if any), the argument types of * the target {@code target} and the {@code combiner} must be identical. *

* (Note that {@link #dropArguments} can be used to remove any arguments * that either the {@code combiner} or {@code target} does not wish to receive.) *

* The combiner handle must have the same argument types as the * target handle, but must return {@link MethodHandle} instead of * the ultimate return type. The returned method handle, in turn, * is required to have exactly the given final method type. *

Here is pseudocode for the resulting adapter: *

     * signature V(A[pos]..., B...);
     * signature T(A[pos]..., V, B...);
     * T target(A... a, V v, B... b);
     * V combiner(A..., B...);
     * T adapter(A... a, B... b) {
     *   V v = combiner(a..., b...);
     *   return target(a..., v, b...);
     * }
     * 
* @param target the method handle to invoke after arguments are combined * @param pos where the return value of {@code combiner} is to * be inserted as an argument to {@code target} * @param combiner method handle to call initially on the incoming arguments * @return method handle which incorporates the specified dispatch logic * @throws IllegalArgumentException if {@code combiner} does not itself * return either void or the {@code pos}-th argument of {@code target}, * or does not have the same argument types as {@code target} * (minus the inserted argument) */ public static MethodHandle combineArguments(MethodHandle target, int pos, MethodHandle combiner) { MethodType mhType = target.type(); Class combineType = combiner.type().returnType(); MethodType incomingArgs; if (pos < 0) { // No inserted argument; target & combiner must have same argument types. incomingArgs = mhType; if (!incomingArgs.changeReturnType(combineType).equals(combiner.type())) throw newIllegalArgumentException("target and combiner types do not match"); } else { // Inserted argument. if (pos >= mhType.parameterCount() || mhType.parameterType(pos) != combineType) throw newIllegalArgumentException("inserted combiner argument does not match target"); incomingArgs = mhType.dropParameterType(pos); } if (!incomingArgs.changeReturnType(combineType).equals(combiner.type())) { throw newIllegalArgumentException("target and combiner types do not match"); } return MethodHandleImpl.combineArguments(IMPL_TOKEN, target, combiner, pos); } }