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
17package com.google.dexmaker.stock;
18
19import com.google.dexmaker.Code;
20import com.google.dexmaker.Comparison;
21import com.google.dexmaker.DexMaker;
22import com.google.dexmaker.FieldId;
23import com.google.dexmaker.Label;
24import com.google.dexmaker.Local;
25import com.google.dexmaker.MethodId;
26import com.google.dexmaker.TypeId;
27import java.io.File;
28import java.io.IOException;
29import java.lang.reflect.Constructor;
30import java.lang.reflect.Field;
31import java.lang.reflect.InvocationHandler;
32import java.lang.reflect.InvocationTargetException;
33import java.lang.reflect.Method;
34import java.lang.reflect.Modifier;
35import static java.lang.reflect.Modifier.PRIVATE;
36import static java.lang.reflect.Modifier.PUBLIC;
37import static java.lang.reflect.Modifier.STATIC;
38import java.lang.reflect.UndeclaredThrowableException;
39import java.util.Arrays;
40import java.util.Collections;
41import java.util.HashMap;
42import java.util.HashSet;
43import java.util.Map;
44import java.util.Set;
45import 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 */
117public 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        Set<MethodSetEntry> seenFinalMethods = new HashSet<MethodSetEntry>();
599        for (Class<?> c = baseClass; c != null; c = c.getSuperclass()) {
600            getMethodsToProxy(methodsToProxy, seenFinalMethods, c);
601        }
602        for (Class<?> c : interfaces) {
603            getMethodsToProxy(methodsToProxy, seenFinalMethods, c);
604        }
605
606        Method[] results = new Method[methodsToProxy.size()];
607        int i = 0;
608        for (MethodSetEntry entry : methodsToProxy) {
609            results[i++] = entry.originalMethod;
610        }
611        return results;
612    }
613
614    private void getMethodsToProxy(Set<MethodSetEntry> sink, Set<MethodSetEntry> seenFinalMethods,
615            Class<?> c) {
616        for (Method method : c.getDeclaredMethods()) {
617            if ((method.getModifiers() & Modifier.FINAL) != 0) {
618                // Skip final methods, we can't override them. We
619                // also need to remember them, in case the same
620                // method exists in a parent class.
621                seenFinalMethods.add(new MethodSetEntry(method));
622                continue;
623            }
624            if ((method.getModifiers() & STATIC) != 0) {
625                // Skip static methods, overriding them has no effect.
626                continue;
627            }
628            if (method.getName().equals("finalize") && method.getParameterTypes().length == 0) {
629                // Skip finalize method, it's likely important that it execute as normal.
630                continue;
631            }
632            MethodSetEntry entry = new MethodSetEntry(method);
633            if (seenFinalMethods.contains(entry)) {
634                // This method is final in a child class.
635                // We can't override it.
636                continue;
637            }
638            sink.add(entry);
639        }
640
641        for (Class<?> i : c.getInterfaces()) {
642            getMethodsToProxy(sink, seenFinalMethods, i);
643        }
644    }
645
646    private static <T> String getMethodNameForProxyOf(Class<T> clazz) {
647        return clazz.getSimpleName() + "_Proxy";
648    }
649
650    private static TypeId<?>[] classArrayToTypeArray(Class<?>[] input) {
651        TypeId<?>[] result = new TypeId[input.length];
652        for (int i = 0; i < input.length; ++i) {
653            result[i] = TypeId.get(input[i]);
654        }
655        return result;
656    }
657
658    /**
659     * Calculates the correct return statement code for a method.
660     * <p>
661     * A void method will not return anything.  A method that returns a primitive will need to
662     * unbox the boxed result.  Otherwise we will cast the result.
663     */
664    // This one is tricky to fix, I gave up.
665    @SuppressWarnings({ "rawtypes", "unchecked" })
666    private static void generateCodeForReturnStatement(Code code, Class methodReturnType,
667            Local localForResultOfInvoke, Local localOfMethodReturnType, Local aBoxedResult) {
668        if (PRIMITIVE_TO_UNBOX_METHOD.containsKey(methodReturnType)) {
669            code.cast(aBoxedResult, localForResultOfInvoke);
670            MethodId unboxingMethodFor = getUnboxMethodForPrimitive(methodReturnType);
671            code.invokeVirtual(unboxingMethodFor, localOfMethodReturnType, aBoxedResult);
672            code.returnValue(localOfMethodReturnType);
673        } else if (void.class.equals(methodReturnType)) {
674            code.returnVoid();
675        } else {
676            code.cast(localOfMethodReturnType, localForResultOfInvoke);
677            code.returnValue(localOfMethodReturnType);
678        }
679    }
680
681    private static <T> Set<T> asSet(T... array) {
682        return new CopyOnWriteArraySet<T>(Arrays.asList(array));
683    }
684
685    private static MethodId<?, ?> getUnboxMethodForPrimitive(Class<?> methodReturnType) {
686        return PRIMITIVE_TO_UNBOX_METHOD.get(methodReturnType);
687    }
688
689    private static final Map<Class<?>, Class<?>> PRIMITIVE_TO_BOXED;
690    static {
691        PRIMITIVE_TO_BOXED = new HashMap<Class<?>, Class<?>>();
692        PRIMITIVE_TO_BOXED.put(boolean.class, Boolean.class);
693        PRIMITIVE_TO_BOXED.put(int.class, Integer.class);
694        PRIMITIVE_TO_BOXED.put(byte.class, Byte.class);
695        PRIMITIVE_TO_BOXED.put(long.class, Long.class);
696        PRIMITIVE_TO_BOXED.put(short.class, Short.class);
697        PRIMITIVE_TO_BOXED.put(float.class, Float.class);
698        PRIMITIVE_TO_BOXED.put(double.class, Double.class);
699        PRIMITIVE_TO_BOXED.put(char.class, Character.class);
700    }
701
702    private static final Map<TypeId<?>, MethodId<?, ?>> PRIMITIVE_TYPE_TO_UNBOX_METHOD;
703    static {
704        PRIMITIVE_TYPE_TO_UNBOX_METHOD = new HashMap<TypeId<?>, MethodId<?, ?>>();
705        for (Map.Entry<Class<?>, Class<?>> entry : PRIMITIVE_TO_BOXED.entrySet()) {
706            TypeId<?> primitiveType = TypeId.get(entry.getKey());
707            TypeId<?> boxedType = TypeId.get(entry.getValue());
708            MethodId<?, ?> valueOfMethod = boxedType.getMethod(boxedType, "valueOf", primitiveType);
709            PRIMITIVE_TYPE_TO_UNBOX_METHOD.put(primitiveType, valueOfMethod);
710        }
711    }
712
713    /**
714     * Map from primitive type to method used to unbox a boxed version of the primitive.
715     * <p>
716     * This is required for methods whose return type is primitive, since the
717     * {@link InvocationHandler} will return us a boxed result, and we'll need to convert it back to
718     * primitive value.
719     */
720    private static final Map<Class<?>, MethodId<?, ?>> PRIMITIVE_TO_UNBOX_METHOD;
721    static {
722        Map<Class<?>, MethodId<?, ?>> map = new HashMap<Class<?>, MethodId<?, ?>>();
723        map.put(boolean.class, TypeId.get(Boolean.class).getMethod(TypeId.BOOLEAN, "booleanValue"));
724        map.put(int.class, TypeId.get(Integer.class).getMethod(TypeId.INT, "intValue"));
725        map.put(byte.class, TypeId.get(Byte.class).getMethod(TypeId.BYTE, "byteValue"));
726        map.put(long.class, TypeId.get(Long.class).getMethod(TypeId.LONG, "longValue"));
727        map.put(short.class, TypeId.get(Short.class).getMethod(TypeId.SHORT, "shortValue"));
728        map.put(float.class, TypeId.get(Float.class).getMethod(TypeId.FLOAT, "floatValue"));
729        map.put(double.class, TypeId.get(Double.class).getMethod(TypeId.DOUBLE, "doubleValue"));
730        map.put(char.class, TypeId.get(Character.class).getMethod(TypeId.CHAR, "charValue"));
731        PRIMITIVE_TO_UNBOX_METHOD = map;
732    }
733
734    /**
735     * Wrapper class to let us disambiguate {@link Method} objects.
736     * <p>
737     * The purpose of this class is to override the {@link #equals(Object)} and {@link #hashCode()}
738     * methods so we can use a {@link Set} to remove duplicate methods that are overrides of one
739     * another. For these purposes, we consider two methods to be equal if they have the same
740     * name, return type, and parameter types.
741     */
742    private static class MethodSetEntry {
743        private final String name;
744        private final Class<?>[] paramTypes;
745        private final Class<?> returnType;
746        private final Method originalMethod;
747
748        public MethodSetEntry(Method method) {
749            originalMethod = method;
750            name = method.getName();
751            paramTypes = method.getParameterTypes();
752            returnType = method.getReturnType();
753        }
754
755        @Override
756        public boolean equals(Object o) {
757            if (o instanceof MethodSetEntry) {
758                MethodSetEntry other = (MethodSetEntry) o;
759                return name.equals(other.name)
760                        && returnType.equals(other.returnType)
761                        && Arrays.equals(paramTypes, other.paramTypes);
762            }
763            return false;
764        }
765
766        @Override
767        public int hashCode() {
768            int result = 17;
769            result += 31 * result + name.hashCode();
770            result += 31 * result + returnType.hashCode();
771            result += 31 * result + Arrays.hashCode(paramTypes);
772            return result;
773        }
774    }
775}
776