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