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