• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.google.dexmaker.stock;
18 
19 import com.google.dexmaker.Code;
20 import com.google.dexmaker.Comparison;
21 import com.google.dexmaker.DexMaker;
22 import com.google.dexmaker.FieldId;
23 import com.google.dexmaker.Label;
24 import com.google.dexmaker.Local;
25 import com.google.dexmaker.MethodId;
26 import com.google.dexmaker.TypeId;
27 import java.io.File;
28 import java.io.IOException;
29 import java.lang.reflect.Constructor;
30 import java.lang.reflect.Field;
31 import java.lang.reflect.InvocationHandler;
32 import java.lang.reflect.InvocationTargetException;
33 import java.lang.reflect.Method;
34 import java.lang.reflect.Modifier;
35 import static java.lang.reflect.Modifier.PRIVATE;
36 import static java.lang.reflect.Modifier.PUBLIC;
37 import static java.lang.reflect.Modifier.STATIC;
38 import java.lang.reflect.UndeclaredThrowableException;
39 import java.util.Arrays;
40 import java.util.Collections;
41 import java.util.Comparator;
42 import java.util.HashMap;
43 import java.util.HashSet;
44 import java.util.Map;
45 import java.util.Set;
46 import java.util.concurrent.CopyOnWriteArraySet;
47 
48 /**
49  * Creates dynamic proxies of concrete classes.
50  * <p>
51  * This is similar to the {@code java.lang.reflect.Proxy} class, but works for classes instead of
52  * interfaces.
53  * <h3>Example</h3>
54  * The following example demonstrates the creation of a dynamic proxy for {@code java.util.Random}
55  * which will always return 4 when asked for integers, and which logs method calls to every method.
56  * <pre>
57  * InvocationHandler handler = new InvocationHandler() {
58  *     &#64;Override
59  *     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
60  *         if (method.getName().equals("nextInt")) {
61  *             // Chosen by fair dice roll, guaranteed to be random.
62  *             return 4;
63  *         }
64  *         Object result = ProxyBuilder.callSuper(proxy, method, args);
65  *         System.out.println("Method: " + method.getName() + " args: "
66  *                 + Arrays.toString(args) + " result: " + result);
67  *         return result;
68  *     }
69  * };
70  * Random debugRandom = ProxyBuilder.forClass(Random.class)
71  *         .dexCache(getInstrumentation().getTargetContext().getDir("dx", Context.MODE_PRIVATE))
72  *         .handler(handler)
73  *         .build();
74  * assertEquals(4, debugRandom.nextInt());
75  * debugRandom.setSeed(0);
76  * assertTrue(debugRandom.nextBoolean());
77  * </pre>
78  * <h3>Usage</h3>
79  * Call {@link #forClass(Class)} for the Class you wish to proxy. Call
80  * {@link #handler(InvocationHandler)} passing in an {@link InvocationHandler}, and then call
81  * {@link #build()}. The returned instance will be a dynamically generated subclass where all method
82  * calls will be delegated to the invocation handler, except as noted below.
83  * <p>
84  * The static method {@link #callSuper(Object, Method, Object...)} allows you to access the original
85  * super method for a given proxy. This allows the invocation handler to selectively override some
86  * methods but not others.
87  * <p>
88  * By default, the {@link #build()} method will call the no-arg constructor belonging to the class
89  * being proxied. If you wish to call a different constructor, you must provide arguments for both
90  * {@link #constructorArgTypes(Class[])} and {@link #constructorArgValues(Object[])}.
91  * <p>
92  * This process works only for classes with public and protected level of visibility.
93  * <p>
94  * You may proxy abstract classes.  You may not proxy final classes.
95  * <p>
96  * Only non-private, non-final, non-static methods will be dispatched to the invocation handler.
97  * Private, static or final methods will always call through to the superclass as normal.
98  * <p>
99  * The {@link #finalize()} method on {@code Object} will not be proxied.
100  * <p>
101  * You must provide a dex cache directory via the {@link #dexCache(File)} method. You should take
102  * care not to make this a world-writable directory, so that third parties cannot inject code into
103  * your application.  A suitable parameter for these output directories would be something like
104  * this:
105  * <pre>{@code
106  *     getApplicationContext().getDir("dx", Context.MODE_PRIVATE);
107  * }</pre>
108  * <p>
109  * If the base class to be proxied leaks the {@code this} pointer in the constructor (bad practice),
110  * that is to say calls a non-private non-final method from the constructor, the invocation handler
111  * will not be invoked.  As a simple concrete example, when proxying Random we discover that it
112  * inernally calls setSeed during the constructor.  The proxy will not intercept this call during
113  * proxy construction, but will intercept as normal afterwards.  This behaviour may be subject to
114  * change in future releases.
115  * <p>
116  * This class is <b>not thread safe</b>.
117  */
118 public final class ProxyBuilder<T> {
119     // Version of ProxyBuilder. It should be updated if the implementation
120     // of the generated proxy class changes.
121     public static final int VERSION = 1;
122 
123     private static final String FIELD_NAME_HANDLER = "$__handler";
124     private static final String FIELD_NAME_METHODS = "$__methodArray";
125 
126     /**
127      * A cache of all proxy classes ever generated. At the time of writing,
128      * Android's runtime doesn't support class unloading so there's little
129      * value in using weak references.
130      */
131     private static final Map<Class<?>, Class<?>> generatedProxyClasses
132             = Collections.synchronizedMap(new HashMap<Class<?>, Class<?>>());
133 
134     private final Class<T> baseClass;
135     private ClassLoader parentClassLoader = ProxyBuilder.class.getClassLoader();
136     private InvocationHandler handler;
137     private File dexCache;
138     private Class<?>[] constructorArgTypes = new Class[0];
139     private Object[] constructorArgValues = new Object[0];
140     private Set<Class<?>> interfaces = new HashSet<Class<?>>();
141 
ProxyBuilder(Class<T> clazz)142     private ProxyBuilder(Class<T> clazz) {
143         baseClass = clazz;
144     }
145 
forClass(Class<T> clazz)146     public static <T> ProxyBuilder<T> forClass(Class<T> clazz) {
147         return new ProxyBuilder<T>(clazz);
148     }
149 
150     /**
151      * Specifies the parent ClassLoader to use when creating the proxy.
152      *
153      * <p>If null, {@code ProxyBuilder.class.getClassLoader()} will be used.
154      */
parentClassLoader(ClassLoader parent)155     public ProxyBuilder<T> parentClassLoader(ClassLoader parent) {
156         parentClassLoader = parent;
157         return this;
158     }
159 
handler(InvocationHandler handler)160     public ProxyBuilder<T> handler(InvocationHandler handler) {
161         this.handler = handler;
162         return this;
163     }
164 
165     /**
166      * Sets the directory where executable code is stored. See {@link
167      * DexMaker#generateAndLoad DexMaker.generateAndLoad()} for guidance on
168      * choosing a secure location for the dex cache.
169      */
dexCache(File dexCacheParent)170     public ProxyBuilder<T> dexCache(File dexCacheParent) {
171         dexCache = new File(dexCacheParent, "v" + Integer.toString(VERSION));
172         dexCache.mkdir();
173         return this;
174     }
175 
implementing(Class<?>.... interfaces)176     public ProxyBuilder<T> implementing(Class<?>... interfaces) {
177         for (Class<?> i : interfaces) {
178             if (!i.isInterface()) {
179                 throw new IllegalArgumentException("Not an interface: " + i.getName());
180             }
181             this.interfaces.add(i);
182         }
183         return this;
184     }
185 
constructorArgValues(Object... constructorArgValues)186     public ProxyBuilder<T> constructorArgValues(Object... constructorArgValues) {
187         this.constructorArgValues = constructorArgValues;
188         return this;
189     }
190 
constructorArgTypes(Class<?>.... constructorArgTypes)191     public ProxyBuilder<T> constructorArgTypes(Class<?>... constructorArgTypes) {
192         this.constructorArgTypes = constructorArgTypes;
193         return this;
194     }
195 
196     /**
197      * Create a new instance of the class to proxy.
198      *
199      * @throws UnsupportedOperationException if the class we are trying to create a proxy for is
200      *     not accessible.
201      * @throws IOException if an exception occurred writing to the {@code dexCache} directory.
202      * @throws UndeclaredThrowableException if the constructor for the base class to proxy throws
203      *     a declared exception during construction.
204      * @throws IllegalArgumentException if the handler is null, if the constructor argument types
205      *     do not match the constructor argument values, or if no such constructor exists.
206      */
build()207     public T build() throws IOException {
208         check(handler != null, "handler == null");
209         check(constructorArgTypes.length == constructorArgValues.length,
210                 "constructorArgValues.length != constructorArgTypes.length");
211         Class<? extends T> proxyClass = buildProxyClass();
212         Constructor<? extends T> constructor;
213         try {
214             constructor = proxyClass.getConstructor(constructorArgTypes);
215         } catch (NoSuchMethodException e) {
216             throw new IllegalArgumentException("No constructor for " + baseClass.getName()
217                     + " with parameter types " + Arrays.toString(constructorArgTypes));
218         }
219         T result;
220         try {
221             result = constructor.newInstance(constructorArgValues);
222         } catch (InstantiationException e) {
223             // Should not be thrown, generated class is not abstract.
224             throw new AssertionError(e);
225         } catch (IllegalAccessException e) {
226             // Should not be thrown, the generated constructor is accessible.
227             throw new AssertionError(e);
228         } catch (InvocationTargetException e) {
229             // Thrown when the base class constructor throws an exception.
230             throw launderCause(e);
231         }
232         setHandlerInstanceField(result, handler);
233         return result;
234     }
235 
236     // TODO: test coverage for this
237     // TODO: documentation for this
buildProxyClass()238     public Class<? extends T> buildProxyClass() throws IOException {
239         // try the cache to see if we've generated this one before
240         @SuppressWarnings("unchecked") // we only populate the map with matching types
241         Class<? extends T> proxyClass = (Class) generatedProxyClasses.get(baseClass);
242         if (proxyClass != null
243                 && proxyClass.getClassLoader().getParent() == parentClassLoader
244                 && interfaces.equals(asSet(proxyClass.getInterfaces()))) {
245             return proxyClass; // cache hit!
246         }
247 
248         // the cache missed; generate the class
249         DexMaker dexMaker = new DexMaker();
250         String generatedName = getMethodNameForProxyOf(baseClass);
251         TypeId<? extends T> generatedType = TypeId.get("L" + generatedName + ";");
252         TypeId<T> superType = TypeId.get(baseClass);
253         generateConstructorsAndFields(dexMaker, generatedType, superType, baseClass);
254         Method[] methodsToProxy = getMethodsToProxyRecursive();
255         generateCodeForAllMethods(dexMaker, generatedType, methodsToProxy, superType);
256         dexMaker.declare(generatedType, generatedName + ".generated", PUBLIC, superType,
257                 getInterfacesAsTypeIds());
258         ClassLoader classLoader = dexMaker.generateAndLoad(parentClassLoader, dexCache);
259         try {
260             proxyClass = loadClass(classLoader, generatedName);
261         } catch (IllegalAccessError e) {
262             // Thrown when the base class is not accessible.
263             throw new UnsupportedOperationException(
264                     "cannot proxy inaccessible class " + baseClass, e);
265         } catch (ClassNotFoundException e) {
266             // Should not be thrown, we're sure to have generated this class.
267             throw new AssertionError(e);
268         }
269         setMethodsStaticField(proxyClass, methodsToProxy);
270         generatedProxyClasses.put(baseClass, proxyClass);
271         return proxyClass;
272     }
273 
274     // The type cast is safe: the generated type will extend the base class type.
275     @SuppressWarnings("unchecked")
loadClass(ClassLoader classLoader, String generatedName)276     private Class<? extends T> loadClass(ClassLoader classLoader, String generatedName)
277             throws ClassNotFoundException {
278         return (Class<? extends T>) classLoader.loadClass(generatedName);
279     }
280 
launderCause(InvocationTargetException e)281     private static RuntimeException launderCause(InvocationTargetException e) {
282         Throwable cause = e.getCause();
283         // Errors should be thrown as they are.
284         if (cause instanceof Error) {
285             throw (Error) cause;
286         }
287         // RuntimeException can be thrown as-is.
288         if (cause instanceof RuntimeException) {
289             throw (RuntimeException) cause;
290         }
291         // Declared exceptions will have to be wrapped.
292         throw new UndeclaredThrowableException(cause);
293     }
294 
setHandlerInstanceField(Object instance, InvocationHandler handler)295     private static void setHandlerInstanceField(Object instance, InvocationHandler handler) {
296         try {
297             Field handlerField = instance.getClass().getDeclaredField(FIELD_NAME_HANDLER);
298             handlerField.setAccessible(true);
299             handlerField.set(instance, handler);
300         } catch (NoSuchFieldException e) {
301             // Should not be thrown, generated proxy class has been generated with this field.
302             throw new AssertionError(e);
303         } catch (IllegalAccessException e) {
304             // Should not be thrown, we just set the field to accessible.
305             throw new AssertionError(e);
306         }
307     }
308 
setMethodsStaticField(Class<?> proxyClass, Method[] methodsToProxy)309     private static void setMethodsStaticField(Class<?> proxyClass, Method[] methodsToProxy) {
310         try {
311             Field methodArrayField = proxyClass.getDeclaredField(FIELD_NAME_METHODS);
312             methodArrayField.setAccessible(true);
313             methodArrayField.set(null, methodsToProxy);
314         } catch (NoSuchFieldException e) {
315             // Should not be thrown, generated proxy class has been generated with this field.
316             throw new AssertionError(e);
317         } catch (IllegalAccessException e) {
318             // Should not be thrown, we just set the field to accessible.
319             throw new AssertionError(e);
320         }
321     }
322 
323     /**
324      * Returns the proxy's {@link InvocationHandler}.
325      *
326      * @throws IllegalArgumentException if the object supplied is not a proxy created by this class.
327      */
getInvocationHandler(Object instance)328     public static InvocationHandler getInvocationHandler(Object instance) {
329         try {
330             Field field = instance.getClass().getDeclaredField(FIELD_NAME_HANDLER);
331             field.setAccessible(true);
332             return (InvocationHandler) field.get(instance);
333         } catch (NoSuchFieldException e) {
334             throw new IllegalArgumentException("Not a valid proxy instance", e);
335         } catch (IllegalAccessException e) {
336             // Should not be thrown, we just set the field to accessible.
337             throw new AssertionError(e);
338         }
339     }
340 
341     // TODO: test coverage for isProxyClass
342 
343     /**
344      * Returns true if {@code c} is a proxy class created by this builder.
345      */
isProxyClass(Class<?> c)346     public static boolean isProxyClass(Class<?> c) {
347         // TODO: use a marker interface instead?
348         try {
349             c.getDeclaredField(FIELD_NAME_HANDLER);
350             return true;
351         } catch (NoSuchFieldException e) {
352             return false;
353         }
354     }
355 
generateCodeForAllMethods(DexMaker dexMaker, TypeId<G> generatedType, Method[] methodsToProxy, TypeId<T> superclassType)356     private static <T, G extends T> void generateCodeForAllMethods(DexMaker dexMaker,
357             TypeId<G> generatedType, Method[] methodsToProxy, TypeId<T> superclassType) {
358         TypeId<InvocationHandler> handlerType = TypeId.get(InvocationHandler.class);
359         TypeId<Method[]> methodArrayType = TypeId.get(Method[].class);
360         FieldId<G, InvocationHandler> handlerField =
361                 generatedType.getField(handlerType, FIELD_NAME_HANDLER);
362         FieldId<G, Method[]> allMethods =
363                 generatedType.getField(methodArrayType, FIELD_NAME_METHODS);
364         TypeId<Method> methodType = TypeId.get(Method.class);
365         TypeId<Object[]> objectArrayType = TypeId.get(Object[].class);
366         MethodId<InvocationHandler, Object> methodInvoke = handlerType.getMethod(TypeId.OBJECT,
367                 "invoke", TypeId.OBJECT, methodType, objectArrayType);
368         for (int m = 0; m < methodsToProxy.length; ++m) {
369             /*
370              * If the 5th method on the superclass Example that can be overridden were to look like
371              * this:
372              *
373              *     public int doSomething(Bar param0, int param1) {
374              *         ...
375              *     }
376              *
377              * Then the following code will generate a method on the proxy that looks something
378              * like this:
379              *
380              *     public int doSomething(Bar param0, int param1) {
381              *         int methodIndex = 4;
382              *         Method[] allMethods = Example_Proxy.$__methodArray;
383              *         Method thisMethod = allMethods[methodIndex];
384              *         int argsLength = 2;
385              *         Object[] args = new Object[argsLength];
386              *         InvocationHandler localHandler = this.$__handler;
387              *         // for-loop begins
388              *         int p = 0;
389              *         Bar parameter0 = param0;
390              *         args[p] = parameter0;
391              *         p = 1;
392              *         int parameter1 = param1;
393              *         Integer boxed1 = Integer.valueOf(parameter1);
394              *         args[p] = boxed1;
395              *         // for-loop ends
396              *         Object result = localHandler.invoke(this, thisMethod, args);
397              *         Integer castResult = (Integer) result;
398              *         int unboxedResult = castResult.intValue();
399              *         return unboxedResult;
400              *     }
401              *
402              * Or, in more idiomatic Java:
403              *
404              *     public int doSomething(Bar param0, int param1) {
405              *         if ($__handler == null) {
406              *             return super.doSomething(param0, param1);
407              *         }
408              *         return __handler.invoke(this, __methodArray[4],
409              *                 new Object[] { param0, Integer.valueOf(param1) });
410              *     }
411              */
412             Method method = methodsToProxy[m];
413             String name = method.getName();
414             Class<?>[] argClasses = method.getParameterTypes();
415             TypeId<?>[] argTypes = new TypeId<?>[argClasses.length];
416             for (int i = 0; i < argTypes.length; ++i) {
417                 argTypes[i] = TypeId.get(argClasses[i]);
418             }
419             Class<?> returnType = method.getReturnType();
420             TypeId<?> resultType = TypeId.get(returnType);
421             MethodId<T, ?> superMethod = superclassType.getMethod(resultType, name, argTypes);
422             MethodId<?, ?> methodId = generatedType.getMethod(resultType, name, argTypes);
423             Code code = dexMaker.declare(methodId, PUBLIC);
424             Local<G> localThis = code.getThis(generatedType);
425             Local<InvocationHandler> localHandler = code.newLocal(handlerType);
426             Local<Object> invokeResult = code.newLocal(TypeId.OBJECT);
427             Local<Integer> intValue = code.newLocal(TypeId.INT);
428             Local<Object[]> args = code.newLocal(objectArrayType);
429             Local<Integer> argsLength = code.newLocal(TypeId.INT);
430             Local<Object> temp = code.newLocal(TypeId.OBJECT);
431             Local<?> resultHolder = code.newLocal(resultType);
432             Local<Method[]> methodArray = code.newLocal(methodArrayType);
433             Local<Method> thisMethod = code.newLocal(methodType);
434             Local<Integer> methodIndex = code.newLocal(TypeId.INT);
435             Class<?> aBoxedClass = PRIMITIVE_TO_BOXED.get(returnType);
436             Local<?> aBoxedResult = null;
437             if (aBoxedClass != null) {
438                 aBoxedResult = code.newLocal(TypeId.get(aBoxedClass));
439             }
440             Local<?>[] superArgs2 = new Local<?>[argClasses.length];
441             Local<?> superResult2 = code.newLocal(resultType);
442             Local<InvocationHandler> nullHandler = code.newLocal(handlerType);
443 
444             code.loadConstant(methodIndex, m);
445             code.sget(allMethods, methodArray);
446             code.aget(thisMethod, methodArray, methodIndex);
447             code.loadConstant(argsLength, argTypes.length);
448             code.newArray(args, argsLength);
449             code.iget(handlerField, localHandler, localThis);
450 
451             // if (proxy == null)
452             code.loadConstant(nullHandler, null);
453             Label handlerNullCase = new Label();
454             code.compare(Comparison.EQ, handlerNullCase, nullHandler, localHandler);
455 
456             // This code is what we execute when we have a valid proxy: delegate to invocation
457             // handler.
458             for (int p = 0; p < argTypes.length; ++p) {
459                 code.loadConstant(intValue, p);
460                 Local<?> parameter = code.getParameter(p, argTypes[p]);
461                 Local<?> unboxedIfNecessary = boxIfRequired(code, parameter, temp);
462                 code.aput(args, intValue, unboxedIfNecessary);
463             }
464             code.invokeInterface(methodInvoke, invokeResult, localHandler,
465                     localThis, thisMethod, args);
466             generateCodeForReturnStatement(code, returnType, invokeResult, resultHolder,
467                     aBoxedResult);
468 
469             // This code is executed if proxy is null: call the original super method.
470             // This is required to handle the case of construction of an object which leaks the
471             // "this" pointer.
472             code.mark(handlerNullCase);
473             for (int i = 0; i < superArgs2.length; ++i) {
474                 superArgs2[i] = code.getParameter(i, argTypes[i]);
475             }
476             if (void.class.equals(returnType)) {
477                 code.invokeSuper(superMethod, null, localThis, superArgs2);
478                 code.returnVoid();
479             } else {
480                 invokeSuper(superMethod, code, localThis, superArgs2, superResult2);
481                 code.returnValue(superResult2);
482             }
483 
484             /*
485              * And to allow calling the original super method, the following is also generated:
486              *
487              *     public String super$doSomething$java_lang_String(Bar param0, int param1) {
488              *          int result = super.doSomething(param0, param1);
489              *          return result;
490              *     }
491              */
492             // TODO: don't include a super_ method if the target is abstract!
493             MethodId<G, ?> callsSuperMethod = generatedType.getMethod(
494                     resultType, superMethodName(method), argTypes);
495             Code superCode = dexMaker.declare(callsSuperMethod, PUBLIC);
496             Local<G> superThis = superCode.getThis(generatedType);
497             Local<?>[] superArgs = new Local<?>[argClasses.length];
498             for (int i = 0; i < superArgs.length; ++i) {
499                 superArgs[i] = superCode.getParameter(i, argTypes[i]);
500             }
501             if (void.class.equals(returnType)) {
502                 superCode.invokeSuper(superMethod, null, superThis, superArgs);
503                 superCode.returnVoid();
504             } else {
505                 Local<?> superResult = superCode.newLocal(resultType);
506                 invokeSuper(superMethod, superCode, superThis, superArgs, superResult);
507                 superCode.returnValue(superResult);
508             }
509         }
510     }
511 
512     @SuppressWarnings({"unchecked", "rawtypes"})
invokeSuper(MethodId superMethod, Code superCode, Local superThis, Local[] superArgs, Local superResult)513     private static void invokeSuper(MethodId superMethod, Code superCode,
514             Local superThis, Local[] superArgs, Local superResult) {
515         superCode.invokeSuper(superMethod, superResult, superThis, superArgs);
516     }
517 
boxIfRequired(Code code, Local<?> parameter, Local<Object> temp)518     private static Local<?> boxIfRequired(Code code, Local<?> parameter, Local<Object> temp) {
519         MethodId<?, ?> unboxMethod = PRIMITIVE_TYPE_TO_UNBOX_METHOD.get(parameter.getType());
520         if (unboxMethod == null) {
521             return parameter;
522         }
523         code.invokeStatic(unboxMethod, temp, parameter);
524         return temp;
525     }
526 
callSuper(Object proxy, Method method, Object... args)527     public static Object callSuper(Object proxy, Method method, Object... args) throws Throwable {
528         try {
529             return proxy.getClass()
530                     .getMethod(superMethodName(method), method.getParameterTypes())
531                     .invoke(proxy, args);
532         } catch (InvocationTargetException e) {
533             throw e.getCause();
534         }
535     }
536 
537     /**
538      * The super method must include the return type, otherwise its ambiguous
539      * for methods with covariant return types.
540      */
superMethodName(Method method)541     private static String superMethodName(Method method) {
542         String returnType = method.getReturnType().getName();
543         return "super$" + method.getName() + "$"
544                 + returnType.replace('.', '_').replace('[', '_').replace(';', '_');
545     }
546 
check(boolean condition, String message)547     private static void check(boolean condition, String message) {
548         if (!condition) {
549             throw new IllegalArgumentException(message);
550         }
551     }
552 
generateConstructorsAndFields(DexMaker dexMaker, TypeId<G> generatedType, TypeId<T> superType, Class<T> superClass)553     private static <T, G extends T> void generateConstructorsAndFields(DexMaker dexMaker,
554             TypeId<G> generatedType, TypeId<T> superType, Class<T> superClass) {
555         TypeId<InvocationHandler> handlerType = TypeId.get(InvocationHandler.class);
556         TypeId<Method[]> methodArrayType = TypeId.get(Method[].class);
557         FieldId<G, InvocationHandler> handlerField = generatedType.getField(
558                 handlerType, FIELD_NAME_HANDLER);
559         dexMaker.declare(handlerField, PRIVATE, null);
560         FieldId<G, Method[]> allMethods = generatedType.getField(
561                 methodArrayType, FIELD_NAME_METHODS);
562         dexMaker.declare(allMethods, PRIVATE | STATIC, null);
563         for (Constructor<T> constructor : getConstructorsToOverwrite(superClass)) {
564             if (constructor.getModifiers() == Modifier.FINAL) {
565                 continue;
566             }
567             TypeId<?>[] types = classArrayToTypeArray(constructor.getParameterTypes());
568             MethodId<?, ?> method = generatedType.getConstructor(types);
569             Code constructorCode = dexMaker.declare(method, PUBLIC);
570             Local<G> thisRef = constructorCode.getThis(generatedType);
571             Local<?>[] params = new Local[types.length];
572             for (int i = 0; i < params.length; ++i) {
573                 params[i] = constructorCode.getParameter(i, types[i]);
574             }
575             MethodId<T, ?> superConstructor = superType.getConstructor(types);
576             constructorCode.invokeDirect(superConstructor, null, thisRef, params);
577             constructorCode.returnVoid();
578         }
579     }
580 
581     // The type parameter on Constructor is the class in which the constructor is declared.
582     // The getDeclaredConstructors() method gets constructors declared only in the given class,
583     // hence this cast is safe.
584     @SuppressWarnings("unchecked")
getConstructorsToOverwrite(Class<T> clazz)585     private static <T> Constructor<T>[] getConstructorsToOverwrite(Class<T> clazz) {
586         return (Constructor<T>[]) clazz.getDeclaredConstructors();
587     }
588 
getInterfacesAsTypeIds()589     private TypeId<?>[] getInterfacesAsTypeIds() {
590         TypeId<?>[] result = new TypeId<?>[interfaces.size()];
591         int i = 0;
592         for (Class<?> implemented : interfaces) {
593             result[i++] = TypeId.get(implemented);
594         }
595         return result;
596     }
597 
598     /**
599      * Gets all {@link Method} objects we can proxy in the hierarchy of the
600      * supplied class.
601      */
getMethodsToProxyRecursive()602     private Method[] getMethodsToProxyRecursive() {
603         Set<MethodSetEntry> methodsToProxy = new HashSet<MethodSetEntry>();
604         Set<MethodSetEntry> seenFinalMethods = new HashSet<MethodSetEntry>();
605         for (Class<?> c = baseClass; c != null; c = c.getSuperclass()) {
606             getMethodsToProxy(methodsToProxy, seenFinalMethods, c);
607         }
608         for (Class<?> c : interfaces) {
609             getMethodsToProxy(methodsToProxy, seenFinalMethods, c);
610         }
611 
612         Method[] results = new Method[methodsToProxy.size()];
613         int i = 0;
614         for (MethodSetEntry entry : methodsToProxy) {
615             results[i++] = entry.originalMethod;
616         }
617 
618         // Sort the results array so that they are returned by this method
619         // in a deterministic fashion.
620         Arrays.sort(results, new Comparator<Method>() {
621             @Override
622             public int compare(Method method1, Method method2) {
623                 return method1.toString().compareTo(method2.toString());
624             }
625         });
626 
627         return results;
628     }
629 
getMethodsToProxy(Set<MethodSetEntry> sink, Set<MethodSetEntry> seenFinalMethods, Class<?> c)630     private void getMethodsToProxy(Set<MethodSetEntry> sink, Set<MethodSetEntry> seenFinalMethods,
631             Class<?> c) {
632         for (Method method : c.getDeclaredMethods()) {
633             if ((method.getModifiers() & Modifier.FINAL) != 0) {
634                 // Skip final methods, we can't override them. We
635                 // also need to remember them, in case the same
636                 // method exists in a parent class.
637                 seenFinalMethods.add(new MethodSetEntry(method));
638                 continue;
639             }
640             if ((method.getModifiers() & STATIC) != 0) {
641                 // Skip static methods, overriding them has no effect.
642                 continue;
643             }
644             if (method.getName().equals("finalize") && method.getParameterTypes().length == 0) {
645                 // Skip finalize method, it's likely important that it execute as normal.
646                 continue;
647             }
648             MethodSetEntry entry = new MethodSetEntry(method);
649             if (seenFinalMethods.contains(entry)) {
650                 // This method is final in a child class.
651                 // We can't override it.
652                 continue;
653             }
654             sink.add(entry);
655         }
656 
657         for (Class<?> i : c.getInterfaces()) {
658             getMethodsToProxy(sink, seenFinalMethods, i);
659         }
660     }
661 
getMethodNameForProxyOf(Class<T> clazz)662     private static <T> String getMethodNameForProxyOf(Class<T> clazz) {
663         return clazz.getSimpleName() + "_Proxy";
664     }
665 
classArrayToTypeArray(Class<?>[] input)666     private static TypeId<?>[] classArrayToTypeArray(Class<?>[] input) {
667         TypeId<?>[] result = new TypeId[input.length];
668         for (int i = 0; i < input.length; ++i) {
669             result[i] = TypeId.get(input[i]);
670         }
671         return result;
672     }
673 
674     /**
675      * Calculates the correct return statement code for a method.
676      * <p>
677      * A void method will not return anything.  A method that returns a primitive will need to
678      * unbox the boxed result.  Otherwise we will cast the result.
679      */
680     // This one is tricky to fix, I gave up.
681     @SuppressWarnings({ "rawtypes", "unchecked" })
generateCodeForReturnStatement(Code code, Class methodReturnType, Local localForResultOfInvoke, Local localOfMethodReturnType, Local aBoxedResult)682     private static void generateCodeForReturnStatement(Code code, Class methodReturnType,
683             Local localForResultOfInvoke, Local localOfMethodReturnType, Local aBoxedResult) {
684         if (PRIMITIVE_TO_UNBOX_METHOD.containsKey(methodReturnType)) {
685             code.cast(aBoxedResult, localForResultOfInvoke);
686             MethodId unboxingMethodFor = getUnboxMethodForPrimitive(methodReturnType);
687             code.invokeVirtual(unboxingMethodFor, localOfMethodReturnType, aBoxedResult);
688             code.returnValue(localOfMethodReturnType);
689         } else if (void.class.equals(methodReturnType)) {
690             code.returnVoid();
691         } else {
692             code.cast(localOfMethodReturnType, localForResultOfInvoke);
693             code.returnValue(localOfMethodReturnType);
694         }
695     }
696 
asSet(T... array)697     private static <T> Set<T> asSet(T... array) {
698         return new CopyOnWriteArraySet<T>(Arrays.asList(array));
699     }
700 
getUnboxMethodForPrimitive(Class<?> methodReturnType)701     private static MethodId<?, ?> getUnboxMethodForPrimitive(Class<?> methodReturnType) {
702         return PRIMITIVE_TO_UNBOX_METHOD.get(methodReturnType);
703     }
704 
705     private static final Map<Class<?>, Class<?>> PRIMITIVE_TO_BOXED;
706     static {
707         PRIMITIVE_TO_BOXED = new HashMap<Class<?>, Class<?>>();
PRIMITIVE_TO_BOXED.put(boolean.class, Boolean.class)708         PRIMITIVE_TO_BOXED.put(boolean.class, Boolean.class);
PRIMITIVE_TO_BOXED.put(int.class, Integer.class)709         PRIMITIVE_TO_BOXED.put(int.class, Integer.class);
PRIMITIVE_TO_BOXED.put(byte.class, Byte.class)710         PRIMITIVE_TO_BOXED.put(byte.class, Byte.class);
PRIMITIVE_TO_BOXED.put(long.class, Long.class)711         PRIMITIVE_TO_BOXED.put(long.class, Long.class);
PRIMITIVE_TO_BOXED.put(short.class, Short.class)712         PRIMITIVE_TO_BOXED.put(short.class, Short.class);
PRIMITIVE_TO_BOXED.put(float.class, Float.class)713         PRIMITIVE_TO_BOXED.put(float.class, Float.class);
PRIMITIVE_TO_BOXED.put(double.class, Double.class)714         PRIMITIVE_TO_BOXED.put(double.class, Double.class);
PRIMITIVE_TO_BOXED.put(char.class, Character.class)715         PRIMITIVE_TO_BOXED.put(char.class, Character.class);
716     }
717 
718     private static final Map<TypeId<?>, MethodId<?, ?>> PRIMITIVE_TYPE_TO_UNBOX_METHOD;
719     static {
720         PRIMITIVE_TYPE_TO_UNBOX_METHOD = new HashMap<TypeId<?>, MethodId<?, ?>>();
721         for (Map.Entry<Class<?>, Class<?>> entry : PRIMITIVE_TO_BOXED.entrySet()) {
722             TypeId<?> primitiveType = TypeId.get(entry.getKey());
723             TypeId<?> boxedType = TypeId.get(entry.getValue());
724             MethodId<?, ?> valueOfMethod = boxedType.getMethod(boxedType, "valueOf", primitiveType);
PRIMITIVE_TYPE_TO_UNBOX_METHOD.put(primitiveType, valueOfMethod)725             PRIMITIVE_TYPE_TO_UNBOX_METHOD.put(primitiveType, valueOfMethod);
726         }
727     }
728 
729     /**
730      * Map from primitive type to method used to unbox a boxed version of the primitive.
731      * <p>
732      * This is required for methods whose return type is primitive, since the
733      * {@link InvocationHandler} will return us a boxed result, and we'll need to convert it back to
734      * primitive value.
735      */
736     private static final Map<Class<?>, MethodId<?, ?>> PRIMITIVE_TO_UNBOX_METHOD;
737     static {
738         Map<Class<?>, MethodId<?, ?>> map = new HashMap<Class<?>, MethodId<?, ?>>();
map.put(boolean.class, TypeId.get(Boolean.class).getMethod(TypeId.BOOLEAN, "booleanValue"))739         map.put(boolean.class, TypeId.get(Boolean.class).getMethod(TypeId.BOOLEAN, "booleanValue"));
map.put(int.class, TypeId.get(Integer.class).getMethod(TypeId.INT, "intValue"))740         map.put(int.class, TypeId.get(Integer.class).getMethod(TypeId.INT, "intValue"));
map.put(byte.class, TypeId.get(Byte.class).getMethod(TypeId.BYTE, "byteValue"))741         map.put(byte.class, TypeId.get(Byte.class).getMethod(TypeId.BYTE, "byteValue"));
map.put(long.class, TypeId.get(Long.class).getMethod(TypeId.LONG, "longValue"))742         map.put(long.class, TypeId.get(Long.class).getMethod(TypeId.LONG, "longValue"));
map.put(short.class, TypeId.get(Short.class).getMethod(TypeId.SHORT, "shortValue"))743         map.put(short.class, TypeId.get(Short.class).getMethod(TypeId.SHORT, "shortValue"));
map.put(float.class, TypeId.get(Float.class).getMethod(TypeId.FLOAT, "floatValue"))744         map.put(float.class, TypeId.get(Float.class).getMethod(TypeId.FLOAT, "floatValue"));
map.put(double.class, TypeId.get(Double.class).getMethod(TypeId.DOUBLE, "doubleValue"))745         map.put(double.class, TypeId.get(Double.class).getMethod(TypeId.DOUBLE, "doubleValue"));
map.put(char.class, TypeId.get(Character.class).getMethod(TypeId.CHAR, "charValue"))746         map.put(char.class, TypeId.get(Character.class).getMethod(TypeId.CHAR, "charValue"));
747         PRIMITIVE_TO_UNBOX_METHOD = map;
748     }
749 
750     /**
751      * Wrapper class to let us disambiguate {@link Method} objects.
752      * <p>
753      * The purpose of this class is to override the {@link #equals(Object)} and {@link #hashCode()}
754      * methods so we can use a {@link Set} to remove duplicate methods that are overrides of one
755      * another. For these purposes, we consider two methods to be equal if they have the same
756      * name, return type, and parameter types.
757      */
758     private static class MethodSetEntry {
759         private final String name;
760         private final Class<?>[] paramTypes;
761         private final Class<?> returnType;
762         private final Method originalMethod;
763 
MethodSetEntry(Method method)764         public MethodSetEntry(Method method) {
765             originalMethod = method;
766             name = method.getName();
767             paramTypes = method.getParameterTypes();
768             returnType = method.getReturnType();
769         }
770 
771         @Override
equals(Object o)772         public boolean equals(Object o) {
773             if (o instanceof MethodSetEntry) {
774                 MethodSetEntry other = (MethodSetEntry) o;
775                 return name.equals(other.name)
776                         && returnType.equals(other.returnType)
777                         && Arrays.equals(paramTypes, other.paramTypes);
778             }
779             return false;
780         }
781 
782         @Override
hashCode()783         public int hashCode() {
784             int result = 17;
785             result += 31 * result + name.hashCode();
786             result += 31 * result + returnType.hashCode();
787             result += 31 * result + Arrays.hashCode(paramTypes);
788             return result;
789         }
790     }
791 }
792