18da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal/* 28da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal * Copyright 2010 Google Inc. 38da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal * 48da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal * Licensed under the Apache License, Version 2.0 (the "License"); 58da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal * you may not use this file except in compliance with the License. 68da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal * You may obtain a copy of the License at 78da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal * 88da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal * http://www.apache.org/licenses/LICENSE-2.0 98da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal * 108da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal * Unless required by applicable law or agreed to in writing, software 118da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal * distributed under the License is distributed on an "AS IS" BASIS, 128da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 138da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal * See the License for the specific language governing permissions and 148da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal * limitations under the License. 158da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal */ 168da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigalpackage com.google.android.testing.mocking; 178da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal 188da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigalimport javassist.CannotCompileException; 198da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigalimport javassist.ClassClassPath; 208da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigalimport javassist.ClassPool; 218da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigalimport javassist.CtClass; 228da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigalimport javassist.CtConstructor; 238da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigalimport javassist.CtField; 248da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigalimport javassist.CtMethod; 258da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigalimport javassist.CtNewConstructor; 268da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigalimport javassist.NotFoundException; 278da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal 288da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigalimport java.io.IOException; 298da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigalimport java.lang.reflect.Constructor; 308da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigalimport java.lang.reflect.Method; 318da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigalimport java.lang.reflect.Modifier; 328da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigalimport java.util.ArrayList; 338da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigalimport java.util.Arrays; 348da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigalimport java.util.HashMap; 358da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigalimport java.util.List; 368da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigalimport java.util.Map; 378da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal 388da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal 398da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal/** 408da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal * AndroidMockGenerator creates the subclass and interface required for mocking 418da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal * a given Class. 428da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal * 438da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal * The only public method of AndroidMockGenerator is createMocksForClass. See 448da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal * the javadocs for this method for more information about AndroidMockGenerator. 458da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal * 468da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal * @author swoodward@google.com (Stephen Woodward) 478da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal */ 488da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigalclass AndroidMockGenerator { 498da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal public AndroidMockGenerator() { 508da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal ClassPool.doPruning = false; 518da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal ClassPool.getDefault().insertClassPath(new ClassClassPath(MockObject.class)); 528da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 538da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal 548da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal /** 558da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal * Creates a List of javassist.CtClass objects representing all of the 568da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal * interfaces and subclasses required to meet the Mocking requests of the 578da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal * Class specified by {@code clazz}. 588da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal * 598da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal * A test class can request that a Class be prepared for mocking by using the 608da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal * {@link UsesMocks} annotation at either the Class or Method level. All 618da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal * classes specified by these annotations will have exactly two CtClass 628da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal * objects created, one for a generated interface, and one for a generated 638da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal * subclass. The interface and subclass both define the same methods which 648da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal * comprise all of the mockable methods of the provided class. At present, for 658da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal * a method to be mockable, it must be non-final and non-static, although this 668da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal * may expand in the future. 678da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal * 688da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal * The class itself must be mockable, otherwise this method will ignore the 698da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal * requested mock and print a warning. At present, a class is mockable if it 708da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal * is a non-final publicly-instantiable Java class that is assignable from the 718da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal * java.lang.Object class. See the javadocs for 728da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal * {@link java.lang.Class#isAssignableFrom(Class)} for more information about 738da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal * what "is assignable from the Object class" means. As a non-exhaustive 748da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal * example, if a given Class represents an Enum, Annotation, Primitive or 758da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal * Array, then it is not assignable from Object. Interfaces are also ignored 768da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal * since these need no modifications in order to be mocked. 778da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal * 788da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal * @param clazz the Class object to have all of its UsesMocks annotations 798da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal * processed and the corresponding Mock Classes created. 808da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal * @return a List of CtClass objects representing the Classes and Interfaces 818da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal * required for mocking the classes requested by {@code clazz} 828da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal * @throws ClassNotFoundException 838da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal * @throws CannotCompileException 848da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal * @throws IOException 858da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal */ 868da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal public List<GeneratedClassFile> createMocksForClass(Class<?> clazz) 878da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal throws ClassNotFoundException, IOException, CannotCompileException { 888da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal return this.createMocksForClass(clazz, SdkVersion.UNKNOWN); 898da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 908da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal 918da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal public List<GeneratedClassFile> createMocksForClass(Class<?> clazz, SdkVersion sdkVersion) 928da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal throws ClassNotFoundException, IOException, CannotCompileException { 938da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal if (!classIsSupportedType(clazz)) { 948da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal reportReasonForUnsupportedType(clazz); 958da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal return Arrays.asList(new GeneratedClassFile[0]); 968da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 978da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal CtClass newInterfaceCtClass = generateInterface(clazz, sdkVersion); 988da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal GeneratedClassFile newInterface = new GeneratedClassFile(newInterfaceCtClass.getName(), 998da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal newInterfaceCtClass.toBytecode()); 1008da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal CtClass mockDelegateCtClass = generateSubClass(clazz, newInterfaceCtClass, sdkVersion); 1018da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal GeneratedClassFile mockDelegate = new GeneratedClassFile(mockDelegateCtClass.getName(), 1028da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal mockDelegateCtClass.toBytecode()); 1038da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal return Arrays.asList(new GeneratedClassFile[] {newInterface, mockDelegate}); 1048da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 1058da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal 1068da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal private void reportReasonForUnsupportedType(Class<?> clazz) { 1078da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal String reason = null; 1088da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal if (clazz.isInterface()) { 1098da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal // do nothing to make sure none of the other conditions apply. 1108da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } else if (clazz.isEnum()) { 1118da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal reason = "Cannot mock an Enum"; 1128da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } else if (clazz.isAnnotation()) { 1138da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal reason = "Cannot mock an Annotation"; 1148da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } else if (clazz.isArray()) { 1158da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal reason = "Cannot mock an Array"; 1168da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } else if (Modifier.isFinal(clazz.getModifiers())) { 1178da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal reason = "Cannot mock a Final class"; 1188da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } else if (clazz.isPrimitive()) { 1198da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal reason = "Cannot mock primitives"; 1208da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } else if (!Object.class.isAssignableFrom(clazz)) { 1218da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal reason = "Cannot mock non-classes"; 1228da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } else if (!containsUsableConstructor(clazz)) { 1238da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal reason = "Cannot mock a class with no public constructors"; 1248da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } else { 1258da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal // Whatever the reason is, it's not one that we care about. 1268da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 1278da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal if (reason != null) { 1288da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal // Sometimes we want to be silent, so check 'reason' against null. 1298da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal System.err.println(reason + ": " + clazz.getName()); 1308da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 1318da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 1328da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal 1338da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal private boolean containsUsableConstructor(Class<?> clazz) { 1348da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal Constructor<?>[] constructors = clazz.getDeclaredConstructors(); 1358da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal for (Constructor<?> constructor : constructors) { 1368da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal if (Modifier.isPublic(constructor.getModifiers()) || 1378da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal Modifier.isProtected(constructor.getModifiers())) { 1388da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal return true; 1398da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 1408da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 1418da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal return false; 1428da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 1438da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal 1448da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal boolean classIsSupportedType(Class<?> clazz) { 1458da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal return (containsUsableConstructor(clazz)) && Object.class.isAssignableFrom(clazz) 1468da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal && !clazz.isInterface() && !clazz.isEnum() && !clazz.isAnnotation() && !clazz.isArray() 1478da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal && !Modifier.isFinal(clazz.getModifiers()); 1488da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 1498da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal 1508da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal void saveCtClass(CtClass clazz) throws ClassNotFoundException, IOException { 1518da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal try { 1528da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal clazz.writeFile(); 1538da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } catch (NotFoundException e) { 1548da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal throw new ClassNotFoundException("Error while saving modified class " + clazz.getName(), e); 1558da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } catch (CannotCompileException e) { 1568da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal throw new RuntimeException("Internal Error: Attempt to save syntactically incorrect code " 1578da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal + "for class " + clazz.getName(), e); 1588da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 1598da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 1608da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal 1618da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal CtClass generateInterface(Class<?> originalClass, SdkVersion sdkVersion) { 1628da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal ClassPool classPool = getClassPool(); 1638da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal try { 1648da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal return classPool.getCtClass(FileUtils.getInterfaceNameFor(originalClass, sdkVersion)); 1658da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } catch (NotFoundException e) { 1668da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal CtClass newInterface = 1678da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal classPool.makeInterface(FileUtils.getInterfaceNameFor(originalClass, sdkVersion)); 1688da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal addInterfaceMethods(originalClass, newInterface); 1698da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal return newInterface; 1708da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 1718da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 1728da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal 1738da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal String getInterfaceMethodSource(Method method) throws UnsupportedOperationException { 1748da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal StringBuilder methodBody = getMethodSignature(method); 1758da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal methodBody.append(";"); 1768da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal return methodBody.toString(); 1778da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 1788da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal 1798da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal private StringBuilder getMethodSignature(Method method) { 1808da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal int modifiers = method.getModifiers(); 1818da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal if (Modifier.isFinal(modifiers) || Modifier.isStatic(modifiers)) { 1828da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal throw new UnsupportedOperationException( 1838da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal "Cannot specify final or static methods in an interface"); 1848da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 1858da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal StringBuilder methodSignature = new StringBuilder("public "); 1868da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal methodSignature.append(getClassName(method.getReturnType())); 1878da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal methodSignature.append(" "); 1888da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal methodSignature.append(method.getName()); 1898da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal methodSignature.append("("); 1908da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal int i = 0; 1918da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal for (Class<?> arg : method.getParameterTypes()) { 1928da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal methodSignature.append(getClassName(arg)); 1938da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal methodSignature.append(" arg"); 1948da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal methodSignature.append(i); 1958da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal if (i < method.getParameterTypes().length - 1) { 1968da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal methodSignature.append(","); 1978da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 1988da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal i++; 1998da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 2008da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal methodSignature.append(")"); 2018da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal if (method.getExceptionTypes().length > 0) { 2028da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal methodSignature.append(" throws "); 2038da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 2048da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal i = 0; 2058da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal for (Class<?> exception : method.getExceptionTypes()) { 2068da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal methodSignature.append(getClassName(exception)); 2078da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal if (i < method.getExceptionTypes().length - 1) { 2088da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal methodSignature.append(","); 2098da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 2108da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal i++; 2118da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 2128da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal return methodSignature; 2138da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 2148da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal 2158da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal private String getClassName(Class<?> clazz) { 2168da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal return clazz.getCanonicalName(); 2178da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 2188da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal 2198da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal static ClassPool getClassPool() { 2208da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal return ClassPool.getDefault(); 2218da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 2228da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal 2238da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal private boolean classExists(String name) { 2248da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal // The following line is the ideal, but doesn't work (bug in library). 2258da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal // return getClassPool().find(name) != null; 2268da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal try { 2278da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal getClassPool().get(name); 2288da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal return true; 2298da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } catch (NotFoundException e) { 2308da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal return false; 2318da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 2328da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 2338da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal 2348da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal CtClass generateSubClass(Class<?> superClass, CtClass newInterface, SdkVersion sdkVersion) 2358da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal throws ClassNotFoundException { 2368da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal if (classExists(FileUtils.getSubclassNameFor(superClass, sdkVersion))) { 2378da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal try { 2388da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal return getClassPool().get(FileUtils.getSubclassNameFor(superClass, sdkVersion)); 2398da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } catch (NotFoundException e) { 2408da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal throw new ClassNotFoundException("This should be impossible, since we just checked for " 2418da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal + "the existence of the class being created", e); 2428da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 2438da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 2448da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal CtClass newClass = generateSkeletalClass(superClass, newInterface, sdkVersion); 2458da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal if (!newClass.isFrozen()) { 2468da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal newClass.addInterface(newInterface); 2478da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal try { 2488da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal newClass.addInterface(getClassPool().get(MockObject.class.getName())); 2498da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } catch (NotFoundException e) { 2508da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal throw new ClassNotFoundException("Could not find " + MockObject.class.getName(), e); 2518da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 2528da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal addMethods(superClass, newClass); 2538da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal addGetDelegateMethod(newClass); 2548da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal addSetDelegateMethod(newClass, newInterface); 2558da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal addConstructors(newClass, superClass); 2568da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 2578da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal return newClass; 2588da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 2598da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal 2608da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal private void addConstructors(CtClass clazz, Class<?> superClass) throws ClassNotFoundException { 2618da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal CtClass superCtClass = getCtClassForClass(superClass); 2628da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal 2638da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal CtConstructor[] constructors = superCtClass.getDeclaredConstructors(); 2648da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal for (CtConstructor constructor : constructors) { 2658da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal int modifiers = constructor.getModifiers(); 2668da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal if (Modifier.isPublic(modifiers) || Modifier.isProtected(modifiers)) { 2678da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal CtConstructor ctConstructor; 2688da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal try { 2698da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal ctConstructor = CtNewConstructor.make(constructor.getParameterTypes(), 2708da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal constructor.getExceptionTypes(), clazz); 2718da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal clazz.addConstructor(ctConstructor); 2728da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } catch (CannotCompileException e) { 2738da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal throw new RuntimeException("Internal Error - Could not add constructors.", e); 2748da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } catch (NotFoundException e) { 2758da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal throw new RuntimeException("Internal Error - Constructor suddenly could not be found", e); 2768da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 2778da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 2788da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 2798da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 2808da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal 2818da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal CtClass getCtClassForClass(Class<?> clazz) throws ClassNotFoundException { 2828da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal ClassPool classPool = getClassPool(); 2838da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal try { 2848da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal return classPool.get(clazz.getName()); 2858da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } catch (NotFoundException e) { 2868da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal throw new ClassNotFoundException("Class not found when finding the class to be mocked: " 2878da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal + clazz.getName(), e); 2888da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 2898da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 2908da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal 2918da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal private void addSetDelegateMethod(CtClass clazz, CtClass newInterface) { 2928da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal try { 2938da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal clazz.addMethod(CtMethod.make(getSetDelegateMethodSource(newInterface), clazz)); 2948da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } catch (CannotCompileException e) { 2958da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal throw new RuntimeException("Internal error while creating the setDelegate() method", e); 2968da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 2978da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 2988da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal 2998da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal String getSetDelegateMethodSource(CtClass newInterface) { 3008da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal return "public void setDelegate___AndroidMock(" + newInterface.getName() + " obj) { this." 3018da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal + getDelegateFieldName() + " = obj;}"; 3028da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 3038da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal 3048da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal private void addGetDelegateMethod(CtClass clazz) { 3058da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal try { 3068da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal CtMethod newMethod = CtMethod.make(getGetDelegateMethodSource(), clazz); 3078da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal try { 3088da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal CtMethod existingMethod = clazz.getMethod(newMethod.getName(), newMethod.getSignature()); 3098da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal clazz.removeMethod(existingMethod); 3108da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } catch (NotFoundException e) { 3118da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal // expected path... sigh. 3128da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 3138da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal clazz.addMethod(newMethod); 3148da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } catch (CannotCompileException e) { 3158da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal throw new RuntimeException("Internal error while creating the getDelegate() method", e); 3168da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 3178da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 3188da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal 3198da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal private String getGetDelegateMethodSource() { 3208da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal return "public Object getDelegate___AndroidMock() { return this." + getDelegateFieldName() 3218da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal + "; }"; 3228da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 3238da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal 3248da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal String getDelegateFieldName() { 3258da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal return "delegateMockObject"; 3268da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 3278da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal 3288da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal void addInterfaceMethods(Class<?> originalClass, CtClass newInterface) { 3298da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal Method[] methods = getAllMethods(originalClass); 3308da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal for (Method method : methods) { 3318da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal try { 3328da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal if (isMockable(method)) { 3338da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal CtMethod newMethod = CtMethod.make(getInterfaceMethodSource(method), newInterface); 3348da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal newInterface.addMethod(newMethod); 3358da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 3368da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } catch (UnsupportedOperationException e) { 3378da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal // Can't handle finals and statics. 3388da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } catch (CannotCompileException e) { 3398da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal throw new RuntimeException( 3408da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal "Internal error while creating a new Interface method for class " 3418da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal + originalClass.getName() + ". Method name: " + method.getName(), e); 3428da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 3438da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 3448da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 3458da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal 3468da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal void addMethods(Class<?> superClass, CtClass newClass) { 3478da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal Method[] methods = getAllMethods(superClass); 3488da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal if (newClass.isFrozen()) { 3498da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal newClass.defrost(); 3508da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 3518da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal List<CtMethod> existingMethods = Arrays.asList(newClass.getDeclaredMethods()); 3528da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal for (Method method : methods) { 3538da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal try { 3548da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal if (isMockable(method)) { 3558da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal CtMethod newMethod = CtMethod.make(getDelegateMethodSource(method), newClass); 3568da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal if (!existingMethods.contains(newMethod)) { 3578da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal newClass.addMethod(newMethod); 3588da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 3598da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 3608da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } catch (UnsupportedOperationException e) { 3618da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal // Can't handle finals and statics. 3628da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } catch (CannotCompileException e) { 3638da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal throw new RuntimeException("Internal Error while creating subclass methods for " 3648da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal + newClass.getName() + " method: " + method.getName(), e); 3658da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 3668da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 3678da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 3688da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal 3698da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal Method[] getAllMethods(Class<?> clazz) { 3708da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal Map<String, Method> methodMap = getAllMethodsMap(clazz); 3718da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal return methodMap.values().toArray(new Method[0]); 3728da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 3738da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal 3748da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal private Map<String, Method> getAllMethodsMap(Class<?> clazz) { 3758da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal Map<String, Method> methodMap = new HashMap<String, Method>(); 3768da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal Class<?> superClass = clazz.getSuperclass(); 3778da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal if (superClass != null) { 3788da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal methodMap.putAll(getAllMethodsMap(superClass)); 3798da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 3808da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal List<Method> methods = new ArrayList<Method>(Arrays.asList(clazz.getDeclaredMethods())); 3818da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal for (Method method : methods) { 3828da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal String key = method.getName(); 3838da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal for (Class<?> param : method.getParameterTypes()) { 3848da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal key += param.getCanonicalName(); 3858da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 3868da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal methodMap.put(key, method); 3878da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 3888da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal return methodMap; 3898da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 3908da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal 3918da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal boolean isMockable(Method method) { 3928da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal if (isForbiddenMethod(method)) { 3938da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal return false; 3948da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 3958da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal int modifiers = method.getModifiers(); 3968da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal return !Modifier.isFinal(modifiers) && !Modifier.isStatic(modifiers) && !method.isBridge() 3978da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal && (Modifier.isPublic(modifiers) || Modifier.isProtected(modifiers)); 3988da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 3998da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal 4008da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal boolean isForbiddenMethod(Method method) { 4018da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal if (method.getName().equals("equals")) { 4028da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal return method.getParameterTypes().length == 1 4038da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal && method.getParameterTypes()[0].equals(Object.class); 4048da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } else if (method.getName().equals("toString")) { 4058da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal return method.getParameterTypes().length == 0; 4068da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } else if (method.getName().equals("hashCode")) { 4078da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal return method.getParameterTypes().length == 0; 4088da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 4098da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal return false; 4108da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 4118da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal 4128da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal private String getReturnDefault(Method method) { 4138da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal Class<?> returnType = method.getReturnType(); 4148da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal if (!returnType.isPrimitive()) { 4158da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal return "null"; 4168da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } else if (returnType == Boolean.TYPE) { 4178da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal return "false"; 4188da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } else if (returnType == Void.TYPE) { 4198da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal return ""; 4208da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } else { 4218da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal return "(" + returnType.getName() + ")0"; 4228da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 4238da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 4248da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal 4258da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal String getDelegateMethodSource(Method method) { 4268da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal StringBuilder methodBody = getMethodSignature(method); 4278da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal methodBody.append("{"); 4288da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal methodBody.append("if(this."); 4298da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal methodBody.append(getDelegateFieldName()); 4308da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal methodBody.append("==null){return "); 4318da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal methodBody.append(getReturnDefault(method)); 4328da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal methodBody.append(";}"); 4338da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal if (!method.getReturnType().equals(Void.TYPE)) { 4348da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal methodBody.append("return "); 4358da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 4368da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal methodBody.append("this."); 4378da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal methodBody.append(getDelegateFieldName()); 4388da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal methodBody.append("."); 4398da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal methodBody.append(method.getName()); 4408da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal methodBody.append("("); 4418da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal for (int i = 0; i < method.getParameterTypes().length; ++i) { 4428da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal methodBody.append("arg"); 4438da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal methodBody.append(i); 4448da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal if (i < method.getParameterTypes().length - 1) { 4458da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal methodBody.append(","); 4468da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 4478da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 4488da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal methodBody.append(");}"); 4498da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal return methodBody.toString(); 4508da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 4518da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal 4528da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal CtClass generateSkeletalClass(Class<?> superClass, CtClass newInterface, SdkVersion sdkVersion) 4538da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal throws ClassNotFoundException { 4548da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal ClassPool classPool = getClassPool(); 4558da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal CtClass superCtClass = getCtClassForClass(superClass); 4568da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal String subclassName = FileUtils.getSubclassNameFor(superClass, sdkVersion); 4578da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal 4588da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal CtClass newClass; 4598da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal try { 4608da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal newClass = classPool.makeClass(subclassName, superCtClass); 4618da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } catch (RuntimeException e) { 4628da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal if (e.getMessage().contains("frozen class")) { 4638da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal try { 4648da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal return classPool.get(subclassName); 4658da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } catch (NotFoundException ex) { 4668da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal throw new ClassNotFoundException("Internal Error: could not find class", ex); 4678da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 4688da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 4698da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal throw e; 4708da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 4718da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal 4728da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal try { 4738da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal newClass.addField(new CtField(newInterface, getDelegateFieldName(), newClass)); 4748da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } catch (CannotCompileException e) { 4758da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal throw new RuntimeException("Internal error adding the delegate field to " 4768da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal + newClass.getName(), e); 4778da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 4788da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal return newClass; 4798da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal } 4808da3e6ec64b991f5aa1e6561941d130683eba753Luis Sigal} 481