/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package java.lang.reflect; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Set; import libcore.util.EmptyArray; /** * {@code Proxy} defines methods for creating dynamic proxy classes and instances. * A proxy class implements a declared set of interfaces and delegates method * invocations to an {@code InvocationHandler}. * * @see InvocationHandler * @since 1.3 */ public class Proxy implements Serializable { private static final long serialVersionUID = -2222568056686623797L; private static int nextClassNameIndex = 0; /** * Orders methods by their name, parameters, return type and inheritance relationship. * * @hide */ private static final Comparator ORDER_BY_SIGNATURE_AND_SUBTYPE = new Comparator() { @Override public int compare(Method a, Method b) { int comparison = Method.ORDER_BY_SIGNATURE.compare(a, b); if (comparison != 0) { return comparison; } Class aClass = a.getDeclaringClass(); Class bClass = b.getDeclaringClass(); if (aClass == bClass) { return 0; } else if (aClass.isAssignableFrom(bClass)) { return 1; } else if (bClass.isAssignableFrom(aClass)) { return -1; } else { return 0; } } }; /** The invocation handler on which the method calls are dispatched. */ protected InvocationHandler h; @SuppressWarnings("unused") private Proxy() { } /** * Constructs a new {@code Proxy} instance with the specified invocation * handler. * * @param h * the invocation handler for the newly created proxy */ protected Proxy(InvocationHandler h) { this.h = h; } /** * Returns the dynamically built {@code Class} for the specified interfaces. * Creates a new {@code Class} when necessary. The order of the interfaces * is relevant. Invocations of this method with the same interfaces but * different order result in different generated classes. The interfaces * must be visible from the supplied class loader; no duplicates are * permitted. All non-public interfaces must be defined in the same package. * * @param loader * the class loader that will define the proxy class * @param interfaces * an array of {@code Class} objects, each one identifying an * interface that will be implemented by the returned proxy * class * @return a proxy class that implements all of the interfaces referred to * in the contents of {@code interfaces} * @throws IllegalArgumentException * if any of the interface restrictions are violated * @throws NullPointerException * if either {@code interfaces} or any of its elements are * {@code null} */ public static Class getProxyClass(ClassLoader loader, Class... interfaces) throws IllegalArgumentException { if (loader == null) { loader = ClassLoader.getSystemClassLoader(); } if (interfaces == null) { throw new NullPointerException("interfaces == null"); } // Make a copy of the list early on because we're using the list as a // cache key and we don't want it changing under us. final List> interfaceList = new ArrayList>(interfaces.length); Collections.addAll(interfaceList, interfaces); // We use a HashSet *only* for detecting duplicates and null entries. We // can't use it as our cache key because we need to preserve the order in // which these interfaces were specified. (Different orders should define // different proxies.) final Set> interfaceSet = new HashSet>(interfaceList); if (interfaceSet.contains(null)) { throw new NullPointerException("interface list contains null: " + interfaceList); } if (interfaceSet.size() != interfaces.length) { throw new IllegalArgumentException("duplicate interface in list: " + interfaceList); } synchronized (loader.proxyCache) { Class proxy = loader.proxyCache.get(interfaceList); if (proxy != null) { return proxy; } } String commonPackageName = null; for (Class c : interfaces) { if (!c.isInterface()) { throw new IllegalArgumentException(c + " is not an interface"); } if (!isVisibleToClassLoader(loader, c)) { throw new IllegalArgumentException(c + " is not visible from class loader"); } if (!Modifier.isPublic(c.getModifiers())) { String packageName = c.getPackageName$(); if (packageName == null) { packageName = ""; } if (commonPackageName != null && !commonPackageName.equals(packageName)) { throw new IllegalArgumentException( "non-public interfaces must be in the same package"); } commonPackageName = packageName; } } List methods = getMethods(interfaces); Collections.sort(methods, ORDER_BY_SIGNATURE_AND_SUBTYPE); validateReturnTypes(methods); List[]> exceptions = deduplicateAndGetExceptions(methods); ArtMethod[] methodsArray = new ArtMethod[methods.size()]; for (int i = 0; i < methodsArray.length; i++) { methodsArray[i] = methods.get(i).getArtMethod(); } Class[][] exceptionsArray = exceptions.toArray(new Class[exceptions.size()][]); String baseName = commonPackageName != null && !commonPackageName.isEmpty() ? commonPackageName + ".$Proxy" : "$Proxy"; Class result; synchronized (loader.proxyCache) { result = loader.proxyCache.get(interfaceSet); if (result == null) { String name = baseName + nextClassNameIndex++; result = generateProxy(name, interfaces, loader, methodsArray, exceptionsArray); loader.proxyCache.put(interfaceList, result); } } return result; } private static boolean isVisibleToClassLoader(ClassLoader loader, Class c) { try { return loader == c.getClassLoader() || c == Class.forName(c.getName(), false, loader); } catch (ClassNotFoundException ex) { return false; } } /** * Returns an instance of the dynamically built class for the specified * interfaces. Method invocations on the returned instance are forwarded to * the specified invocation handler. The interfaces must be visible from the * supplied class loader; no duplicates are permitted. All non-public * interfaces must be defined in the same package. * * @param loader * the class loader that will define the proxy class * @param interfaces * an array of {@code Class} objects, each one identifying an * interface that will be implemented by the returned proxy * object * @param invocationHandler * the invocation handler that handles the dispatched method * invocations * @return a new proxy object that delegates to the handler {@code h} * @throws IllegalArgumentException * if any of the interface restrictions are violated * @throws NullPointerException * if the interfaces or any of its elements are null */ public static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler invocationHandler) throws IllegalArgumentException { if (invocationHandler == null) { throw new NullPointerException("invocationHandler == null"); } Exception cause; try { return getProxyClass(loader, interfaces) .getConstructor(InvocationHandler.class) .newInstance(invocationHandler); } catch (NoSuchMethodException e) { cause = e; } catch (IllegalAccessException e) { cause = e; } catch (InstantiationException e) { cause = e; } catch (InvocationTargetException e) { cause = e; } AssertionError error = new AssertionError(); error.initCause(cause); throw error; } /** * Indicates whether or not the specified class is a dynamically generated * proxy class. * * @param cl * the class * @return {@code true} if the class is a proxy class, {@code false} * otherwise * @throws NullPointerException * if the class is {@code null} */ public static boolean isProxyClass(Class cl) { return cl.isProxy(); } /** * Returns the invocation handler of the specified proxy instance. * * @param proxy * the proxy instance * @return the invocation handler of the specified proxy instance * @throws IllegalArgumentException * if the supplied {@code proxy} is not a proxy object */ public static InvocationHandler getInvocationHandler(Object proxy) throws IllegalArgumentException { // TODO: return false for subclasses of Proxy not created by generateProxy() if (!(proxy instanceof Proxy)) { throw new IllegalArgumentException("not a proxy instance"); } return ((Proxy) proxy).h; } private static List getMethods(Class[] interfaces) { List result = new ArrayList(); try { result.add(Object.class.getMethod("equals", Object.class)); result.add(Object.class.getMethod("hashCode", EmptyArray.CLASS)); result.add(Object.class.getMethod("toString", EmptyArray.CLASS)); } catch (NoSuchMethodException e) { throw new AssertionError(); } getMethodsRecursive(interfaces, result); return result; } /** * Fills {@code proxiedMethods} with the methods of {@code interfaces} and * the interfaces they extend. May contain duplicates. */ private static void getMethodsRecursive(Class[] interfaces, List methods) { for (Class i : interfaces) { getMethodsRecursive(i.getInterfaces(), methods); Collections.addAll(methods, i.getDeclaredMethods()); } } /** * Throws if any two methods in {@code methods} have the same name and * parameters but incompatible return types. * * @param methods the methods to find exceptions for, ordered by name and * signature. */ private static void validateReturnTypes(List methods) { Method vs = null; for (Method method : methods) { if (vs == null || !vs.equalNameAndParameters(method)) { vs = method; // this has a different name or parameters continue; } Class returnType = method.getReturnType(); Class vsReturnType = vs.getReturnType(); if (returnType.isInterface() && vsReturnType.isInterface()) { // all interfaces are mutually compatible } else if (vsReturnType.isAssignableFrom(returnType)) { vs = method; // the new return type is a subtype; use it instead } else if (!returnType.isAssignableFrom(vsReturnType)) { throw new IllegalArgumentException("proxied interface methods have incompatible " + "return types:\n " + vs + "\n " + method); } } } /** * Remove methods that have the same name, parameters and return type. This * computes the exceptions of each method; this is the intersection of the * exceptions of equivalent methods. * * @param methods the methods to find exceptions for, ordered by name and * signature. */ private static List[]> deduplicateAndGetExceptions(List methods) { List[]> exceptions = new ArrayList[]>(methods.size()); for (int i = 0; i < methods.size(); ) { Method method = methods.get(i); Class[] exceptionTypes = method.getExceptionTypes(); if (i > 0 && Method.ORDER_BY_SIGNATURE.compare(method, methods.get(i - 1)) == 0) { exceptions.set(i - 1, intersectExceptions(exceptions.get(i - 1), exceptionTypes)); methods.remove(i); } else { exceptions.add(exceptionTypes); i++; } } return exceptions; } /** * Returns the exceptions that are declared in both {@code aExceptions} and * {@code bExceptions}. If an exception type in one array is a subtype of an * exception from the other, the subtype is included in the intersection. */ private static Class[] intersectExceptions(Class[] aExceptions, Class[] bExceptions) { if (aExceptions.length == 0 || bExceptions.length == 0) { return EmptyArray.CLASS; } if (Arrays.equals(aExceptions, bExceptions)) { return aExceptions; } Set> intersection = new HashSet>(); for (Class a : aExceptions) { for (Class b : bExceptions) { if (a.isAssignableFrom(b)) { intersection.add(b); } else if (b.isAssignableFrom(a)) { intersection.add(a); } } } return intersection.toArray(new Class[intersection.size()]); } private static native Class generateProxy(String name, Class[] interfaces, ClassLoader loader, ArtMethod[] methods, Class[][] exceptions); /* * The VM clones this method's descriptor when generating a proxy class. * There is no implementation. */ private static native void constructorPrototype(InvocationHandler h); static Object invoke(Proxy proxy, ArtMethod method, Object[] args) throws Throwable { InvocationHandler h = proxy.h; return h.invoke(proxy, new Method(method), args); } }