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