1579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson/* 2579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * Copyright (C) 2011 The Android Open Source Project 3579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * 4579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * Licensed under the Apache License, Version 2.0 (the "License"); 5579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * you may not use this file except in compliance with the License. 6579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * You may obtain a copy of the License at 7579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * 8579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * http://www.apache.org/licenses/LICENSE-2.0 9579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * 10579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * Unless required by applicable law or agreed to in writing, software 11579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * distributed under the License is distributed on an "AS IS" BASIS, 12579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * See the License for the specific language governing permissions and 14579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * limitations under the License. 15579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson */ 16579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson 171977585657cb304a9e1ffa8a2320fa8053a7383cJesse Wilsonpackage com.google.dexmaker.stock; 18579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson 191977585657cb304a9e1ffa8a2320fa8053a7383cJesse Wilsonimport com.google.dexmaker.Code; 201977585657cb304a9e1ffa8a2320fa8053a7383cJesse Wilsonimport com.google.dexmaker.Comparison; 21ab220f004db90fa94ef9349ca1adde5f89012e8dJesse Wilsonimport com.google.dexmaker.DexMaker; 221977585657cb304a9e1ffa8a2320fa8053a7383cJesse Wilsonimport com.google.dexmaker.FieldId; 231977585657cb304a9e1ffa8a2320fa8053a7383cJesse Wilsonimport com.google.dexmaker.Label; 241977585657cb304a9e1ffa8a2320fa8053a7383cJesse Wilsonimport com.google.dexmaker.Local; 251977585657cb304a9e1ffa8a2320fa8053a7383cJesse Wilsonimport com.google.dexmaker.MethodId; 260e49fb9243b7463835ab80ef7cc62435f55846ceJesse Wilsonimport com.google.dexmaker.TypeId; 27579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilsonimport java.io.File; 28579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilsonimport java.io.IOException; 29579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilsonimport java.lang.reflect.Constructor; 30579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilsonimport java.lang.reflect.Field; 31579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilsonimport java.lang.reflect.InvocationHandler; 32579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilsonimport java.lang.reflect.InvocationTargetException; 33579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilsonimport java.lang.reflect.Method; 34579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilsonimport java.lang.reflect.Modifier; 3590699b97998f1582a921202fb909f17f9718d177Jesse Wilsonimport static java.lang.reflect.Modifier.PRIVATE; 3690699b97998f1582a921202fb909f17f9718d177Jesse Wilsonimport static java.lang.reflect.Modifier.PUBLIC; 3790699b97998f1582a921202fb909f17f9718d177Jesse Wilsonimport static java.lang.reflect.Modifier.STATIC; 38579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilsonimport java.lang.reflect.UndeclaredThrowableException; 39579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilsonimport java.util.Arrays; 402e28a229885e9ba7fec9ef42cbf30fdcf8a0c939Jesse Wilsonimport java.util.Collections; 41fec7276299b950a8831ead9a9756d27e0fc60560Andrew Yousefimport java.util.Comparator; 42579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilsonimport java.util.HashMap; 43579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilsonimport java.util.HashSet; 44579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilsonimport java.util.Map; 45579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilsonimport java.util.Set; 461af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilsonimport java.util.concurrent.CopyOnWriteArraySet; 47579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson 48579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson/** 49579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * Creates dynamic proxies of concrete classes. 50579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * <p> 51579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * This is similar to the {@code java.lang.reflect.Proxy} class, but works for classes instead of 52579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * interfaces. 53579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * <h3>Example</h3> 54579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * The following example demonstrates the creation of a dynamic proxy for {@code java.util.Random} 55579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * which will always return 4 when asked for integers, and which logs method calls to every method. 56579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * <pre> 57579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * InvocationHandler handler = new InvocationHandler() { 58579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * @Override 59579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 60579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * if (method.getName().equals("nextInt")) { 61579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * // Chosen by fair dice roll, guaranteed to be random. 62579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * return 4; 63579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * } 64579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * Object result = ProxyBuilder.callSuper(proxy, method, args); 65579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * System.out.println("Method: " + method.getName() + " args: " 66579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * + Arrays.toString(args) + " result: " + result); 67579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * return result; 68579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * } 69579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * }; 70579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * Random debugRandom = ProxyBuilder.forClass(Random.class) 71579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * .dexCache(getInstrumentation().getTargetContext().getDir("dx", Context.MODE_PRIVATE)) 72579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * .handler(handler) 73579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * .build(); 74579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * assertEquals(4, debugRandom.nextInt()); 75579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * debugRandom.setSeed(0); 76579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * assertTrue(debugRandom.nextBoolean()); 77579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * </pre> 78579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * <h3>Usage</h3> 79579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * Call {@link #forClass(Class)} for the Class you wish to proxy. Call 80579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * {@link #handler(InvocationHandler)} passing in an {@link InvocationHandler}, and then call 81579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * {@link #build()}. The returned instance will be a dynamically generated subclass where all method 82579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * calls will be delegated to the invocation handler, except as noted below. 83579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * <p> 84579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * The static method {@link #callSuper(Object, Method, Object...)} allows you to access the original 85579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * super method for a given proxy. This allows the invocation handler to selectively override some 86579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * methods but not others. 87579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * <p> 88579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * By default, the {@link #build()} method will call the no-arg constructor belonging to the class 89579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * being proxied. If you wish to call a different constructor, you must provide arguments for both 90579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * {@link #constructorArgTypes(Class[])} and {@link #constructorArgValues(Object[])}. 91579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * <p> 92579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * This process works only for classes with public and protected level of visibility. 93579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * <p> 94579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * You may proxy abstract classes. You may not proxy final classes. 95579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * <p> 96579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * Only non-private, non-final, non-static methods will be dispatched to the invocation handler. 97579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * Private, static or final methods will always call through to the superclass as normal. 98579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * <p> 99579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * The {@link #finalize()} method on {@code Object} will not be proxied. 100579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * <p> 101579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * You must provide a dex cache directory via the {@link #dexCache(File)} method. You should take 102579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * care not to make this a world-writable directory, so that third parties cannot inject code into 103579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * your application. A suitable parameter for these output directories would be something like 104579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * this: 105579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * <pre>{@code 106579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * getApplicationContext().getDir("dx", Context.MODE_PRIVATE); 107579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * }</pre> 108579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * <p> 109579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * If the base class to be proxied leaks the {@code this} pointer in the constructor (bad practice), 110579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * that is to say calls a non-private non-final method from the constructor, the invocation handler 111579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * will not be invoked. As a simple concrete example, when proxying Random we discover that it 112579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * inernally calls setSeed during the constructor. The proxy will not intercept this call during 113579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * proxy construction, but will intercept as normal afterwards. This behaviour may be subject to 114579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * change in future releases. 115579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * <p> 116579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * This class is <b>not thread safe</b>. 117579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson */ 118579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilsonpublic final class ProxyBuilder<T> { 119054604d4c95cb530bea955718d79dbe3dc0962cfAndrew Yousef // Version of ProxyBuilder. It should be updated if the implementation 120054604d4c95cb530bea955718d79dbe3dc0962cfAndrew Yousef // of the generated proxy class changes. 121054604d4c95cb530bea955718d79dbe3dc0962cfAndrew Yousef public static final int VERSION = 1; 122054604d4c95cb530bea955718d79dbe3dc0962cfAndrew Yousef 123579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson private static final String FIELD_NAME_HANDLER = "$__handler"; 124579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson private static final String FIELD_NAME_METHODS = "$__methodArray"; 125579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson 1262e28a229885e9ba7fec9ef42cbf30fdcf8a0c939Jesse Wilson /** 1272e28a229885e9ba7fec9ef42cbf30fdcf8a0c939Jesse Wilson * A cache of all proxy classes ever generated. At the time of writing, 1282e28a229885e9ba7fec9ef42cbf30fdcf8a0c939Jesse Wilson * Android's runtime doesn't support class unloading so there's little 1292e28a229885e9ba7fec9ef42cbf30fdcf8a0c939Jesse Wilson * value in using weak references. 1302e28a229885e9ba7fec9ef42cbf30fdcf8a0c939Jesse Wilson */ 1312e28a229885e9ba7fec9ef42cbf30fdcf8a0c939Jesse Wilson private static final Map<Class<?>, Class<?>> generatedProxyClasses 1322e28a229885e9ba7fec9ef42cbf30fdcf8a0c939Jesse Wilson = Collections.synchronizedMap(new HashMap<Class<?>, Class<?>>()); 1332e28a229885e9ba7fec9ef42cbf30fdcf8a0c939Jesse Wilson 134579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson private final Class<T> baseClass; 135579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson private ClassLoader parentClassLoader = ProxyBuilder.class.getClassLoader(); 136579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson private InvocationHandler handler; 137579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson private File dexCache; 138579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson private Class<?>[] constructorArgTypes = new Class[0]; 139579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson private Object[] constructorArgValues = new Object[0]; 1401af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson private Set<Class<?>> interfaces = new HashSet<Class<?>>(); 141579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson 142579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson private ProxyBuilder(Class<T> clazz) { 143579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson baseClass = clazz; 144579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } 145579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson 146579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson public static <T> ProxyBuilder<T> forClass(Class<T> clazz) { 147579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson return new ProxyBuilder<T>(clazz); 148579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } 149579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson 150579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson /** 151579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * Specifies the parent ClassLoader to use when creating the proxy. 152579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * 153579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * <p>If null, {@code ProxyBuilder.class.getClassLoader()} will be used. 154579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson */ 155579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson public ProxyBuilder<T> parentClassLoader(ClassLoader parent) { 156579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson parentClassLoader = parent; 157579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson return this; 158579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } 159579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson 160579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson public ProxyBuilder<T> handler(InvocationHandler handler) { 161579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson this.handler = handler; 162579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson return this; 163579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } 164579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson 16573cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson /** 16673cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson * Sets the directory where executable code is stored. See {@link 16773cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson * DexMaker#generateAndLoad DexMaker.generateAndLoad()} for guidance on 16873cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson * choosing a secure location for the dex cache. 16973cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson */ 170054604d4c95cb530bea955718d79dbe3dc0962cfAndrew Yousef public ProxyBuilder<T> dexCache(File dexCacheParent) { 171054604d4c95cb530bea955718d79dbe3dc0962cfAndrew Yousef dexCache = new File(dexCacheParent, "v" + Integer.toString(VERSION)); 172054604d4c95cb530bea955718d79dbe3dc0962cfAndrew Yousef dexCache.mkdir(); 173579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson return this; 174579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } 1751af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson 1761af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson public ProxyBuilder<T> implementing(Class<?>... interfaces) { 1771af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson for (Class<?> i : interfaces) { 1781af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson if (!i.isInterface()) { 1791af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson throw new IllegalArgumentException("Not an interface: " + i.getName()); 1801af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson } 1811af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson this.interfaces.add(i); 1821af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson } 1831af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson return this; 1841af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson } 185579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson 186579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson public ProxyBuilder<T> constructorArgValues(Object... constructorArgValues) { 187579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson this.constructorArgValues = constructorArgValues; 188579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson return this; 189579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } 190579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson 191579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson public ProxyBuilder<T> constructorArgTypes(Class<?>... constructorArgTypes) { 192579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson this.constructorArgTypes = constructorArgTypes; 193579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson return this; 194579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } 195579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson 196579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson /** 197579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * Create a new instance of the class to proxy. 198579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * 199579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * @throws UnsupportedOperationException if the class we are trying to create a proxy for is 200579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * not accessible. 2011977585657cb304a9e1ffa8a2320fa8053a7383cJesse Wilson * @throws IOException if an exception occurred writing to the {@code dexCache} directory. 202579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * @throws UndeclaredThrowableException if the constructor for the base class to proxy throws 203579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * a declared exception during construction. 204579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * @throws IllegalArgumentException if the handler is null, if the constructor argument types 205579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * do not match the constructor argument values, or if no such constructor exists. 206579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson */ 2071977585657cb304a9e1ffa8a2320fa8053a7383cJesse Wilson public T build() throws IOException { 208579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson check(handler != null, "handler == null"); 209579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson check(constructorArgTypes.length == constructorArgValues.length, 210579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson "constructorArgValues.length != constructorArgTypes.length"); 2111af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson Class<? extends T> proxyClass = buildProxyClass(); 212579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson Constructor<? extends T> constructor; 213579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson try { 214579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson constructor = proxyClass.getConstructor(constructorArgTypes); 215579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } catch (NoSuchMethodException e) { 2161af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson throw new IllegalArgumentException("No constructor for " + baseClass.getName() 217679fb66c12a24691a6d7720d79c64c28f5b0532bJesse Wilson + " with parameter types " + Arrays.toString(constructorArgTypes)); 218579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } 219579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson T result; 220579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson try { 221579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson result = constructor.newInstance(constructorArgValues); 222579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } catch (InstantiationException e) { 223579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson // Should not be thrown, generated class is not abstract. 224579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson throw new AssertionError(e); 225579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } catch (IllegalAccessException e) { 226579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson // Should not be thrown, the generated constructor is accessible. 227579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson throw new AssertionError(e); 228579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } catch (InvocationTargetException e) { 2292e28a229885e9ba7fec9ef42cbf30fdcf8a0c939Jesse Wilson // Thrown when the base class constructor throws an exception. 230579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson throw launderCause(e); 231579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } 232579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson setHandlerInstanceField(result, handler); 233579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson return result; 234579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } 235579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson 2361af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson // TODO: test coverage for this 2371af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson // TODO: documentation for this 2381af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson public Class<? extends T> buildProxyClass() throws IOException { 2392e28a229885e9ba7fec9ef42cbf30fdcf8a0c939Jesse Wilson // try the cache to see if we've generated this one before 2402e28a229885e9ba7fec9ef42cbf30fdcf8a0c939Jesse Wilson @SuppressWarnings("unchecked") // we only populate the map with matching types 2412e28a229885e9ba7fec9ef42cbf30fdcf8a0c939Jesse Wilson Class<? extends T> proxyClass = (Class) generatedProxyClasses.get(baseClass); 2421af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson if (proxyClass != null 2431af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson && proxyClass.getClassLoader().getParent() == parentClassLoader 2441af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson && interfaces.equals(asSet(proxyClass.getInterfaces()))) { 2452e28a229885e9ba7fec9ef42cbf30fdcf8a0c939Jesse Wilson return proxyClass; // cache hit! 2462e28a229885e9ba7fec9ef42cbf30fdcf8a0c939Jesse Wilson } 2472e28a229885e9ba7fec9ef42cbf30fdcf8a0c939Jesse Wilson 2482e28a229885e9ba7fec9ef42cbf30fdcf8a0c939Jesse Wilson // the cache missed; generate the class 2492e28a229885e9ba7fec9ef42cbf30fdcf8a0c939Jesse Wilson DexMaker dexMaker = new DexMaker(); 2502e28a229885e9ba7fec9ef42cbf30fdcf8a0c939Jesse Wilson String generatedName = getMethodNameForProxyOf(baseClass); 2512e28a229885e9ba7fec9ef42cbf30fdcf8a0c939Jesse Wilson TypeId<? extends T> generatedType = TypeId.get("L" + generatedName + ";"); 2522e28a229885e9ba7fec9ef42cbf30fdcf8a0c939Jesse Wilson TypeId<T> superType = TypeId.get(baseClass); 2532e28a229885e9ba7fec9ef42cbf30fdcf8a0c939Jesse Wilson generateConstructorsAndFields(dexMaker, generatedType, superType, baseClass); 2541af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson Method[] methodsToProxy = getMethodsToProxyRecursive(); 2552e28a229885e9ba7fec9ef42cbf30fdcf8a0c939Jesse Wilson generateCodeForAllMethods(dexMaker, generatedType, methodsToProxy, superType); 2561af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson dexMaker.declare(generatedType, generatedName + ".generated", PUBLIC, superType, 2571af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson getInterfacesAsTypeIds()); 2582e28a229885e9ba7fec9ef42cbf30fdcf8a0c939Jesse Wilson ClassLoader classLoader = dexMaker.generateAndLoad(parentClassLoader, dexCache); 2592e28a229885e9ba7fec9ef42cbf30fdcf8a0c939Jesse Wilson try { 2602e28a229885e9ba7fec9ef42cbf30fdcf8a0c939Jesse Wilson proxyClass = loadClass(classLoader, generatedName); 2612e28a229885e9ba7fec9ef42cbf30fdcf8a0c939Jesse Wilson } catch (IllegalAccessError e) { 2622e28a229885e9ba7fec9ef42cbf30fdcf8a0c939Jesse Wilson // Thrown when the base class is not accessible. 2631af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson throw new UnsupportedOperationException( 2641af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson "cannot proxy inaccessible class " + baseClass, e); 2652e28a229885e9ba7fec9ef42cbf30fdcf8a0c939Jesse Wilson } catch (ClassNotFoundException e) { 2662e28a229885e9ba7fec9ef42cbf30fdcf8a0c939Jesse Wilson // Should not be thrown, we're sure to have generated this class. 2672e28a229885e9ba7fec9ef42cbf30fdcf8a0c939Jesse Wilson throw new AssertionError(e); 2682e28a229885e9ba7fec9ef42cbf30fdcf8a0c939Jesse Wilson } 2692e28a229885e9ba7fec9ef42cbf30fdcf8a0c939Jesse Wilson setMethodsStaticField(proxyClass, methodsToProxy); 2702e28a229885e9ba7fec9ef42cbf30fdcf8a0c939Jesse Wilson generatedProxyClasses.put(baseClass, proxyClass); 2712e28a229885e9ba7fec9ef42cbf30fdcf8a0c939Jesse Wilson return proxyClass; 2722e28a229885e9ba7fec9ef42cbf30fdcf8a0c939Jesse Wilson } 2732e28a229885e9ba7fec9ef42cbf30fdcf8a0c939Jesse Wilson 274579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson // The type cast is safe: the generated type will extend the base class type. 275579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson @SuppressWarnings("unchecked") 276579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson private Class<? extends T> loadClass(ClassLoader classLoader, String generatedName) 277579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson throws ClassNotFoundException { 278579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson return (Class<? extends T>) classLoader.loadClass(generatedName); 279579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } 280579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson 281579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson private static RuntimeException launderCause(InvocationTargetException e) { 282579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson Throwable cause = e.getCause(); 283579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson // Errors should be thrown as they are. 284579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson if (cause instanceof Error) { 285579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson throw (Error) cause; 286579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } 287579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson // RuntimeException can be thrown as-is. 288579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson if (cause instanceof RuntimeException) { 289579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson throw (RuntimeException) cause; 290579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } 291579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson // Declared exceptions will have to be wrapped. 292579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson throw new UndeclaredThrowableException(cause); 293579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } 294579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson 295579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson private static void setHandlerInstanceField(Object instance, InvocationHandler handler) { 296579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson try { 297579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson Field handlerField = instance.getClass().getDeclaredField(FIELD_NAME_HANDLER); 298579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson handlerField.setAccessible(true); 299579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson handlerField.set(instance, handler); 300579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } catch (NoSuchFieldException e) { 301579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson // Should not be thrown, generated proxy class has been generated with this field. 302579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson throw new AssertionError(e); 303579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } catch (IllegalAccessException e) { 304579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson // Should not be thrown, we just set the field to accessible. 305579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson throw new AssertionError(e); 306579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } 307579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } 308579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson 309579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson private static void setMethodsStaticField(Class<?> proxyClass, Method[] methodsToProxy) { 310579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson try { 311579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson Field methodArrayField = proxyClass.getDeclaredField(FIELD_NAME_METHODS); 312579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson methodArrayField.setAccessible(true); 313579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson methodArrayField.set(null, methodsToProxy); 314579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } catch (NoSuchFieldException e) { 315579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson // Should not be thrown, generated proxy class has been generated with this field. 316579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson throw new AssertionError(e); 317579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } catch (IllegalAccessException e) { 318579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson // Should not be thrown, we just set the field to accessible. 319579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson throw new AssertionError(e); 320579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } 321579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } 322579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson 323579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson /** 324579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * Returns the proxy's {@link InvocationHandler}. 325579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * 326579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * @throws IllegalArgumentException if the object supplied is not a proxy created by this class. 327579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson */ 328579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson public static InvocationHandler getInvocationHandler(Object instance) { 329579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson try { 330579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson Field field = instance.getClass().getDeclaredField(FIELD_NAME_HANDLER); 331579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson field.setAccessible(true); 332579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson return (InvocationHandler) field.get(instance); 333579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } catch (NoSuchFieldException e) { 334579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson throw new IllegalArgumentException("Not a valid proxy instance", e); 335579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } catch (IllegalAccessException e) { 336579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson // Should not be thrown, we just set the field to accessible. 337579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson throw new AssertionError(e); 33873cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson } 33973cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson } 34073cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson 3411af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson // TODO: test coverage for isProxyClass 3421af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson 34373cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson /** 34473cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson * Returns true if {@code c} is a proxy class created by this builder. 34573cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson */ 34673cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson public static boolean isProxyClass(Class<?> c) { 34773cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson // TODO: use a marker interface instead? 34873cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson try { 34973cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson c.getDeclaredField(FIELD_NAME_HANDLER); 35073cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson return true; 35173cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson } catch (NoSuchFieldException e) { 35273cfa4498f640e0915b95fc806db4a0d54172fe8Jesse Wilson return false; 353579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } 354579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } 355579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson 356ab220f004db90fa94ef9349ca1adde5f89012e8dJesse Wilson private static <T, G extends T> void generateCodeForAllMethods(DexMaker dexMaker, 3570e49fb9243b7463835ab80ef7cc62435f55846ceJesse Wilson TypeId<G> generatedType, Method[] methodsToProxy, TypeId<T> superclassType) { 3580e49fb9243b7463835ab80ef7cc62435f55846ceJesse Wilson TypeId<InvocationHandler> handlerType = TypeId.get(InvocationHandler.class); 3590e49fb9243b7463835ab80ef7cc62435f55846ceJesse Wilson TypeId<Method[]> methodArrayType = TypeId.get(Method[].class); 360579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson FieldId<G, InvocationHandler> handlerField = 361579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson generatedType.getField(handlerType, FIELD_NAME_HANDLER); 362579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson FieldId<G, Method[]> allMethods = 363579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson generatedType.getField(methodArrayType, FIELD_NAME_METHODS); 3640e49fb9243b7463835ab80ef7cc62435f55846ceJesse Wilson TypeId<Method> methodType = TypeId.get(Method.class); 3650e49fb9243b7463835ab80ef7cc62435f55846ceJesse Wilson TypeId<Object[]> objectArrayType = TypeId.get(Object[].class); 3660e49fb9243b7463835ab80ef7cc62435f55846ceJesse Wilson MethodId<InvocationHandler, Object> methodInvoke = handlerType.getMethod(TypeId.OBJECT, 3670e49fb9243b7463835ab80ef7cc62435f55846ceJesse Wilson "invoke", TypeId.OBJECT, methodType, objectArrayType); 368579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson for (int m = 0; m < methodsToProxy.length; ++m) { 369579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson /* 370579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * If the 5th method on the superclass Example that can be overridden were to look like 371579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * this: 372579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * 373579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * public int doSomething(Bar param0, int param1) { 374579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * ... 375579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * } 376579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * 377579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * Then the following code will generate a method on the proxy that looks something 378579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * like this: 379579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * 380579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * public int doSomething(Bar param0, int param1) { 381579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * int methodIndex = 4; 382579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * Method[] allMethods = Example_Proxy.$__methodArray; 383579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * Method thisMethod = allMethods[methodIndex]; 384579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * int argsLength = 2; 385579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * Object[] args = new Object[argsLength]; 386579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * InvocationHandler localHandler = this.$__handler; 387579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * // for-loop begins 388579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * int p = 0; 389579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * Bar parameter0 = param0; 390579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * args[p] = parameter0; 391579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * p = 1; 392579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * int parameter1 = param1; 393579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * Integer boxed1 = Integer.valueOf(parameter1); 394579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * args[p] = boxed1; 395579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * // for-loop ends 396579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * Object result = localHandler.invoke(this, thisMethod, args); 397579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * Integer castResult = (Integer) result; 398579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * int unboxedResult = castResult.intValue(); 399579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * return unboxedResult; 400579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * } 401579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * 402579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * Or, in more idiomatic Java: 403579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * 404579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * public int doSomething(Bar param0, int param1) { 405579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * if ($__handler == null) { 406579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * return super.doSomething(param0, param1); 407579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * } 408579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * return __handler.invoke(this, __methodArray[4], 409579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * new Object[] { param0, Integer.valueOf(param1) }); 410579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * } 411579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson */ 412579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson Method method = methodsToProxy[m]; 413579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson String name = method.getName(); 414579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson Class<?>[] argClasses = method.getParameterTypes(); 4150e49fb9243b7463835ab80ef7cc62435f55846ceJesse Wilson TypeId<?>[] argTypes = new TypeId<?>[argClasses.length]; 416579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson for (int i = 0; i < argTypes.length; ++i) { 4170e49fb9243b7463835ab80ef7cc62435f55846ceJesse Wilson argTypes[i] = TypeId.get(argClasses[i]); 418579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } 419579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson Class<?> returnType = method.getReturnType(); 4200e49fb9243b7463835ab80ef7cc62435f55846ceJesse Wilson TypeId<?> resultType = TypeId.get(returnType); 421579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson MethodId<T, ?> superMethod = superclassType.getMethod(resultType, name, argTypes); 422579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson MethodId<?, ?> methodId = generatedType.getMethod(resultType, name, argTypes); 423ab220f004db90fa94ef9349ca1adde5f89012e8dJesse Wilson Code code = dexMaker.declare(methodId, PUBLIC); 424579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson Local<G> localThis = code.getThis(generatedType); 425579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson Local<InvocationHandler> localHandler = code.newLocal(handlerType); 4260e49fb9243b7463835ab80ef7cc62435f55846ceJesse Wilson Local<Object> invokeResult = code.newLocal(TypeId.OBJECT); 4270e49fb9243b7463835ab80ef7cc62435f55846ceJesse Wilson Local<Integer> intValue = code.newLocal(TypeId.INT); 428579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson Local<Object[]> args = code.newLocal(objectArrayType); 4290e49fb9243b7463835ab80ef7cc62435f55846ceJesse Wilson Local<Integer> argsLength = code.newLocal(TypeId.INT); 4300e49fb9243b7463835ab80ef7cc62435f55846ceJesse Wilson Local<Object> temp = code.newLocal(TypeId.OBJECT); 431579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson Local<?> resultHolder = code.newLocal(resultType); 432579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson Local<Method[]> methodArray = code.newLocal(methodArrayType); 433579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson Local<Method> thisMethod = code.newLocal(methodType); 4340e49fb9243b7463835ab80ef7cc62435f55846ceJesse Wilson Local<Integer> methodIndex = code.newLocal(TypeId.INT); 435579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson Class<?> aBoxedClass = PRIMITIVE_TO_BOXED.get(returnType); 436579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson Local<?> aBoxedResult = null; 437579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson if (aBoxedClass != null) { 4380e49fb9243b7463835ab80ef7cc62435f55846ceJesse Wilson aBoxedResult = code.newLocal(TypeId.get(aBoxedClass)); 439579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } 440579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson Local<?>[] superArgs2 = new Local<?>[argClasses.length]; 441579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson Local<?> superResult2 = code.newLocal(resultType); 442579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson Local<InvocationHandler> nullHandler = code.newLocal(handlerType); 443579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson 444579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson code.loadConstant(methodIndex, m); 445579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson code.sget(allMethods, methodArray); 4460e49fb9243b7463835ab80ef7cc62435f55846ceJesse Wilson code.aget(thisMethod, methodArray, methodIndex); 447579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson code.loadConstant(argsLength, argTypes.length); 4480e49fb9243b7463835ab80ef7cc62435f55846ceJesse Wilson code.newArray(args, argsLength); 4490e49fb9243b7463835ab80ef7cc62435f55846ceJesse Wilson code.iget(handlerField, localHandler, localThis); 450579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson 451579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson // if (proxy == null) 452579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson code.loadConstant(nullHandler, null); 45323abc2fe89ec3713645d64bdb74415a9090084f4Jesse Wilson Label handlerNullCase = new Label(); 4540e49fb9243b7463835ab80ef7cc62435f55846ceJesse Wilson code.compare(Comparison.EQ, handlerNullCase, nullHandler, localHandler); 455579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson 456579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson // This code is what we execute when we have a valid proxy: delegate to invocation 457579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson // handler. 458579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson for (int p = 0; p < argTypes.length; ++p) { 459579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson code.loadConstant(intValue, p); 460579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson Local<?> parameter = code.getParameter(p, argTypes[p]); 4611977585657cb304a9e1ffa8a2320fa8053a7383cJesse Wilson Local<?> unboxedIfNecessary = boxIfRequired(code, parameter, temp); 462579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson code.aput(args, intValue, unboxedIfNecessary); 463579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } 464579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson code.invokeInterface(methodInvoke, invokeResult, localHandler, 465579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson localThis, thisMethod, args); 466579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson generateCodeForReturnStatement(code, returnType, invokeResult, resultHolder, 467579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson aBoxedResult); 468579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson 469579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson // This code is executed if proxy is null: call the original super method. 470579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson // This is required to handle the case of construction of an object which leaks the 471579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson // "this" pointer. 472579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson code.mark(handlerNullCase); 473579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson for (int i = 0; i < superArgs2.length; ++i) { 474579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson superArgs2[i] = code.getParameter(i, argTypes[i]); 475579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } 476579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson if (void.class.equals(returnType)) { 477579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson code.invokeSuper(superMethod, null, localThis, superArgs2); 478579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson code.returnVoid(); 479579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } else { 480579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson invokeSuper(superMethod, code, localThis, superArgs2, superResult2); 481579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson code.returnValue(superResult2); 482579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } 483579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson 484579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson /* 485579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * And to allow calling the original super method, the following is also generated: 486579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * 4871af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson * public String super$doSomething$java_lang_String(Bar param0, int param1) { 488579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * int result = super.doSomething(param0, param1); 489579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * return result; 490579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * } 491579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson */ 4921af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson // TODO: don't include a super_ method if the target is abstract! 493579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson MethodId<G, ?> callsSuperMethod = generatedType.getMethod( 4941af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson resultType, superMethodName(method), argTypes); 495ab220f004db90fa94ef9349ca1adde5f89012e8dJesse Wilson Code superCode = dexMaker.declare(callsSuperMethod, PUBLIC); 496579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson Local<G> superThis = superCode.getThis(generatedType); 497579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson Local<?>[] superArgs = new Local<?>[argClasses.length]; 498579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson for (int i = 0; i < superArgs.length; ++i) { 499579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson superArgs[i] = superCode.getParameter(i, argTypes[i]); 500579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } 501579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson if (void.class.equals(returnType)) { 502579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson superCode.invokeSuper(superMethod, null, superThis, superArgs); 503579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson superCode.returnVoid(); 504579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } else { 505579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson Local<?> superResult = superCode.newLocal(resultType); 506579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson invokeSuper(superMethod, superCode, superThis, superArgs, superResult); 507579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson superCode.returnValue(superResult); 508579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } 509579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } 510579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } 511579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson 5121977585657cb304a9e1ffa8a2320fa8053a7383cJesse Wilson @SuppressWarnings({"unchecked", "rawtypes"}) 5131977585657cb304a9e1ffa8a2320fa8053a7383cJesse Wilson private static void invokeSuper(MethodId superMethod, Code superCode, 514579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson Local superThis, Local[] superArgs, Local superResult) { 515579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson superCode.invokeSuper(superMethod, superResult, superThis, superArgs); 516579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } 517579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson 5181977585657cb304a9e1ffa8a2320fa8053a7383cJesse Wilson private static Local<?> boxIfRequired(Code code, Local<?> parameter, Local<Object> temp) { 519579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson MethodId<?, ?> unboxMethod = PRIMITIVE_TYPE_TO_UNBOX_METHOD.get(parameter.getType()); 520579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson if (unboxMethod == null) { 521579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson return parameter; 522579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } 523579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson code.invokeStatic(unboxMethod, temp, parameter); 524579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson return temp; 525579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } 526579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson 5271af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson public static Object callSuper(Object proxy, Method method, Object... args) throws Throwable { 5281af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson try { 5291af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson return proxy.getClass() 5301af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson .getMethod(superMethodName(method), method.getParameterTypes()) 5311af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson .invoke(proxy, args); 5321af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson } catch (InvocationTargetException e) { 5331af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson throw e.getCause(); 5341af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson } 5351af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson } 5361af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson 5371af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson /** 5381af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson * The super method must include the return type, otherwise its ambiguous 5391af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson * for methods with covariant return types. 5401af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson */ 5411af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson private static String superMethodName(Method method) { 5421af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson String returnType = method.getReturnType().getName(); 5431af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson return "super$" + method.getName() + "$" 5441af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson + returnType.replace('.', '_').replace('[', '_').replace(';', '_'); 545579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } 546579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson 547579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson private static void check(boolean condition, String message) { 548579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson if (!condition) { 549579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson throw new IllegalArgumentException(message); 550579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } 551579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } 552579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson 553ab220f004db90fa94ef9349ca1adde5f89012e8dJesse Wilson private static <T, G extends T> void generateConstructorsAndFields(DexMaker dexMaker, 5540e49fb9243b7463835ab80ef7cc62435f55846ceJesse Wilson TypeId<G> generatedType, TypeId<T> superType, Class<T> superClass) { 5550e49fb9243b7463835ab80ef7cc62435f55846ceJesse Wilson TypeId<InvocationHandler> handlerType = TypeId.get(InvocationHandler.class); 5560e49fb9243b7463835ab80ef7cc62435f55846ceJesse Wilson TypeId<Method[]> methodArrayType = TypeId.get(Method[].class); 557579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson FieldId<G, InvocationHandler> handlerField = generatedType.getField( 558579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson handlerType, FIELD_NAME_HANDLER); 559ab220f004db90fa94ef9349ca1adde5f89012e8dJesse Wilson dexMaker.declare(handlerField, PRIVATE, null); 560579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson FieldId<G, Method[]> allMethods = generatedType.getField( 561579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson methodArrayType, FIELD_NAME_METHODS); 562ab220f004db90fa94ef9349ca1adde5f89012e8dJesse Wilson dexMaker.declare(allMethods, PRIVATE | STATIC, null); 563579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson for (Constructor<T> constructor : getConstructorsToOverwrite(superClass)) { 564579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson if (constructor.getModifiers() == Modifier.FINAL) { 565579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson continue; 566579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } 5670e49fb9243b7463835ab80ef7cc62435f55846ceJesse Wilson TypeId<?>[] types = classArrayToTypeArray(constructor.getParameterTypes()); 568579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson MethodId<?, ?> method = generatedType.getConstructor(types); 569c0271e9981ddd85a13ed88defd0b5b1a5ccc6f46Jesse Wilson Code constructorCode = dexMaker.declare(method, PUBLIC); 570579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson Local<G> thisRef = constructorCode.getThis(generatedType); 571579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson Local<?>[] params = new Local[types.length]; 572579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson for (int i = 0; i < params.length; ++i) { 573579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson params[i] = constructorCode.getParameter(i, types[i]); 574579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } 575579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson MethodId<T, ?> superConstructor = superType.getConstructor(types); 576579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson constructorCode.invokeDirect(superConstructor, null, thisRef, params); 577579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson constructorCode.returnVoid(); 578579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } 579579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } 580579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson 581579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson // The type parameter on Constructor is the class in which the constructor is declared. 582579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson // The getDeclaredConstructors() method gets constructors declared only in the given class, 583579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson // hence this cast is safe. 584579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson @SuppressWarnings("unchecked") 585579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson private static <T> Constructor<T>[] getConstructorsToOverwrite(Class<T> clazz) { 586579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson return (Constructor<T>[]) clazz.getDeclaredConstructors(); 587579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } 588579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson 5891af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson private TypeId<?>[] getInterfacesAsTypeIds() { 5901af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson TypeId<?>[] result = new TypeId<?>[interfaces.size()]; 5911af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson int i = 0; 5921af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson for (Class<?> implemented : interfaces) { 5931af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson result[i++] = TypeId.get(implemented); 5941af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson } 5951af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson return result; 5961af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson } 5971af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson 598579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson /** 5991af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson * Gets all {@link Method} objects we can proxy in the hierarchy of the 6001af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson * supplied class. 601579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson */ 6021af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson private Method[] getMethodsToProxyRecursive() { 603579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson Set<MethodSetEntry> methodsToProxy = new HashSet<MethodSetEntry>(); 60495689a700bfea5e2d78380a442fc2903cc40a3f2Mark Brophy Set<MethodSetEntry> seenFinalMethods = new HashSet<MethodSetEntry>(); 6051af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson for (Class<?> c = baseClass; c != null; c = c.getSuperclass()) { 60695689a700bfea5e2d78380a442fc2903cc40a3f2Mark Brophy getMethodsToProxy(methodsToProxy, seenFinalMethods, c); 6071af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson } 6081af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson for (Class<?> c : interfaces) { 60995689a700bfea5e2d78380a442fc2903cc40a3f2Mark Brophy getMethodsToProxy(methodsToProxy, seenFinalMethods, c); 610579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } 6111af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson 612579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson Method[] results = new Method[methodsToProxy.size()]; 613579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson int i = 0; 614579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson for (MethodSetEntry entry : methodsToProxy) { 615579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson results[i++] = entry.originalMethod; 616579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } 617fec7276299b950a8831ead9a9756d27e0fc60560Andrew Yousef 618fec7276299b950a8831ead9a9756d27e0fc60560Andrew Yousef // Sort the results array so that they are returned by this method 619fec7276299b950a8831ead9a9756d27e0fc60560Andrew Yousef // in a deterministic fashion. 620fec7276299b950a8831ead9a9756d27e0fc60560Andrew Yousef Arrays.sort(results, new Comparator<Method>() { 621fec7276299b950a8831ead9a9756d27e0fc60560Andrew Yousef @Override 622fec7276299b950a8831ead9a9756d27e0fc60560Andrew Yousef public int compare(Method method1, Method method2) { 623fec7276299b950a8831ead9a9756d27e0fc60560Andrew Yousef return method1.toString().compareTo(method2.toString()); 624fec7276299b950a8831ead9a9756d27e0fc60560Andrew Yousef } 625fec7276299b950a8831ead9a9756d27e0fc60560Andrew Yousef }); 626fec7276299b950a8831ead9a9756d27e0fc60560Andrew Yousef 627579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson return results; 628579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } 629579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson 63095689a700bfea5e2d78380a442fc2903cc40a3f2Mark Brophy private void getMethodsToProxy(Set<MethodSetEntry> sink, Set<MethodSetEntry> seenFinalMethods, 63195689a700bfea5e2d78380a442fc2903cc40a3f2Mark Brophy Class<?> c) { 6321af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson for (Method method : c.getDeclaredMethods()) { 6331af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson if ((method.getModifiers() & Modifier.FINAL) != 0) { 63495689a700bfea5e2d78380a442fc2903cc40a3f2Mark Brophy // Skip final methods, we can't override them. We 63595689a700bfea5e2d78380a442fc2903cc40a3f2Mark Brophy // also need to remember them, in case the same 63695689a700bfea5e2d78380a442fc2903cc40a3f2Mark Brophy // method exists in a parent class. 63795689a700bfea5e2d78380a442fc2903cc40a3f2Mark Brophy seenFinalMethods.add(new MethodSetEntry(method)); 6381af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson continue; 6391af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson } 6401af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson if ((method.getModifiers() & STATIC) != 0) { 6411af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson // Skip static methods, overriding them has no effect. 6421af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson continue; 6431af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson } 6441af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson if (method.getName().equals("finalize") && method.getParameterTypes().length == 0) { 6451af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson // Skip finalize method, it's likely important that it execute as normal. 6461af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson continue; 6471af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson } 64895689a700bfea5e2d78380a442fc2903cc40a3f2Mark Brophy MethodSetEntry entry = new MethodSetEntry(method); 64995689a700bfea5e2d78380a442fc2903cc40a3f2Mark Brophy if (seenFinalMethods.contains(entry)) { 65095689a700bfea5e2d78380a442fc2903cc40a3f2Mark Brophy // This method is final in a child class. 65195689a700bfea5e2d78380a442fc2903cc40a3f2Mark Brophy // We can't override it. 65295689a700bfea5e2d78380a442fc2903cc40a3f2Mark Brophy continue; 65395689a700bfea5e2d78380a442fc2903cc40a3f2Mark Brophy } 65495689a700bfea5e2d78380a442fc2903cc40a3f2Mark Brophy sink.add(entry); 6551af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson } 65695689a700bfea5e2d78380a442fc2903cc40a3f2Mark Brophy 6571af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson for (Class<?> i : c.getInterfaces()) { 65895689a700bfea5e2d78380a442fc2903cc40a3f2Mark Brophy getMethodsToProxy(sink, seenFinalMethods, i); 6591af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson } 6601af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson } 6611af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson 662579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson private static <T> String getMethodNameForProxyOf(Class<T> clazz) { 663579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson return clazz.getSimpleName() + "_Proxy"; 664579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } 665579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson 6660e49fb9243b7463835ab80ef7cc62435f55846ceJesse Wilson private static TypeId<?>[] classArrayToTypeArray(Class<?>[] input) { 6670e49fb9243b7463835ab80ef7cc62435f55846ceJesse Wilson TypeId<?>[] result = new TypeId[input.length]; 668579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson for (int i = 0; i < input.length; ++i) { 6690e49fb9243b7463835ab80ef7cc62435f55846ceJesse Wilson result[i] = TypeId.get(input[i]); 670579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } 671579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson return result; 672579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } 673579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson 674579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson /** 675579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * Calculates the correct return statement code for a method. 676579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * <p> 677579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * A void method will not return anything. A method that returns a primitive will need to 678579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * unbox the boxed result. Otherwise we will cast the result. 679579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson */ 680579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson // This one is tricky to fix, I gave up. 681579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson @SuppressWarnings({ "rawtypes", "unchecked" }) 682579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson private static void generateCodeForReturnStatement(Code code, Class methodReturnType, 683579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson Local localForResultOfInvoke, Local localOfMethodReturnType, Local aBoxedResult) { 684579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson if (PRIMITIVE_TO_UNBOX_METHOD.containsKey(methodReturnType)) { 68597b0be6b3da9df87e9026f880b0b0bffc7242450Jesse Wilson code.cast(aBoxedResult, localForResultOfInvoke); 686579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson MethodId unboxingMethodFor = getUnboxMethodForPrimitive(methodReturnType); 687579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson code.invokeVirtual(unboxingMethodFor, localOfMethodReturnType, aBoxedResult); 688579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson code.returnValue(localOfMethodReturnType); 689579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } else if (void.class.equals(methodReturnType)) { 690579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson code.returnVoid(); 691579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } else { 69297b0be6b3da9df87e9026f880b0b0bffc7242450Jesse Wilson code.cast(localOfMethodReturnType, localForResultOfInvoke); 693579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson code.returnValue(localOfMethodReturnType); 694579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } 695579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } 696579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson 6971af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson private static <T> Set<T> asSet(T... array) { 6981af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson return new CopyOnWriteArraySet<T>(Arrays.asList(array)); 6991af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson } 7001af1da6af1f59f0bc1f9d048f31279ce5e614c3dJesse Wilson 701579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson private static MethodId<?, ?> getUnboxMethodForPrimitive(Class<?> methodReturnType) { 702579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson return PRIMITIVE_TO_UNBOX_METHOD.get(methodReturnType); 703579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } 704579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson 705579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson private static final Map<Class<?>, Class<?>> PRIMITIVE_TO_BOXED; 706579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson static { 707579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson PRIMITIVE_TO_BOXED = new HashMap<Class<?>, Class<?>>(); 708579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson PRIMITIVE_TO_BOXED.put(boolean.class, Boolean.class); 709579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson PRIMITIVE_TO_BOXED.put(int.class, Integer.class); 710579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson PRIMITIVE_TO_BOXED.put(byte.class, Byte.class); 711579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson PRIMITIVE_TO_BOXED.put(long.class, Long.class); 712579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson PRIMITIVE_TO_BOXED.put(short.class, Short.class); 713579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson PRIMITIVE_TO_BOXED.put(float.class, Float.class); 714579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson PRIMITIVE_TO_BOXED.put(double.class, Double.class); 715579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson PRIMITIVE_TO_BOXED.put(char.class, Character.class); 716579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } 717579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson 7180e49fb9243b7463835ab80ef7cc62435f55846ceJesse Wilson private static final Map<TypeId<?>, MethodId<?, ?>> PRIMITIVE_TYPE_TO_UNBOX_METHOD; 719579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson static { 7200e49fb9243b7463835ab80ef7cc62435f55846ceJesse Wilson PRIMITIVE_TYPE_TO_UNBOX_METHOD = new HashMap<TypeId<?>, MethodId<?, ?>>(); 721579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson for (Map.Entry<Class<?>, Class<?>> entry : PRIMITIVE_TO_BOXED.entrySet()) { 7220e49fb9243b7463835ab80ef7cc62435f55846ceJesse Wilson TypeId<?> primitiveType = TypeId.get(entry.getKey()); 7230e49fb9243b7463835ab80ef7cc62435f55846ceJesse Wilson TypeId<?> boxedType = TypeId.get(entry.getValue()); 724579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson MethodId<?, ?> valueOfMethod = boxedType.getMethod(boxedType, "valueOf", primitiveType); 725579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson PRIMITIVE_TYPE_TO_UNBOX_METHOD.put(primitiveType, valueOfMethod); 726579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } 727579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } 728579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson 729579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson /** 730579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * Map from primitive type to method used to unbox a boxed version of the primitive. 731579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * <p> 732579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * This is required for methods whose return type is primitive, since the 733579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * {@link InvocationHandler} will return us a boxed result, and we'll need to convert it back to 734579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * primitive value. 735579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson */ 736579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson private static final Map<Class<?>, MethodId<?, ?>> PRIMITIVE_TO_UNBOX_METHOD; 737579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson static { 738579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson Map<Class<?>, MethodId<?, ?>> map = new HashMap<Class<?>, MethodId<?, ?>>(); 7390e49fb9243b7463835ab80ef7cc62435f55846ceJesse Wilson map.put(boolean.class, TypeId.get(Boolean.class).getMethod(TypeId.BOOLEAN, "booleanValue")); 7400e49fb9243b7463835ab80ef7cc62435f55846ceJesse Wilson map.put(int.class, TypeId.get(Integer.class).getMethod(TypeId.INT, "intValue")); 7410e49fb9243b7463835ab80ef7cc62435f55846ceJesse Wilson map.put(byte.class, TypeId.get(Byte.class).getMethod(TypeId.BYTE, "byteValue")); 7420e49fb9243b7463835ab80ef7cc62435f55846ceJesse Wilson map.put(long.class, TypeId.get(Long.class).getMethod(TypeId.LONG, "longValue")); 7430e49fb9243b7463835ab80ef7cc62435f55846ceJesse Wilson map.put(short.class, TypeId.get(Short.class).getMethod(TypeId.SHORT, "shortValue")); 7440e49fb9243b7463835ab80ef7cc62435f55846ceJesse Wilson map.put(float.class, TypeId.get(Float.class).getMethod(TypeId.FLOAT, "floatValue")); 7450e49fb9243b7463835ab80ef7cc62435f55846ceJesse Wilson map.put(double.class, TypeId.get(Double.class).getMethod(TypeId.DOUBLE, "doubleValue")); 7460e49fb9243b7463835ab80ef7cc62435f55846ceJesse Wilson map.put(char.class, TypeId.get(Character.class).getMethod(TypeId.CHAR, "charValue")); 747579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson PRIMITIVE_TO_UNBOX_METHOD = map; 748579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } 749579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson 750579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson /** 751579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * Wrapper class to let us disambiguate {@link Method} objects. 752579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * <p> 753579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * The purpose of this class is to override the {@link #equals(Object)} and {@link #hashCode()} 754579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * methods so we can use a {@link Set} to remove duplicate methods that are overrides of one 755579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * another. For these purposes, we consider two methods to be equal if they have the same 756579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson * name, return type, and parameter types. 757579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson */ 758579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson private static class MethodSetEntry { 759579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson private final String name; 760579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson private final Class<?>[] paramTypes; 761579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson private final Class<?> returnType; 762579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson private final Method originalMethod; 763579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson 764579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson public MethodSetEntry(Method method) { 765579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson originalMethod = method; 766579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson name = method.getName(); 767579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson paramTypes = method.getParameterTypes(); 768579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson returnType = method.getReturnType(); 769579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } 770579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson 771579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson @Override 772579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson public boolean equals(Object o) { 773579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson if (o instanceof MethodSetEntry) { 774579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson MethodSetEntry other = (MethodSetEntry) o; 775579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson return name.equals(other.name) 776579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson && returnType.equals(other.returnType) 777579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson && Arrays.equals(paramTypes, other.paramTypes); 778579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } 779579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson return false; 780579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } 781579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson 782579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson @Override 783579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson public int hashCode() { 784579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson int result = 17; 785579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson result += 31 * result + name.hashCode(); 786579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson result += 31 * result + returnType.hashCode(); 787579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson result += 31 * result + Arrays.hashCode(paramTypes); 788579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson return result; 789579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } 790579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } 791579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson} 792