Home | History | Annotate | Download | only in stock
      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 
    137     private ProxyBuilder(Class<T> clazz) {
    138         baseClass = clazz;
    139     }
    140 
    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      */
    150     public ProxyBuilder<T> parentClassLoader(ClassLoader parent) {
    151         parentClassLoader = parent;
    152         return this;
    153     }
    154 
    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      */
    165     public ProxyBuilder<T> dexCache(File dexCache) {
    166         this.dexCache = dexCache;
    167         return this;
    168     }
    169 
    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 
    180     public ProxyBuilder<T> constructorArgValues(Object... constructorArgValues) {
    181         this.constructorArgValues = constructorArgValues;
    182         return this;
    183     }
    184 
    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      */
    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
    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")
    270     private Class<? extends T> loadClass(ClassLoader classLoader, String generatedName)
    271             throws ClassNotFoundException {
    272         return (Class<? extends T>) classLoader.loadClass(generatedName);
    273     }
    274 
    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 
    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 
    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      */
    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      */
    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 
    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"})
    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 
    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 
    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      */
    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 
    541     private static void check(boolean condition, String message) {
    542         if (!condition) {
    543             throw new IllegalArgumentException(message);
    544         }
    545     }
    546 
    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")
    579     private static <T> Constructor<T>[] getConstructorsToOverwrite(Class<T> clazz) {
    580         return (Constructor<T>[]) clazz.getDeclaredConstructors();
    581     }
    582 
    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      */
    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 
    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 
    635     private static <T> String getMethodNameForProxyOf(Class<T> clazz) {
    636         return clazz.getSimpleName() + "_Proxy";
    637     }
    638 
    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" })
    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 
    670     private static <T> Set<T> asSet(T... array) {
    671         return new CopyOnWriteArraySet<T>(Arrays.asList(array));
    672     }
    673 
    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<?>>();
    681         PRIMITIVE_TO_BOXED.put(boolean.class, Boolean.class);
    682         PRIMITIVE_TO_BOXED.put(int.class, Integer.class);
    683         PRIMITIVE_TO_BOXED.put(byte.class, Byte.class);
    684         PRIMITIVE_TO_BOXED.put(long.class, Long.class);
    685         PRIMITIVE_TO_BOXED.put(short.class, Short.class);
    686         PRIMITIVE_TO_BOXED.put(float.class, Float.class);
    687         PRIMITIVE_TO_BOXED.put(double.class, Double.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);
    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<?, ?>>();
    712         map.put(boolean.class, TypeId.get(Boolean.class).getMethod(TypeId.BOOLEAN, "booleanValue"));
    713         map.put(int.class, TypeId.get(Integer.class).getMethod(TypeId.INT, "intValue"));
    714         map.put(byte.class, TypeId.get(Byte.class).getMethod(TypeId.BYTE, "byteValue"));
    715         map.put(long.class, TypeId.get(Long.class).getMethod(TypeId.LONG, "longValue"));
    716         map.put(short.class, TypeId.get(Short.class).getMethod(TypeId.SHORT, "shortValue"));
    717         map.put(float.class, TypeId.get(Float.class).getMethod(TypeId.FLOAT, "floatValue"));
    718         map.put(double.class, TypeId.get(Double.class).getMethod(TypeId.DOUBLE, "doubleValue"));
    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 
    737         public MethodSetEntry(Method method) {
    738             originalMethod = method;
    739             name = method.getName();
    740             paramTypes = method.getParameterTypes();
    741             returnType = method.getReturnType();
    742         }
    743 
    744         @Override
    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
    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