Home | History | Annotate | Download | only in mocking
      1 /*
      2  * Copyright 2010 Google Inc.
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
      5  * use this file except in compliance with the License. You may obtain a copy of
      6  * the License at
      7  *
      8  * http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
     12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
     13  * License for the specific language governing permissions and limitations under
     14  * the License.
     15  */
     16 package com.google.android.testing.mocking;
     17 
     18 import javassist.CannotCompileException;
     19 import javassist.CtClass;
     20 import javassist.NotFoundException;
     21 
     22 import java.io.File;
     23 import java.io.IOException;
     24 import java.util.ArrayList;
     25 import java.util.Collection;
     26 import java.util.Collections;
     27 import java.util.HashSet;
     28 import java.util.List;
     29 import java.util.Set;
     30 import java.util.jar.JarEntry;
     31 import java.util.jar.JarFile;
     32 
     33 /**
     34  * Mock Generator for Android Framework classes.
     35  *
     36  * <p>
     37  * This class will pull pre-defined mocks for Android framework classes. This is
     38  * needed because a project might build against version X and then run against
     39  * version Y, where the specification of the class being mocked will have
     40  * changed, rendering the mock invalid.
     41  *
     42  * @author swoodward (at) google.com (Stephen Woodward)
     43  */
     44 public class AndroidFrameworkMockGenerator {
     45   private static final String LIB_FOLDER = "lib";
     46   private static final String JAR_NAME = "android_";
     47 
     48   /**
     49    * Returns a set of mock support classes for the specified Class for all versions of
     50    * the Android SDK. If the requested class is not part of the Android framework, then the class
     51    * will not be found and an exception will be thrown.
     52    *
     53    * @param clazz the class to mock.
     54    * @return All available mock support classes (for all known Android SDKs) for
     55    *         the requested Class.
     56    */
     57   public List<GeneratedClassFile> getMocksForClass(Class<?> clazz) throws ClassNotFoundException,
     58       IOException {
     59     List<Class<?>> prebuiltClasses = getPrebuiltClassesFor(clazz);
     60     List<GeneratedClassFile> classList = new ArrayList<GeneratedClassFile>();
     61     for (Class<?> prebuiltClass : prebuiltClasses) {
     62       try {
     63         CtClass ctClass = AndroidMockGenerator.getClassPool().get(prebuiltClass.getName());
     64         classList.add(new GeneratedClassFile(ctClass.getName(), ctClass.toBytecode()));
     65       } catch (NotFoundException e) {
     66         throw new ClassNotFoundException("Missing class while fetching prebuilt mocks: "
     67             + prebuiltClass.getName(), e);
     68       } catch (CannotCompileException e) {
     69         throw new RuntimeException("Internal Error copying a prebuilt mock: "
     70             + prebuiltClass.getName(), e);
     71       }
     72     }
     73     return classList;
     74   }
     75 
     76   private List<Class<?>> getPrebuiltClassesFor(Class<?> clazz) throws ClassNotFoundException {
     77     List<Class<?>> classes = new ArrayList<Class<?>>();
     78     SdkVersion[] versions = SdkVersion.getAllVersions();
     79     for (SdkVersion sdkVersion : versions) {
     80       classes.add(Class.forName(FileUtils.getSubclassNameFor(clazz, sdkVersion)));
     81       classes.add(Class.forName(FileUtils.getInterfaceNameFor(clazz, sdkVersion)));
     82     }
     83     return classes;
     84   }
     85 
     86   /**
     87    * @return a List of {@link GeneratedClassFile} objects representing the mocks for the specified
     88    *         class for a single version of the Android SDK.
     89    */
     90   public List<GeneratedClassFile> createMocksForClass(Class<?> clazz, SdkVersion version)
     91       throws ClassNotFoundException, IOException, CannotCompileException {
     92     AndroidMockGenerator mockGenerator = new AndroidMockGenerator();
     93     List<GeneratedClassFile> mocks = new ArrayList<GeneratedClassFile>();
     94     mocks.addAll(mockGenerator.createMocksForClass(clazz, version));
     95     return mocks;
     96   }
     97 
     98   /**
     99    * @return A list of all class files inside the provided jar file.
    100    */
    101   List<Class<?>> getClassList(JarFile jar) throws ClassNotFoundException {
    102     return getClassList(Collections.list(jar.entries()));
    103   }
    104 
    105   List<Class<?>> getClassList(Collection<JarEntry> jarEntries) throws ClassNotFoundException {
    106     List<Class<?>> classList = new ArrayList<Class<?>>();
    107     for (JarEntry jarEntry : jarEntries) {
    108       if (jarEntryIsClassFile(jarEntry)) {
    109         classList.add(Class.forName(FileUtils.getClassNameFor(jarEntry.getName())));
    110       }
    111     }
    112     return classList;
    113   }
    114 
    115   /**
    116    * @return true if the provided JarEntry represents a class file.
    117    */
    118   boolean jarEntryIsClassFile(JarEntry jarEntry) {
    119     return jarEntry.getName().endsWith(".class");
    120   }
    121 
    122   /**
    123    * @return the Android framework jar file for the specified {@link SdkVersion}.
    124    */
    125   static String getJarFileNameForVersion(SdkVersion version) {
    126     return new StringBuilder()
    127         .append(LIB_FOLDER)
    128         .append(File.separator)
    129         .append("android")
    130         .append(File.separator)
    131         .append(JAR_NAME)
    132         .append(version.getVersionName())
    133         .append(".jar").toString();
    134   }
    135 
    136   private static Set<GeneratedClassFile> generateMocks(
    137       SdkVersion version, JarFile jar)
    138       throws ClassNotFoundException, IOException, CannotCompileException {
    139     AndroidFrameworkMockGenerator mockGenerator = new AndroidFrameworkMockGenerator();
    140     List<Class<?>> classList = mockGenerator.getClassList(jar);
    141     Set<GeneratedClassFile> classes = new HashSet<GeneratedClassFile>();
    142     for (Class<?> clazz : classList) {
    143       classes.addAll(mockGenerator.createMocksForClass(clazz, version));
    144     }
    145     return classes;
    146   }
    147 
    148   private static JarFile getJarFile(SdkVersion version) throws IOException {
    149     File jarFile = new File(getJarFileNameForVersion(version)).getAbsoluteFile();
    150     System.out.println("Using Jar File: " + jarFile.getAbsolutePath());
    151     return new JarFile(jarFile);
    152   }
    153 
    154   public static void main(String[] args) {
    155     try {
    156       String outputFolderName = args[0];
    157       SdkVersion version = SdkVersion.getVersionFor(Integer.parseInt(args[1]));
    158       System.out.println("Generating files for " + version.getPackagePrefix());
    159 
    160       JarFile jar = getJarFile(version);
    161 
    162       Set<GeneratedClassFile> classes = generateMocks(version, jar);
    163       for (GeneratedClassFile clazz : classes) {
    164         FileUtils.saveClassToFolder(clazz, outputFolderName);
    165       }
    166     } catch (Exception e) {
    167       throw new RuntimeException("Internal error generating framework mocks", e);
    168     }
    169   }
    170 }
    171