/* * Copyright (c) 2008, 2010, Oracle and/or its affiliates. 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. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package java.dyn; import java.lang.reflect.*; import sun.dyn.Access; import sun.dyn.MemberName; import sun.dyn.MethodHandleImpl; import sun.dyn.util.VerifyAccess; import sun.dyn.util.Wrapper; import java.util.List; 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; /** * This class consists exclusively of static methods that operate on or return * method handles. They fall into several categories: * *

* @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. /** * Return a {@link Lookup lookup object} on the caller, * which has the capability to access any method handle that the caller has access to, * including direct method handles to private fields and methods. * This lookup object is a capability which may be delegated to trusted agents. * Do not store it in place where untrusted code can access it. */ public static Lookup lookup() { return new Lookup(); } /** * Return a {@link Lookup lookup object} which is trusted minimally. * It can only be used to create method handles to * publicly accessible fields and methods. *

* As a matter of pure convention, the {@linkplain Lookup#lookupClass lookup class} * of this lookup object will be {@link java.lang.Object}. *

* The lookup class can be changed to any other class {@code C} using an expression of the form * {@linkplain Lookup#in publicLookup().in(C.class)}. * Since all classes have equal access to public names, * such a change would confer no new access rights. */ public static Lookup publicLookup() { return Lookup.PUBLIC_LOOKUP; } /** * A lookup object is a factory for creating method handles, * when the creation requires access checking. * Method handles do not perform * access checks when they are called, but rather when they are created. * (This is a major difference * from reflective {@link Method}, which performs access checking * against every caller, on every call.) * Therefore, method handle access * restrictions must be enforced when a method handle is created. * The caller class against which those restrictions are enforced * is known as the {@linkplain #lookupClass lookup class}. * A lookup object 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 * {@link MethodHandles#lookup 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 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. *

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

How access errors are handled

* 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}. * This rule is applied even if the Java compiler might have created * an wrapper method to access a private method of another class * in the same top-level declaration. * For example, a lookup object created for a nested class {@code C.D} * can access private members within other related classes such as * {@code C}, {@code C.D.E}, or {@code C.B}. */ public static final class Lookup { /** The class on behalf of whom the lookup is being performed. */ private final Class lookupClass; /** The allowed sorts of members which may be looked up (public, etc.), with STATIC for package. */ private final int allowedModes; private static final int PUBLIC = Modifier.PUBLIC, PACKAGE = Modifier.STATIC, PROTECTED = Modifier.PROTECTED, PRIVATE = Modifier.PRIVATE, ALL_MODES = (PUBLIC | PACKAGE | PROTECTED | PRIVATE), TRUSTED = -1; private static int fixmods(int mods) { mods &= (ALL_MODES - PACKAGE); return (mods != 0) ? mods : PACKAGE; } /** Which class is performing the lookup? It is this class against * which checks are performed for visibility and access permissions. *

* The class implies a maximum level of access permission, * but the permissions may be additionally limited by the bitmask * {@link #lookupModes}, which controls whether non-public members * can be accessed. */ public Class lookupClass() { return lookupClass; } // This is just for calling out to MethodHandleImpl. private Class lookupClassOrNull() { return (allowedModes == TRUSTED) ? null : lookupClass; } /** Which types of members can this lookup object produce? * The result is a bit-mask of the {@link Modifier} bits * {@linkplain Modifier#PUBLIC PUBLIC (0x01)}, * {@linkplain Modifier#PROTECTED PROTECTED (0x02)}, * {@linkplain Modifier#PRIVATE PRIVATE (0x04)}, * and {@linkplain Modifier#STATIC STATIC (0x08)}. * The modifier bit {@code STATIC} stands in for the package protection mode, * which does not have an explicit modifier bit. */ public int lookupModes() { return allowedModes & ALL_MODES; } /** 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() { this(getCallerClassAtEntryPoint(), ALL_MODES); // make sure we haven't accidentally picked up a privileged class: checkUnprivilegedlookupClass(lookupClass); } Lookup(Access token, Class lookupClass) { this(lookupClass, ALL_MODES); Access.check(token); } private Lookup(Class lookupClass, int allowedModes) { this.lookupClass = lookupClass; this.allowedModes = allowedModes; } /** * Create a lookup on the specified new lookup class. * The resulting object will report the specified * class as its own {@link #lookupClass}. *

* However, the resulting {@code Lookup} object is guaranteed * to have no more access capabilities than the original. * In particular:

*/ public Lookup in(Class requestedLookupClass) { requestedLookupClass.getClass(); // null check if (allowedModes == TRUSTED) // IMPL_LOOKUP can make any lookup at all return new Lookup(requestedLookupClass, ALL_MODES); if (requestedLookupClass == this.lookupClass) return this; // keep same capabilities int newModes = (allowedModes & (ALL_MODES & ~PROTECTED)); if ((newModes & PACKAGE) != 0 && !VerifyAccess.isSamePackage(this.lookupClass, requestedLookupClass)) { newModes &= ~(PACKAGE|PRIVATE); } if ((newModes & PRIVATE) != 0 && !VerifyAccess.isSamePackageMember(this.lookupClass, requestedLookupClass)) { newModes &= ~PRIVATE; } checkUnprivilegedlookupClass(requestedLookupClass); return new Lookup(requestedLookupClass, newModes); } // Make sure outer class is initialized first. static { IMPL_TOKEN.getClass(); } /** Version of lookup which is trusted minimally. * It can only be used to create method handles to * publicly accessible members. */ static final Lookup PUBLIC_LOOKUP = new Lookup(Object.class, PUBLIC); /** Package-private version of lookup which is trusted. */ static final Lookup IMPL_LOOKUP = new Lookup(Object.class, TRUSTED); 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); } /** Display the name of the class. * If there are restrictions on the access permitted to this lookup, * display those also. */ @Override public String toString() { String modestr; String cname = lookupClass.getName(); switch (allowedModes) { case TRUSTED: return "/trusted"; case PUBLIC: modestr = "/public"; if (lookupClass == Object.class) return modestr; break; case PUBLIC|PACKAGE: return cname + "/package"; case 0: // should not happen return cname + "/empty"; case ALL_MODES: return cname; } StringBuilder buf = new StringBuilder(cname); if ((allowedModes & PUBLIC) != 0) buf.append("/public"); if ((allowedModes & PACKAGE) != 0) buf.append("/package"); if ((allowedModes & PROTECTED) != 0) buf.append("/protected"); if ((allowedModes & PRIVATE) != 0) buf.append("/private"); return buf.toString(); } // 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. assert(Reflection.getCallerClass(CALLER_DEPTH-1) == MethodHandles.class); 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. * (Since static methods do not take receivers, there is no * additional receiver argument inserted into the method handle type, * as there would be with {@link #findVirtual} or {@link #findSpecial}.) * 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 refc 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 refc, String name, MethodType type) throws NoAccessException { MemberName method = resolveOrFail(refc, name, type, true); checkMethod(refc, method, true); return MethodHandleImpl.findMethod(IMPL_TOKEN, method, false, lookupClassOrNull()); } /** * Produce a method handle for a virtual method. * The type of the method handle will be that of the method, * with the receiver type (usually {@code refc}) 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 refc 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 refc, String name, MethodType type) throws NoAccessException { MemberName method = resolveOrFail(refc, name, type, false); checkMethod(refc, method, false); MethodHandle mh = MethodHandleImpl.findMethod(IMPL_TOKEN, method, true, lookupClassOrNull()); return restrictProtectedReceiver(method, mh); } /** * Produce a method handle which creates an object and initializes it, using * the constructor of the specified type. * The parameter types of the method handle will be those of the constructor, * while the return type will be a reference to the constructor's class. * The constructor and all its argument types must be accessible to the lookup class. * If the constructor's class has not yet been initialized, that is done * immediately, before the method handle is returned. *

* Note: The requested type must have a return type of {@code void}. * This is consistent with the JVM's treatment of constructor signatures. * @param refc the class or interface from which the method is accessed * @param type the type of the method, with the receiver argument omitted, and a void return type * @return the desired method handle * @exception SecurityException TBD * @exception NoAccessException if the method does not exist or access checking fails */ public MethodHandle findConstructor(Class refc, MethodType type) throws NoAccessException { String name = ""; MemberName ctor = resolveOrFail(refc, name, type, false, false, lookupClassOrNull()); assert(ctor.isConstructor()); checkAccess(refc, ctor); MethodHandle rawMH = MethodHandleImpl.findMethod(IMPL_TOKEN, ctor, false, lookupClassOrNull()); return MethodHandleImpl.makeAllocator(IMPL_TOKEN, rawMH); } /** * 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 refc the class or interface from which the method is accessed * @param name the name of the method (which must not be "<init>") * @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 refc, String name, MethodType type, Class specialCaller) throws NoAccessException { checkSpecialCaller(specialCaller); MemberName method = resolveOrFail(refc, name, type, false, false, specialCaller); checkMethod(refc, method, false); MethodHandle mh = MethodHandleImpl.findMethod(IMPL_TOKEN, method, false, specialCaller); return restrictReceiver(method, mh, specialCaller); } /** * PROVISIONAL API, WORK IN PROGRESS: * Produce a method handle giving read access to a non-static field. * The type of the method handle will have a return type of the field's * value type. * The method handle's single argument will be the instance containing * the field. * Access checking is performed immediately on behalf of the lookup class. * @param name the field's name * @param type the field's type * @return a method handle which can load values from the field * @exception NoAccessException if access checking fails */ public MethodHandle findGetter(Class refc, String name, Class type) throws NoAccessException { return makeAccessor(refc, name, type, false, false); } /** * PROVISIONAL API, WORK IN PROGRESS: * Produce a method handle giving write access to a non-static field. * The type of the method handle will have a void return type. * The method handle will take two arguments, the instance containing * the field, and the value to be stored. * The second argument will be of the field's value type. * Access checking is performed immediately on behalf of the lookup class. * @param name the field's name * @param type the field's type * @return a method handle which can store values into the field * @exception NoAccessException if access checking fails */ public MethodHandle findSetter(Class refc, String name, Class type) throws NoAccessException { return makeAccessor(refc, name, type, false, true); } /** * PROVISIONAL API, WORK IN PROGRESS: * Produce a method handle giving read access to a static field. * The type of the method handle will have a return type of the field's * value type. * The method handle will take no arguments. * Access checking is performed immediately on behalf of the lookup class. * @param name the field's name * @param type the field's type * @return a method handle which can load values from the field * @exception NoAccessException if access checking fails */ public MethodHandle findStaticGetter(Class refc, String name, Class type) throws NoAccessException { return makeAccessor(refc, name, type, true, false); } /** * PROVISIONAL API, WORK IN PROGRESS: * Produce a method handle giving write access to a static field. * The type of the method handle will have a void return type. * The method handle will take a single * argument, of the field's value type, the value to be stored. * Access checking is performed immediately on behalf of the lookup class. * @param name the field's name * @param type the field's type * @return a method handle which can store values into the field * @exception NoAccessException if access checking fails */ public MethodHandle findStaticSetter(Class refc, String name, Class type) throws NoAccessException { return makeAccessor(refc, name, type, true, true); } /** * 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, * without any insertion of an additional receiver parameter. * The given receiver will be bound into the method handle, * so that every call to the method handle will invoke the * requested method on the given receiver. *

* This is equivalent to the following expression: * * {@link #insertArguments insertArguments}({@link #findVirtual findVirtual}(defc, name, type), receiver) * * where {@code defc} is either {@code receiver.getClass()} or a super * type of that class, in which the requested method is accessible * to the lookup class. * @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 refc = receiver.getClass(); // may get NPE MemberName method = resolveOrFail(refc, name, type, false); checkMethod(refc, method, false); MethodHandle dmh = MethodHandleImpl.findMethod(IMPL_TOKEN, method, true, lookupClassOrNull()); MethodHandle bmh = MethodHandleImpl.bindReceiver(IMPL_TOKEN, dmh, receiver); if (bmh == null) throw newNoAccessException(method, lookupClass()); return bmh; } /** * PROVISIONAL API, WORK IN PROGRESS: * 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 { MemberName method = new MemberName(m); assert(method.isMethod()); if (!m.isAccessible()) checkMethod(method.getDeclaringClass(), method, method.isStatic()); MethodHandle mh = MethodHandleImpl.findMethod(IMPL_TOKEN, method, true, lookupClassOrNull()); if (!m.isAccessible()) mh = restrictProtectedReceiver(method, mh); return mh; } /** * PROVISIONAL API, WORK IN PROGRESS: * Produce a method handle for a reflected method. * It will bypass checks for overriding methods on the receiver, * as if by a {@code invokespecial} instruction from within the {@code specialCaller}. * The type of the method handle will be that of the method, * with the special caller type prepended (and not the receiver of the method). * 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 * @param specialCaller the class nominally calling the 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); MemberName method = new MemberName(m); assert(method.isMethod()); // ignore m.isAccessible: this is a new kind of access checkMethod(m.getDeclaringClass(), method, false); MethodHandle mh = MethodHandleImpl.findMethod(IMPL_TOKEN, method, false, lookupClassOrNull()); return restrictReceiver(method, mh, specialCaller); } /** * PROVISIONAL API, WORK IN PROGRESS: * Produce a method handle for a reflected constructor. * The type of the method handle will be that of the constructor, * with the return type changed to the declaring class. * 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. * @param c the reflected constructor * @return a method handle which can invoke the reflected constructor * @exception NoAccessException if access checking fails */ public MethodHandle unreflectConstructor(Constructor c) throws NoAccessException { MemberName ctor = new MemberName(c); assert(ctor.isConstructor()); if (!c.isAccessible()) checkAccess(c.getDeclaringClass(), ctor); MethodHandle rawCtor = MethodHandleImpl.findMethod(IMPL_TOKEN, ctor, false, lookupClassOrNull()); return MethodHandleImpl.makeAllocator(IMPL_TOKEN, rawCtor); } /** * 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. * If the field is static, the method handle will take no arguments. * Otherwise, its single argument will be the instance containing * the field. * 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 makeAccessor(f.getDeclaringClass(), new MemberName(f), f.isAccessible(), false); } /** * 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. * If the field is static, the method handle will take a single * argument, of the field's value type, the value to be stored. * Otherwise, the two arguments will be the instance containing * the field, and the value to be stored. * 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 makeAccessor(f.getDeclaringClass(), new MemberName(f), f.isAccessible(), true); } /// Helper methods, all package-private. MemberName resolveOrFail(Class refc, String name, Class type, boolean isStatic) throws NoAccessException { checkSymbolicClass(refc); // do this before attempting to resolve int mods = (isStatic ? Modifier.STATIC : 0); return IMPL_NAMES.resolveOrFail(new MemberName(refc, name, type, mods), true, lookupClassOrNull()); } MemberName resolveOrFail(Class refc, String name, MethodType type, boolean isStatic) throws NoAccessException { checkSymbolicClass(refc); // do this before attempting to resolve int mods = (isStatic ? Modifier.STATIC : 0); return IMPL_NAMES.resolveOrFail(new MemberName(refc, name, type, mods), true, lookupClassOrNull()); } MemberName resolveOrFail(Class refc, String name, MethodType type, boolean isStatic, boolean searchSupers, Class specialCaller) throws NoAccessException { checkSymbolicClass(refc); // do this before attempting to resolve int mods = (isStatic ? Modifier.STATIC : 0); return IMPL_NAMES.resolveOrFail(new MemberName(refc, name, type, mods), searchSupers, specialCaller); } void checkSymbolicClass(Class refc) throws NoAccessException { Class caller = lookupClassOrNull(); if (caller != null && !VerifyAccess.isClassAccessible(refc, caller)) throw newNoAccessException("symbolic reference class is not public", new MemberName(refc), caller); } void checkMethod(Class refc, MemberName m, boolean wantStatic) throws NoAccessException { String message; if (m.isConstructor()) message = "expected a method, not a constructor"; else if (!m.isMethod()) message = "expected a method"; else if (wantStatic != m.isStatic()) message = wantStatic ? "expected a static method" : "expected a non-static method"; else { checkAccess(refc, m); return; } throw newNoAccessException(message, m, lookupClass()); } void checkAccess(Class refc, MemberName m) throws NoAccessException { int allowedModes = this.allowedModes; if (allowedModes == TRUSTED) return; int mods = m.getModifiers(); if (Modifier.isPublic(mods) && Modifier.isPublic(refc.getModifiers())) return; // common case int requestedModes = fixmods(mods); // adjust 0 => PACKAGE if ((requestedModes & allowedModes) != 0 && VerifyAccess.isMemberAccessible(refc, m.getDeclaringClass(), mods, lookupClass())) return; if (((requestedModes & ~allowedModes) & PROTECTED) != 0 && VerifyAccess.isSamePackage(m.getDeclaringClass(), lookupClass())) // Protected members can also be checked as if they were package-private. return; throw newNoAccessException(accessFailedMessage(refc, m), m, lookupClass()); } String accessFailedMessage(Class refc, MemberName m) { Class defc = m.getDeclaringClass(); int mods = m.getModifiers(); if (!VerifyAccess.isClassAccessible(defc, lookupClass())) return "class is not public"; if (refc != defc && !VerifyAccess.isClassAccessible(refc, lookupClass())) return "symbolic reference "+refc.getName()+" is not public"; if (Modifier.isPublic(mods)) return "access to public member failed"; // (how?) else if (allowedModes == PUBLIC) return "member is not public"; if (Modifier.isPrivate(mods)) return "member is private"; if (Modifier.isProtected(mods)) return "member is protected"; return "member is private to package"; } void checkSpecialCaller(Class specialCaller) throws NoAccessException { if (allowedModes == TRUSTED) return; if (!VerifyAccess.isSamePackageMember(specialCaller, lookupClass())) throw newNoAccessException("no private access for invokespecial", new MemberName(specialCaller), lookupClass()); } MethodHandle restrictProtectedReceiver(MemberName method, MethodHandle mh) throws NoAccessException { // The accessing class only has the right to use a protected member // on itself or a subclass. Enforce that restriction, from JVMS 5.4.4, etc. if (!method.isProtected() || method.isStatic() || allowedModes == TRUSTED || VerifyAccess.isSamePackageMember(method.getDeclaringClass(), lookupClass())) return mh; else return restrictReceiver(method, mh, lookupClass()); } MethodHandle restrictReceiver(MemberName method, MethodHandle mh, Class caller) throws NoAccessException { assert(!method.isStatic()); Class defc = method.getDeclaringClass(); // receiver type of mh is too wide if (defc.isInterface() || !defc.isAssignableFrom(caller)) { throw newNoAccessException("caller class must be a subclass below the method", method, caller); } MethodType rawType = mh.type(); if (rawType.parameterType(0) == caller) return mh; MethodType narrowType = rawType.changeParameterType(0, caller); return MethodHandleImpl.convertArguments(IMPL_TOKEN, mh, narrowType, rawType, null); } MethodHandle makeAccessor(Class refc, String name, Class type, boolean isStatic, boolean isSetter) throws NoAccessException { MemberName field = resolveOrFail(refc, name, type, isStatic); if (isStatic != field.isStatic()) throw newNoAccessException(isStatic ? "expected a static field" : "expected a non-static field", field, lookupClass()); return makeAccessor(refc, field, false, isSetter); } MethodHandle makeAccessor(Class refc, MemberName field, boolean trusted, boolean isSetter) throws NoAccessException { assert(field.isField()); if (trusted) return MethodHandleImpl.accessField(IMPL_TOKEN, field, isSetter, lookupClassOrNull()); checkAccess(refc, field); MethodHandle mh = MethodHandleImpl.accessField(IMPL_TOKEN, field, isSetter, lookupClassOrNull()); return restrictProtectedReceiver(field, mh); } } /** * 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) /** * @deprecated Alias for MethodHandle.invokeVarargs. */ @Deprecated public static Object invokeVarargs(MethodHandle target, Object... arguments) throws Throwable { return target.invokeVarargs(arguments); } /** * @deprecated Alias for MethodHandle.invokeVarargs. */ @Deprecated public static Object invoke(MethodHandle target, Object... arguments) throws Throwable { return target.invokeVarargs(arguments); } /** * PROVISIONAL API, WORK IN PROGRESS: * Produce a method handle which will invoke any method handle of the * given type on a standard set of {@code Object} type arguments. * The resulting invoker will be a method handle with the following * arguments: *

* The invoker will apply reference casts as necessary and unbox primitive arguments, * as if by {@link #convertArguments}. * 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 method is equivalent to the following code (though it may be more efficient): *

     * MethodHandle invoker = exactInvoker(type);
     * MethodType genericType = type.generic();
     * genericType = genericType.insertParameterType(0, MethodHandle.class);
     * return convertArguments(invoker, genericType);
     * 
* @param type the type of target methods which the invoker will apply to * @return a method handle suitable for invoking any method handle of the given type */ static public MethodHandle genericInvoker(MethodType type) { return invokers(type).genericInvoker(); } /** * PROVISIONAL API, WORK IN PROGRESS: * Produce a method handle which will invoke any method handle of the * given type on a standard set of {@code Object} type arguments * and a single trailing {@code Object[]} array. * The resulting invoker will be a method handle with the following * arguments: * * The invoker will spread the varargs array, 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 method is equivalent to the following code (though it may be more efficient): *

     * MethodHandle invoker = exactInvoker(type);
     * MethodType vaType = MethodType.makeGeneric(objectArgCount, true);
     * vaType = vaType.insertParameterType(0, MethodHandle.class);
     * return spreadArguments(invoker, vaType);
     * 
* @param type the desired target type * @param objectArgCount number of fixed (non-varargs) {@code Object} arguments * @return a method handle suitable for invoking any method handle of the given type */ static public MethodHandle varargsInvoker(MethodType type, int objectArgCount) { if (objectArgCount < 0 || objectArgCount > type.parameterCount()) throw new IllegalArgumentException("bad argument count "+objectArgCount); return invokers(type).varargsInvoker(objectArgCount); } /** * PROVISIONAL API, WORK IN PROGRESS: * Produce 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 method is equivalent to the following code (though it may be more efficient): *

     * 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(); } /** * PROVISIONAL API, WORK IN PROGRESS: * Produce a method handle equivalent to an invokedynamic instruction * which has been linked to the given call site. * Along with {@link Lookup#findVirtual}, {@link Lookup#findStatic}, * and {@link Lookup#findSpecial}, this completes the emulation * of the JVM's {@code invoke} instructions. *

This method is equivalent to the following code: *

     * MethodHandle getTarget, invoker, result;
     * getTarget = lookup().bind(site, "getTarget", methodType(MethodHandle.class));
     * invoker = exactInvoker(site.type());
     * result = foldArguments(invoker, getTarget)
     * 
* @return a method handle which always invokes the call site's target */ public static MethodHandle dynamicInvoker(CallSite site) throws NoAccessException { MethodHandle getCSTarget = GET_TARGET; if (getCSTarget == null) { try { GET_TARGET = getCSTarget = Lookup.IMPL_LOOKUP. findVirtual(CallSite.class, "getTarget", MethodType.methodType(MethodHandle.class)); } catch (NoAccessException ex) { throw new InternalError(); } } MethodHandle getTarget = MethodHandleImpl.bindReceiver(IMPL_TOKEN, getCSTarget, site); MethodHandle invoker = exactInvoker(site.type()); return foldArguments(invoker, getTarget); } private static MethodHandle GET_TARGET = null; // link this lazily, not eagerly static 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 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) /** * Produce a method handle which adapts the type of the * given method handle to a new type by pairwise argument conversion. * The original type and new type must have the same number of arguments. * The resulting method handle is guaranteed to confess a type * which is equal to the desired new type. *

* 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. * Given those types T0, T1, one of the following conversions is applied * if possible: *

    *
  • If T0 and T1 are references, and T1 is not an interface type, * then a cast to T1 is applied. * (The types do not need to be related in any particular way.) *
  • If T0 and T1 are references, and T1 is an interface type, * then the value of type T0 is passed as a T1 without a cast. * (This 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 the return type T1 is void, any returned value is discarded *
  • If the return type T0 is void and T1 a reference, a null value is introduced. *
  • If the return type 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 IllegalArgumentException if the conversion cannot be made * @see MethodHandle#asType */ public static MethodHandle convertArguments(MethodHandle target, MethodType newType) { MethodType oldType = target.type(); if (oldType.equals(newType)) return target; MethodHandle res = null; try { res = MethodHandleImpl.convertArguments(IMPL_TOKEN, target, newType, oldType, null); } catch (IllegalArgumentException ex) { } if (res == null) throw new WrongMethodTypeException("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 as elements to a single argument array. *

* This method may be used as an 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. * @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 trailing 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"); MethodHandle res = MethodHandleImpl.collectArguments(IMPL_TOKEN, target, newType, collectPos, null); if (res == null) { throw newIllegalArgumentException("cannot collect from "+newType+" to " +oldType); } return res; } /** * PROVISIONAL API, WORK IN PROGRESS: * Produce a method handle which calls the original method handle {@code target}, * after inserting the given argument(s) at the given position. * The formal parameters to {@code target} which will be supplied by those * arguments are called bound parameters, because the new method * will contain bindings for those parameters take from {@code values}. * The type of the new method handle will drop the types for the bound * parameters from the original target type, since the new method handle * will no longer require those arguments to be supplied by its callers. *

* Each given argument object must match the corresponding bound parameter type. * If a bound parameter type is a primitive, the argument object * must be a wrapper, and will be unboxed to produce the primitive value. *

* The pos may range between zero and N (inclusively), * where N is the number of argument types in resulting method handle * (after bound parameter types are dropped). * @param target the method handle to invoke after the argument is inserted * @param pos where to insert the argument (zero for the first) * @param values the series of arguments to insert * @return a new method handle which inserts an additional argument, * before calling the original method handle */ public static MethodHandle insertArguments(MethodHandle target, int pos, Object... values) { int insCount = values.length; MethodType oldType = target.type(); ArrayList> ptypes = new ArrayList>(oldType.parameterList()); int outargs = oldType.parameterCount(); int inargs = outargs - insCount; if (inargs < 0) throw newIllegalArgumentException("too many values to insert"); if (pos < 0 || pos > inargs) throw newIllegalArgumentException("no argument type to append"); MethodHandle result = target; for (int i = 0; i < insCount; i++) { Object value = values[i]; Class valueType = oldType.parameterType(pos+i); value = checkValue(valueType, value); if (pos == 0 && !valueType.isPrimitive()) { // At least for now, make bound method handles a special case. MethodHandle bmh = MethodHandleImpl.bindReceiver(IMPL_TOKEN, result, value); if (bmh != null) { result = bmh; continue; } // else fall through to general adapter machinery } result = MethodHandleImpl.bindArgument(IMPL_TOKEN, result, pos, value); } return result; } @Deprecated // "use MethodHandles.insertArguments instead" public static MethodHandle insertArgument(MethodHandle target, int pos, Object value) { return insertArguments(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, * 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. *

* Example: *

     *   import static java.dyn.MethodHandles.*;
     *   import static java.dyn.MethodType.*;
     *   ...
     *   MethodHandle cat = lookup().findVirtual(String.class,
     *     "concat", methodType(String.class, String.class));
     *   System.out.println((String) cat.invokeExact("x", "y")); // xy
     *   MethodHandle d0 = dropArguments(cat, 0, String.class);
     *   System.out.println((String) d0.invokeExact("x", "y", "z")); // yz
     *   MethodHandle d1 = dropArguments(cat, 1, String.class);
     *   System.out.println((String) d1.invokeExact("x", "y", "z")); // xz
     *   MethodHandle d2 = dropArguments(cat, 2, String.class);
     *   System.out.println((String) d2.invokeExact("x", "y", "z")); // xy
     *   MethodHandle d12 = dropArguments(cat, 1, int.class, boolean.class);
     *   System.out.println((String) d12.invokeExact("x", 12, true, "z")); // xz
     * 
* @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, List> valueTypes) { if (valueTypes.size() == 0) return target; MethodType oldType = target.type(); int outargs = oldType.parameterCount(); int inargs = outargs + valueTypes.size(); if (pos < 0 || pos >= inargs) throw newIllegalArgumentException("no argument type to remove"); ArrayList> ptypes = new ArrayList>(oldType.parameterList()); ptypes.addAll(pos, valueTypes); MethodType newType = MethodType.methodType(oldType.returnType(), ptypes); return MethodHandleImpl.dropArguments(IMPL_TOKEN, target, newType, pos); } public static MethodHandle dropArguments(MethodHandle target, int pos, Class... valueTypes) { return dropArguments(target, pos, Arrays.asList(valueTypes)); } /** * PROVISIONAL API, WORK IN PROGRESS: * Adapt a target method handle {@code target} by pre-processing * one or more of its arguments, each with its own unary filter function, * and then calling the target with each pre-processed argument * replaced by the result of its corresponding filter function. *

* The pre-processing is performed by one or more method handles, * specified in the non-null elements of the {@code filters} array. * (If there are no such elements, the original target is returned.) * Each filter (that is, each non-null element of {@code filters}) * is applied to the corresponding argument of the adapter. *

* If a filter {@code F} applies to the {@code N}th argument of * the method handle, then {@code F} must be a method handle which * takes exactly one argument. The type of {@code F}'s sole argument * replaces the corresponding argument type of the target * in the resulting adapted method handle. * The return type of {@code F} must be identical to the corresponding * parameter type of the target. *

* It is an error if there are non-null elements of {@code filters} * which do not correspond to argument positions in the target. * The actual length of the target array may be any number, it need * not be the same as the parameter count of the target type. * (This provides an easy way to filter just the first argument or two * of a target method handle.) *

Here is pseudocode for the resulting adapter: *

     * // there are N arguments in the A sequence
     * T target(A[N]...);
     * [i<N] V[i] filter[i](B[i]) = filters[i] ?: identity;
     * T adapter(B[N]... b) {
     *   A[N] a...;
     *   [i<N] a[i] = filter[i](b[i]);
     *   return target(a...);
     * }
     * 
* @param target the method handle to invoke after arguments are filtered * @param filters method handles to call initially on filtered arguments * @return method handle which incorporates the specified argument filtering logic * @throws IllegalArgumentException if a non-null element of {@code filters} * does not match a corresponding argument type of {@code target} */ public static MethodHandle filterArguments(MethodHandle target, MethodHandle... filters) { MethodType targetType = target.type(); MethodHandle adapter = target; MethodType adapterType = targetType; int pos = -1, maxPos = targetType.parameterCount(); for (MethodHandle filter : filters) { pos += 1; if (filter == null) continue; if (pos >= maxPos) throw newIllegalArgumentException("too many filters"); MethodType filterType = filter.type(); if (filterType.parameterCount() != 1 || filterType.returnType() != targetType.parameterType(pos)) throw newIllegalArgumentException("target and filter types do not match"); adapterType = adapterType.changeParameterType(pos, filterType.parameterType(0)); adapter = MethodHandleImpl.filterArgument(IMPL_TOKEN, adapter, pos, filter); } MethodType midType = adapter.type(); if (midType != adapterType) adapter = MethodHandleImpl.convertArguments(IMPL_TOKEN, adapter, adapterType, midType, null); return adapter; } /** Apply the given filter function to the return value of the given target. */ /*public*/ static MethodHandle filterReturnValue(MethodHandle target, MethodHandle filter) { MethodType targetType = target.type(); MethodType filterType = filter.type(); if (filterType.parameterCount() != 1 || filterType.parameterType(0) != targetType.returnType()) throw newIllegalArgumentException("target and filter types do not match"); // FIXME: Too many nodes here. MethodHandle returner = dropArguments(filter, 0, targetType.parameterList()); return foldArguments(returner, exactInvoker(target.type()).bindTo(target)); } /** * PROVISIONAL API, WORK IN PROGRESS: * Adapt a target method handle {@code target} by pre-processing * some of its arguments, and then calling the target with * the result of the pre-processing, plus all original arguments. *

* The pre-processing is performed by a second method handle, the {@code combiner}. * The first {@code N} arguments passed to the adapter, * are copied to the combiner, which then produces a result. * (Here, {@code N} is defined as the parameter count of the adapter.) * After this, control passes to the {@code target}, with both the result * of the combiner, and all the original incoming arguments. *

* The first argument type of the target must be identical with the * return type of the combiner. * The resulting adapter is the same type as the target, except that the * initial argument type of the target is dropped. *

* (Note that {@link #dropArguments} can be used to remove any arguments * that either the {@code combiner} or {@code target} does not wish to receive. * If some of the incoming arguments are destined only for the combiner, * consider using {@link #collectArguments} instead, since those * arguments will not need to be live on the stack on entry to the * target.) *

* The first argument of the target must be identical with the * return value of the combiner. *

Here is pseudocode for the resulting adapter: *

     * // there are N arguments in the A sequence
     * T target(V, A[N]..., B...);
     * V combiner(A...);
     * T adapter(A... a, B... b) {
     *   V v = combiner(a...);
     *   return target(v, a..., b...);
     * }
     * 
* @param target the method handle to invoke after arguments are combined * @param combiner method handle to call initially on the incoming arguments * @return method handle which incorporates the specified argument folding logic * @throws IllegalArgumentException if the first argument type of * {@code target} is not the same as {@code combiner}'s return type, * or if the following argument types of {@code target} * are not identical with the argument types of {@code combiner} */ public static MethodHandle foldArguments(MethodHandle target, MethodHandle combiner) { MethodType targetType = target.type(); MethodType combinerType = combiner.type(); int foldArgs = combinerType.parameterCount(); boolean ok = (targetType.parameterCount() >= 1 + foldArgs); if (ok && !combinerType.parameterList().equals(targetType.parameterList().subList(1, foldArgs+1))) ok = false; if (ok && !combinerType.returnType().equals(targetType.parameterType(0))) ok = false; if (!ok) throw misMatchedTypes("target and combiner types", targetType, combinerType); MethodType newType = targetType.dropParameterTypes(0, 1); return MethodHandleImpl.foldArguments(IMPL_TOKEN, target, newType, combiner); } // /** // * PROVISIONAL API, WORK IN PROGRESS: // * Adapt a target method handle {@code target} by pre-processing // * some of its arguments to derive a new target method handle. // * Call the new target on the original arguments. // * @param combined method handle to call initially on the incoming arguments // * @return method handle which incorporates the specified dispatching logic // * @throws IllegalArgumentException if the first argument type of // * {@code combiner}'s return type is not {@link MethodHandle}, // * or if the next argument types of {@code target} // * are not identical with the argument types of {@code combiner} // */ // public static // MethodHandle dispatchArguments(MethodType targetType, MethodHandle dispatcher) { // MethodType dispatcherType = dispatcher.type(); // int foldArgs = dispatcherType.parameterCount(); // boolean ok = (targetType.parameterCount() >= foldArgs); // if (!ok) // throw misMatchedTypes("target and dispatcher types", targetType, dispatcherType); // MethodHandle target = exactInvoker(targetType); // return MethodHandleImpl.foldArguments(IMPL_TOKEN, target, targetType, dispatcher); // } /** * 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, and the test is allowed * to have fewer arguments than the other two method handles. *

Here is pseudocode for the resulting adapter: *

     * boolean test(A...);
     * T target(A...,B...);
     * T fallback(A...,B...);
     * T adapter(A... a,B... b) {
     *   if (test(a...))
     *     return target(a..., b...);
     *   else
     *     return fallback(a..., b...);
     * }
     * 
* @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) { MethodType gtype = test.type(); MethodType ttype = target.type(); MethodType ftype = fallback.type(); if (ttype != ftype) throw misMatchedTypes("target and fallback types", ttype, ftype); MethodType gtype2 = ttype.changeReturnType(boolean.class); if (gtype2 != gtype) { if (gtype.returnType() != boolean.class) throw newIllegalArgumentException("guard type is not a predicate "+gtype); int gpc = gtype.parameterCount(), tpc = ttype.parameterCount(); if (gpc < tpc) { test = dropArguments(test, gpc, ttype.parameterList().subList(gpc, tpc)); gtype = test.type(); } if (gtype2 != gtype) throw misMatchedTypes("target and test types", ttype, gtype); } /* { 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.methodType(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).invokeExact(a...)) } */ return MethodHandleImpl.makeGuardWithTest(IMPL_TOKEN, test, target, fallback); } static RuntimeException misMatchedTypes(String what, MethodType t1, MethodType t2) { return newIllegalArgumentException(what + " must match: " + t1 + " != " + t2); } /** * PROVISIONAL API, WORK IN PROGRESS: * Make a method handle which adapts a target method handle, * by running it inside an exception handler. * If the target returns normally, the adapter returns that value. * If an exception matching the specified type is thrown, the fallback * handle is called instead on the exception, plus the original arguments. *

* The handler must have leading parameter of {@code exType} or a supertype, * followed by arguments which correspond (how? TBD) to * all the parameters of the target. * The target and handler must return the same type. *

Here is pseudocode for the resulting adapter: *

     * T target(A...);
     * T handler(ExType, A...);
     * T adapter(A... a) {
     *   try {
     *     return target(a...);
     *   } catch (ExType ex) {
     *     return handler(ex, a...);
     *   }
     * }
     * 
* @param target method handle to call * @param exType the type of exception which the handler will catch * @param handler method handle to call if a matching exception is thrown * @return method handle which incorporates the specified try/catch logic * @throws IllegalArgumentException if {@code handler} does not accept * the given exception type, or if the method handle types do * not match in their return types and their * corresponding parameters */ public static MethodHandle catchException(MethodHandle target, Class exType, MethodHandle handler) { MethodType targetType = target.type(); MethodType handlerType = handler.type(); boolean ok = (targetType.parameterCount() == handlerType.parameterCount() - 1); // for (int i = 0; ok && i < numExArgs; i++) { // if (targetType.parameterType(i) != handlerType.parameterType(1+i)) // ok = false; // } if (!ok) throw newIllegalArgumentException("target and handler types do not match"); return MethodHandleImpl.makeGuardWithCatch(IMPL_TOKEN, target, exType, handler); } /** * Produce a method handle which will throw exceptions of the given {@code exType}. * The method handle will accept a single argument of {@code exType}, * and immediately throw it as an exception. * The method type will nominally specify a return of {@code returnType}. * The return type may be anything convenient: It doesn't matter to the * method handle's behavior, since it will never return normally. */ public static MethodHandle throwException(Class returnType, Class exType) { return MethodHandleImpl.throwException(IMPL_TOKEN, MethodType.methodType(returnType, exType)); } /** * Produce a wrapper instance of the given "SAM" type which redirects its calls to the given method handle. * A SAM type is a type which declares a single abstract method. * Additionally, it must have either no constructor (as an interface) * or have a public or protected constructor of zero arguments (as a class). *

* The resulting instance of the required SAM type will respond to * invocation of the SAM type's single abstract method by calling * the given {@code target} on the incoming arguments, * and returning or throwing whatever the {@code target} * returns or throws. The invocation will be as if by * {@code target.invokeExact}. *

* The method handle may throw an undeclared exception, * which means any checked exception (or other checked throwable) * not declared by the SAM type's single abstract method. * If this happens, the throwable will be wrapped in an instance * of {@link UndeclaredThrowableException} and thrown in that * wrapped form. *

* The wrapper instance is guaranteed to be of a non-public * implementation class C in a package containing no classes * or methods except system-defined classes and methods. * The implementation class C will have no public supertypes * or public methods beyond the following: *

    *
  • the SAM type itself and any methods in the SAM type *
  • the supertypes of the SAM type (if any) and their methods *
  • {@link Object} and its methods *
  • {@link MethodHandleProvider} and its methods *
*

* No stable mapping is promised between the SAM type and * the implementation class C. Over time, several implementation * classes might be used for the same SAM type. *

* This method is not guaranteed to return a distinct * wrapper object for each separate call. If the JVM is able * to prove that a wrapper has already been created for a given * method handle, or for another method handle with the * same behavior, the JVM may return that wrapper in place of * a new wrapper. * @param target the method handle to invoke from the wrapper * @param samType the desired type of the wrapper, a SAM type * @return a correctly-typed wrapper for the given {@code target} * @throws IllegalArgumentException if the {@code target} throws * an undeclared exception */ // ISSUE: Should we delegate equals/hashCode to the targets? // Not useful unless there is a stable equals/hashCode behavior // for MethodHandle, and for MethodHandleProvider.asMethodHandle. public static T asInstance(MethodHandle target, Class samType) { // POC implementation only; violates the above contract several ways final Method sam = getSamMethod(samType); if (sam == null) throw new IllegalArgumentException("not a SAM type: "+samType.getName()); MethodType samMT = MethodType.methodType(sam.getReturnType(), sam.getParameterTypes()); if (!samMT.equals(target.type())) throw new IllegalArgumentException("wrong method type"); final MethodHandle mh = target; return samType.cast(Proxy.newProxyInstance( samType.getClassLoader(), new Class[]{ samType, MethodHandleProvider.class }, new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (method.getDeclaringClass() == MethodHandleProvider.class) { return method.invoke(mh, args); } assert method.equals(sam) : method; return mh.invokeVarargs(args); } })); } private static Method getSamMethod(Class samType) { Method sam = null; for (Method m : samType.getMethods()) { int mod = m.getModifiers(); if (Modifier.isAbstract(mod)) { if (sam != null) return null; // too many abstract methods sam = m; } } if (!samType.isInterface() && getSamConstructor(samType) == null) return null; // wrong kind of constructor return sam; } private static Constructor getSamConstructor(Class samType) { for (Constructor c : samType.getDeclaredConstructors()) { if (c.getParameterTypes().length == 0) { int mod = c.getModifiers(); if (Modifier.isPublic(mod) || Modifier.isProtected(mod)) return c; } } return null; } /*non-public*/ static MethodHandle withTypeHandler(MethodHandle target, MethodHandle typeHandler) { return MethodHandleImpl.withTypeHandler(IMPL_TOKEN, target, typeHandler); } }