1/*
2 * Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package java.lang.invoke;
27
28import java.lang.reflect.*;
29import java.security.AccessController;
30import java.security.PrivilegedAction;
31import sun.invoke.WrapperInstance;
32import java.util.ArrayList;
33import jdk.internal.reflect.CallerSensitive;
34import jdk.internal.reflect.Reflection;
35import sun.reflect.misc.ReflectUtil;
36import static java.lang.invoke.MethodHandleStatics.*;
37
38/**
39 * This class consists exclusively of static methods that help adapt
40 * method handles to other JVM types, such as interfaces.
41 *
42 * @since 1.7
43 */
44public class MethodHandleProxies {
45
46    private MethodHandleProxies() { }  // do not instantiate
47
48    /**
49     * Produces an instance of the given single-method interface which redirects
50     * its calls to the given method handle.
51     * <p>
52     * A single-method interface is an interface which declares a uniquely named method.
53     * When determining the uniquely named method of a single-method interface,
54     * the public {@code Object} methods ({@code toString}, {@code equals}, {@code hashCode})
55     * are disregarded.  For example, {@link java.util.Comparator} is a single-method interface,
56     * even though it re-declares the {@code Object.equals} method.
57     * <p>
58     * The interface must be public.  No additional access checks are performed.
59     * <p>
60     * The resulting instance of the required type will respond to
61     * invocation of the type's uniquely named method by calling
62     * the given target on the incoming arguments,
63     * and returning or throwing whatever the target
64     * returns or throws.  The invocation will be as if by
65     * {@code target.invoke}.
66     * The target's type will be checked before the
67     * instance is created, as if by a call to {@code asType},
68     * which may result in a {@code WrongMethodTypeException}.
69     * <p>
70     * The uniquely named method is allowed to be multiply declared,
71     * with distinct type descriptors.  (E.g., it can be overloaded,
72     * or can possess bridge methods.)  All such declarations are
73     * connected directly to the target method handle.
74     * Argument and return types are adjusted by {@code asType}
75     * for each individual declaration.
76     * <p>
77     * The wrapper instance will implement the requested interface
78     * and its super-types, but no other single-method interfaces.
79     * This means that the instance will not unexpectedly
80     * pass an {@code instanceof} test for any unrequested type.
81     * <p style="font-size:smaller;">
82     * <em>Implementation Note:</em>
83     * Therefore, each instance must implement a unique single-method interface.
84     * Implementations may not bundle together
85     * multiple single-method interfaces onto single implementation classes
86     * in the style of {@link java.awt.AWTEventMulticaster}.
87     * <p>
88     * The method handle may throw an <em>undeclared exception</em>,
89     * which means any checked exception (or other checked throwable)
90     * not declared by the requested type's single abstract method.
91     * If this happens, the throwable will be wrapped in an instance of
92     * {@link java.lang.reflect.UndeclaredThrowableException UndeclaredThrowableException}
93     * and thrown in that wrapped form.
94     * <p>
95     * Like {@link java.lang.Integer#valueOf Integer.valueOf},
96     * {@code asInterfaceInstance} is a factory method whose results are defined
97     * by their behavior.
98     * It is not guaranteed to return a new instance for every call.
99     * <p>
100     * Because of the possibility of {@linkplain java.lang.reflect.Method#isBridge bridge methods}
101     * and other corner cases, the interface may also have several abstract methods
102     * with the same name but having distinct descriptors (types of returns and parameters).
103     * In this case, all the methods are bound in common to the one given target.
104     * The type check and effective {@code asType} conversion is applied to each
105     * method type descriptor, and all abstract methods are bound to the target in common.
106     * Beyond this type check, no further checks are made to determine that the
107     * abstract methods are related in any way.
108     * <p>
109     * Future versions of this API may accept additional types,
110     * such as abstract classes with single abstract methods.
111     * Future versions of this API may also equip wrapper instances
112     * with one or more additional public "marker" interfaces.
113     * <p>
114     * If a security manager is installed, this method is caller sensitive.
115     * During any invocation of the target method handle via the returned wrapper,
116     * the original creator of the wrapper (the caller) will be visible
117     * to context checks requested by the security manager.
118     *
119     * @param <T> the desired type of the wrapper, a single-method interface
120     * @param intfc a class object representing {@code T}
121     * @param target the method handle to invoke from the wrapper
122     * @return a correctly-typed wrapper for the given target
123     * @throws NullPointerException if either argument is null
124     * @throws IllegalArgumentException if the {@code intfc} is not a
125     *         valid argument to this method
126     * @throws WrongMethodTypeException if the target cannot
127     *         be converted to the type required by the requested interface
128     */
129    // Other notes to implementors:
130    // <p>
131    // No stable mapping is promised between the single-method interface and
132    // the implementation class C.  Over time, several implementation
133    // classes might be used for the same type.
134    // <p>
135    // If the implementation is able
136    // to prove that a wrapper of the required type
137    // has already been created for a given
138    // method handle, or for another method handle with the
139    // same behavior, the implementation may return that wrapper in place of
140    // a new wrapper.
141    // <p>
142    // This method is designed to apply to common use cases
143    // where a single method handle must interoperate with
144    // an interface that implements a function-like
145    // API.  Additional variations, such as single-abstract-method classes with
146    // private constructors, or interfaces with multiple but related
147    // entry points, must be covered by hand-written or automatically
148    // generated adapter classes.
149    //
150    @CallerSensitive
151    public static
152    <T> T asInterfaceInstance(final Class<T> intfc, final MethodHandle target) {
153        if (!intfc.isInterface() || !Modifier.isPublic(intfc.getModifiers()))
154            throw newIllegalArgumentException("not a public interface", intfc.getName());
155        final MethodHandle mh;
156        if (System.getSecurityManager() != null) {
157            final Class<?> caller = Reflection.getCallerClass();
158            final ClassLoader ccl = caller != null ? caller.getClassLoader() : null;
159            ReflectUtil.checkProxyPackageAccess(ccl, intfc);
160            mh = ccl != null ? bindCaller(target, caller) : target;
161        } else {
162            mh = target;
163        }
164        ClassLoader proxyLoader = intfc.getClassLoader();
165        if (proxyLoader == null) {
166            ClassLoader cl = Thread.currentThread().getContextClassLoader(); // avoid use of BCP
167            proxyLoader = cl != null ? cl : ClassLoader.getSystemClassLoader();
168        }
169        final Method[] methods = getSingleNameMethods(intfc);
170        if (methods == null)
171            throw newIllegalArgumentException("not a single-method interface", intfc.getName());
172        final MethodHandle[] vaTargets = new MethodHandle[methods.length];
173        for (int i = 0; i < methods.length; i++) {
174            Method sm = methods[i];
175            MethodType smMT = MethodType.methodType(sm.getReturnType(), sm.getParameterTypes());
176            MethodHandle checkTarget = mh.asType(smMT);  // make throw WMT
177            checkTarget = checkTarget.asType(checkTarget.type().changeReturnType(Object.class));
178            vaTargets[i] = checkTarget.asSpreader(Object[].class, smMT.parameterCount());
179        }
180        final InvocationHandler ih = new InvocationHandler() {
181                private Object getArg(String name) {
182                    if ((Object)name == "getWrapperInstanceTarget")  return target;
183                    if ((Object)name == "getWrapperInstanceType")    return intfc;
184                    throw new AssertionError();
185                }
186                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
187                    for (int i = 0; i < methods.length; i++) {
188                        if (method.equals(methods[i]))
189                            return vaTargets[i].invokeExact(args);
190                    }
191                    if (method.getDeclaringClass() == WrapperInstance.class)
192                        return getArg(method.getName());
193                    if (isObjectMethod(method))
194                        return callObjectMethod(proxy, method, args);
195                    throw newInternalError("bad proxy method: "+method);
196                }
197            };
198
199        final Object proxy;
200        if (System.getSecurityManager() != null) {
201            // sun.invoke.WrapperInstance is a restricted interface not accessible
202            // by any non-null class loader.
203            final ClassLoader loader = proxyLoader;
204            proxy = AccessController.doPrivileged(new PrivilegedAction<>() {
205                public Object run() {
206                    return Proxy.newProxyInstance(
207                            loader,
208                            new Class<?>[]{ intfc, WrapperInstance.class },
209                            ih);
210                }
211            });
212        } else {
213            proxy = Proxy.newProxyInstance(proxyLoader,
214                                           new Class<?>[]{ intfc, WrapperInstance.class },
215                                           ih);
216        }
217        return intfc.cast(proxy);
218    }
219
220    private static MethodHandle bindCaller(MethodHandle target, Class<?> hostClass) {
221        return MethodHandleImpl.bindCaller(target, hostClass).withVarargs(target.isVarargsCollector());
222    }
223
224    /**
225     * Determines if the given object was produced by a call to {@link #asInterfaceInstance asInterfaceInstance}.
226     * @param x any reference
227     * @return true if the reference is not null and points to an object produced by {@code asInterfaceInstance}
228     */
229    public static
230    boolean isWrapperInstance(Object x) {
231        return x instanceof WrapperInstance;
232    }
233
234    private static WrapperInstance asWrapperInstance(Object x) {
235        try {
236            if (x != null)
237                return (WrapperInstance) x;
238        } catch (ClassCastException ex) {
239        }
240        throw newIllegalArgumentException("not a wrapper instance");
241    }
242
243    /**
244     * Produces or recovers a target method handle which is behaviorally
245     * equivalent to the unique method of this wrapper instance.
246     * The object {@code x} must have been produced by a call to {@link #asInterfaceInstance asInterfaceInstance}.
247     * This requirement may be tested via {@link #isWrapperInstance isWrapperInstance}.
248     * @param x any reference
249     * @return a method handle implementing the unique method
250     * @throws IllegalArgumentException if the reference x is not to a wrapper instance
251     */
252    public static
253    MethodHandle wrapperInstanceTarget(Object x) {
254        return asWrapperInstance(x).getWrapperInstanceTarget();
255    }
256
257    /**
258     * Recovers the unique single-method interface type for which this wrapper instance was created.
259     * The object {@code x} must have been produced by a call to {@link #asInterfaceInstance asInterfaceInstance}.
260     * This requirement may be tested via {@link #isWrapperInstance isWrapperInstance}.
261     * @param x any reference
262     * @return the single-method interface type for which the wrapper was created
263     * @throws IllegalArgumentException if the reference x is not to a wrapper instance
264     */
265    public static
266    Class<?> wrapperInstanceType(Object x) {
267        return asWrapperInstance(x).getWrapperInstanceType();
268    }
269
270    private static
271    boolean isObjectMethod(Method m) {
272        switch (m.getName()) {
273        case "toString":
274            return (m.getReturnType() == String.class
275                    && m.getParameterTypes().length == 0);
276        case "hashCode":
277            return (m.getReturnType() == int.class
278                    && m.getParameterTypes().length == 0);
279        case "equals":
280            return (m.getReturnType() == boolean.class
281                    && m.getParameterTypes().length == 1
282                    && m.getParameterTypes()[0] == Object.class);
283        }
284        return false;
285    }
286
287    private static
288    Object callObjectMethod(Object self, Method m, Object[] args) {
289        assert(isObjectMethod(m)) : m;
290        switch (m.getName()) {
291        case "toString":
292            return self.getClass().getName() + "@" + Integer.toHexString(self.hashCode());
293        case "hashCode":
294            return System.identityHashCode(self);
295        case "equals":
296            return (self == args[0]);
297        }
298        return null;
299    }
300
301    private static
302    Method[] getSingleNameMethods(Class<?> intfc) {
303        ArrayList<Method> methods = new ArrayList<>();
304        String uniqueName = null;
305        for (Method m : intfc.getMethods()) {
306            if (isObjectMethod(m))  continue;
307            if (!Modifier.isAbstract(m.getModifiers()))  continue;
308            String mname = m.getName();
309            if (uniqueName == null)
310                uniqueName = mname;
311            else if (!uniqueName.equals(mname))
312                return null;  // too many abstract methods
313            methods.add(m);
314        }
315        if (uniqueName == null)  return null;
316        return methods.toArray(new Method[methods.size()]);
317    }
318}
319