1 /*
2  *  Licensed to the Apache Software Foundation (ASF) under one or more
3  *  contributor license agreements.  See the NOTICE file distributed with
4  *  this work for additional information regarding copyright ownership.
5  *  The ASF licenses this file to You under the Apache License, Version 2.0
6  *  (the "License"); you may not use this file except in compliance with
7  *  the License.  You may obtain a copy of the License at
8  *
9  *     http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *  Unless required by applicable law or agreed to in writing, software
12  *  distributed under the License is distributed on an "AS IS" BASIS,
13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *  See the License for the specific language governing permissions and
15  *  limitations under the License.
16  */
17 
18 package java.lang.reflect;
19 
20 import java.io.Serializable;
21 import java.util.ArrayList;
22 import java.util.Arrays;
23 import java.util.Collections;
24 import java.util.Comparator;
25 import java.util.HashSet;
26 import java.util.List;
27 import java.util.Set;
28 import libcore.util.EmptyArray;
29 
30 /**
31  * {@code Proxy} defines methods for creating dynamic proxy classes and instances.
32  * A proxy class implements a declared set of interfaces and delegates method
33  * invocations to an {@code InvocationHandler}.
34  *
35  * @see InvocationHandler
36  * @since 1.3
37  */
38 public class Proxy implements Serializable {
39 
40     private static final long serialVersionUID = -2222568056686623797L;
41 
42     private static int nextClassNameIndex = 0;
43 
44     /**
45      * Orders methods by their name, parameters, return type and inheritance relationship.
46      *
47      * @hide
48      */
49     private static final Comparator<Method> ORDER_BY_SIGNATURE_AND_SUBTYPE = new Comparator<Method>() {
50         @Override public int compare(Method a, Method b) {
51             int comparison = Method.ORDER_BY_SIGNATURE.compare(a, b);
52             if (comparison != 0) {
53                 return comparison;
54             }
55             Class<?> aClass = a.getDeclaringClass();
56             Class<?> bClass = b.getDeclaringClass();
57             if (aClass == bClass) {
58                 return 0;
59             } else if (aClass.isAssignableFrom(bClass)) {
60                 return 1;
61             } else if (bClass.isAssignableFrom(aClass)) {
62                 return -1;
63             } else {
64                 return 0;
65             }
66         }
67     };
68 
69     /** The invocation handler on which the method calls are dispatched. */
70     protected InvocationHandler h;
71 
72     @SuppressWarnings("unused")
Proxy()73     private Proxy() {
74     }
75 
76     /**
77      * Constructs a new {@code Proxy} instance with the specified invocation
78      * handler.
79      *
80      * @param h
81      *            the invocation handler for the newly created proxy
82      */
Proxy(InvocationHandler h)83     protected Proxy(InvocationHandler h) {
84         this.h = h;
85     }
86 
87     /**
88      * Returns the dynamically built {@code Class} for the specified interfaces.
89      * Creates a new {@code Class} when necessary. The order of the interfaces
90      * is relevant. Invocations of this method with the same interfaces but
91      * different order result in different generated classes. The interfaces
92      * must be visible from the supplied class loader; no duplicates are
93      * permitted. All non-public interfaces must be defined in the same package.
94      *
95      * @param loader
96      *            the class loader that will define the proxy class
97      * @param interfaces
98      *            an array of {@code Class} objects, each one identifying an
99      *            interface that will be implemented by the returned proxy
100      *            class
101      * @return a proxy class that implements all of the interfaces referred to
102      *         in the contents of {@code interfaces}
103      * @throws IllegalArgumentException
104      *                if any of the interface restrictions are violated
105      * @throws NullPointerException
106      *                if either {@code interfaces} or any of its elements are
107      *                {@code null}
108      */
getProxyClass(ClassLoader loader, Class<?>... interfaces)109     public static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces)
110             throws IllegalArgumentException {
111         if (loader == null) {
112             loader = ClassLoader.getSystemClassLoader();
113         }
114 
115         if (interfaces == null) {
116             throw new NullPointerException("interfaces == null");
117         }
118 
119         // Make a copy of the list early on because we're using the list as a
120         // cache key and we don't want it changing under us.
121         final List<Class<?>> interfaceList = new ArrayList<Class<?>>(interfaces.length);
122         Collections.addAll(interfaceList, interfaces);
123 
124         // We use a HashSet *only* for detecting duplicates and null entries. We
125         // can't use it as our cache key because we need to preserve the order in
126         // which these interfaces were specified. (Different orders should define
127         // different proxies.)
128         final Set<Class<?>> interfaceSet = new HashSet<Class<?>>(interfaceList);
129         if (interfaceSet.contains(null)) {
130             throw new NullPointerException("interface list contains null: " + interfaceList);
131         }
132 
133         if (interfaceSet.size() != interfaces.length) {
134             throw new IllegalArgumentException("duplicate interface in list: " + interfaceList);
135         }
136 
137         synchronized (loader.proxyCache) {
138             Class<?> proxy = loader.proxyCache.get(interfaceList);
139             if (proxy != null) {
140                 return proxy;
141             }
142         }
143 
144         String commonPackageName = null;
145         for (Class<?> c : interfaces) {
146             if (!c.isInterface()) {
147                 throw new IllegalArgumentException(c + " is not an interface");
148             }
149             if (!isVisibleToClassLoader(loader, c)) {
150                 throw new IllegalArgumentException(c + " is not visible from class loader");
151             }
152             if (!Modifier.isPublic(c.getModifiers())) {
153                 String packageName = c.getPackageName$();
154                 if (packageName == null) {
155                     packageName = "";
156                 }
157                 if (commonPackageName != null && !commonPackageName.equals(packageName)) {
158                     throw new IllegalArgumentException(
159                             "non-public interfaces must be in the same package");
160                 }
161                 commonPackageName = packageName;
162             }
163         }
164 
165         List<Method> methods = getMethods(interfaces);
166         Collections.sort(methods, ORDER_BY_SIGNATURE_AND_SUBTYPE);
167         validateReturnTypes(methods);
168         List<Class<?>[]> exceptions = deduplicateAndGetExceptions(methods);
169 
170         ArtMethod[] methodsArray = new ArtMethod[methods.size()];
171         for (int i = 0; i < methodsArray.length; i++) {
172             methodsArray[i] = methods.get(i).getArtMethod();
173         }
174         Class<?>[][] exceptionsArray = exceptions.toArray(new Class<?>[exceptions.size()][]);
175 
176         String baseName = commonPackageName != null && !commonPackageName.isEmpty()
177                 ? commonPackageName + ".$Proxy"
178                 : "$Proxy";
179 
180         Class<?> result;
181         synchronized (loader.proxyCache) {
182             result = loader.proxyCache.get(interfaceSet);
183             if (result == null) {
184                 String name = baseName + nextClassNameIndex++;
185                 result = generateProxy(name, interfaces, loader, methodsArray, exceptionsArray);
186                 loader.proxyCache.put(interfaceList, result);
187             }
188         }
189 
190         return result;
191     }
192 
isVisibleToClassLoader(ClassLoader loader, Class<?> c)193     private static boolean isVisibleToClassLoader(ClassLoader loader, Class<?> c) {
194         try {
195             return loader == c.getClassLoader() || c == Class.forName(c.getName(), false, loader);
196         } catch (ClassNotFoundException ex) {
197             return false;
198         }
199     }
200 
201     /**
202      * Returns an instance of the dynamically built class for the specified
203      * interfaces. Method invocations on the returned instance are forwarded to
204      * the specified invocation handler. The interfaces must be visible from the
205      * supplied class loader; no duplicates are permitted. All non-public
206      * interfaces must be defined in the same package.
207      *
208      * @param loader
209      *            the class loader that will define the proxy class
210      * @param interfaces
211      *            an array of {@code Class} objects, each one identifying an
212      *            interface that will be implemented by the returned proxy
213      *            object
214      * @param invocationHandler
215      *            the invocation handler that handles the dispatched method
216      *            invocations
217      * @return a new proxy object that delegates to the handler {@code h}
218      * @throws IllegalArgumentException
219      *                if any of the interface restrictions are violated
220      * @throws NullPointerException
221      *                if the interfaces or any of its elements are null
222      */
newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler invocationHandler)223     public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,
224                                           InvocationHandler invocationHandler)
225             throws IllegalArgumentException {
226 
227         if (invocationHandler == null) {
228             throw new NullPointerException("invocationHandler == null");
229         }
230         Exception cause;
231         try {
232             return getProxyClass(loader, interfaces)
233                     .getConstructor(InvocationHandler.class)
234                     .newInstance(invocationHandler);
235         } catch (NoSuchMethodException e) {
236             cause = e;
237         } catch (IllegalAccessException e) {
238             cause = e;
239         } catch (InstantiationException e) {
240             cause = e;
241         } catch (InvocationTargetException e) {
242             cause = e;
243         }
244         AssertionError error = new AssertionError();
245         error.initCause(cause);
246         throw error;
247     }
248 
249     /**
250      * Indicates whether or not the specified class is a dynamically generated
251      * proxy class.
252      *
253      * @param cl
254      *            the class
255      * @return {@code true} if the class is a proxy class, {@code false}
256      *         otherwise
257      * @throws NullPointerException
258      *                if the class is {@code null}
259      */
isProxyClass(Class<?> cl)260     public static boolean isProxyClass(Class<?> cl) {
261         return cl.isProxy();
262     }
263 
264     /**
265      * Returns the invocation handler of the specified proxy instance.
266      *
267      * @param proxy
268      *            the proxy instance
269      * @return the invocation handler of the specified proxy instance
270      * @throws IllegalArgumentException
271      *                if the supplied {@code proxy} is not a proxy object
272      */
getInvocationHandler(Object proxy)273     public static InvocationHandler getInvocationHandler(Object proxy)
274             throws IllegalArgumentException {
275         // TODO: return false for subclasses of Proxy not created by generateProxy()
276         if (!(proxy instanceof Proxy)) {
277             throw new IllegalArgumentException("not a proxy instance");
278         }
279         return ((Proxy) proxy).h;
280     }
281 
getMethods(Class<?>[] interfaces)282     private static List<Method> getMethods(Class<?>[] interfaces) {
283         List<Method> result = new ArrayList<Method>();
284         try {
285             result.add(Object.class.getMethod("equals", Object.class));
286             result.add(Object.class.getMethod("hashCode", EmptyArray.CLASS));
287             result.add(Object.class.getMethod("toString", EmptyArray.CLASS));
288         } catch (NoSuchMethodException e) {
289             throw new AssertionError();
290         }
291 
292         getMethodsRecursive(interfaces, result);
293         return result;
294     }
295 
296     /**
297      * Fills {@code proxiedMethods} with the methods of {@code interfaces} and
298      * the interfaces they extend. May contain duplicates.
299      */
getMethodsRecursive(Class<?>[] interfaces, List<Method> methods)300     private static void getMethodsRecursive(Class<?>[] interfaces, List<Method> methods) {
301         for (Class<?> i : interfaces) {
302             getMethodsRecursive(i.getInterfaces(), methods);
303             Collections.addAll(methods, i.getDeclaredMethods());
304         }
305     }
306 
307     /**
308      * Throws if any two methods in {@code methods} have the same name and
309      * parameters but incompatible return types.
310      *
311      * @param methods the methods to find exceptions for, ordered by name and
312      *     signature.
313      */
validateReturnTypes(List<Method> methods)314     private static void validateReturnTypes(List<Method> methods) {
315         Method vs = null;
316         for (Method method : methods) {
317             if (vs == null || !vs.equalNameAndParameters(method)) {
318                 vs = method; // this has a different name or parameters
319                 continue;
320             }
321             Class<?> returnType = method.getReturnType();
322             Class<?> vsReturnType = vs.getReturnType();
323             if (returnType.isInterface() && vsReturnType.isInterface()) {
324                 // all interfaces are mutually compatible
325             } else if (vsReturnType.isAssignableFrom(returnType)) {
326                 vs = method; // the new return type is a subtype; use it instead
327             } else if (!returnType.isAssignableFrom(vsReturnType)) {
328                 throw new IllegalArgumentException("proxied interface methods have incompatible "
329                         + "return types:\n  " + vs + "\n  " + method);
330             }
331         }
332     }
333 
334     /**
335      * Remove methods that have the same name, parameters and return type. This
336      * computes the exceptions of each method; this is the intersection of the
337      * exceptions of equivalent methods.
338      *
339      * @param methods the methods to find exceptions for, ordered by name and
340      *     signature.
341      */
deduplicateAndGetExceptions(List<Method> methods)342     private static List<Class<?>[]> deduplicateAndGetExceptions(List<Method> methods) {
343         List<Class<?>[]> exceptions = new ArrayList<Class<?>[]>(methods.size());
344 
345         for (int i = 0; i < methods.size(); ) {
346             Method method = methods.get(i);
347             Class<?>[] exceptionTypes = method.getExceptionTypes();
348 
349             if (i > 0 && Method.ORDER_BY_SIGNATURE.compare(method, methods.get(i - 1)) == 0) {
350                 exceptions.set(i - 1, intersectExceptions(exceptions.get(i - 1), exceptionTypes));
351                 methods.remove(i);
352             } else {
353                 exceptions.add(exceptionTypes);
354                 i++;
355             }
356         }
357         return exceptions;
358     }
359 
360     /**
361      * Returns the exceptions that are declared in both {@code aExceptions} and
362      * {@code bExceptions}. If an exception type in one array is a subtype of an
363      * exception from the other, the subtype is included in the intersection.
364      */
intersectExceptions(Class<?>[] aExceptions, Class<?>[] bExceptions)365     private static Class<?>[] intersectExceptions(Class<?>[] aExceptions, Class<?>[] bExceptions) {
366         if (aExceptions.length == 0 || bExceptions.length == 0) {
367             return EmptyArray.CLASS;
368         }
369         if (Arrays.equals(aExceptions, bExceptions)) {
370             return aExceptions;
371         }
372         Set<Class<?>> intersection = new HashSet<Class<?>>();
373         for (Class<?> a : aExceptions) {
374             for (Class<?> b : bExceptions) {
375                 if (a.isAssignableFrom(b)) {
376                     intersection.add(b);
377                 } else if (b.isAssignableFrom(a)) {
378                     intersection.add(a);
379                 }
380             }
381         }
382         return intersection.toArray(new Class<?>[intersection.size()]);
383     }
384 
generateProxy(String name, Class<?>[] interfaces, ClassLoader loader, ArtMethod[] methods, Class<?>[][] exceptions)385     private static native Class<?> generateProxy(String name, Class<?>[] interfaces,
386                                                  ClassLoader loader, ArtMethod[] methods,
387                                                  Class<?>[][] exceptions);
388 
389     /*
390      * The VM clones this method's descriptor when generating a proxy class.
391      * There is no implementation.
392      */
constructorPrototype(InvocationHandler h)393     private static native void constructorPrototype(InvocationHandler h);
394 
invoke(Proxy proxy, ArtMethod method, Object[] args)395     static Object invoke(Proxy proxy, ArtMethod method, Object[] args) throws Throwable {
396         InvocationHandler h = proxy.h;
397         return h.invoke(proxy, new Method(method), args);
398     }
399 }
400