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