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@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 */ getMocksForClass(Class<?> clazz)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 getPrebuiltClassesFor(Class<?> clazz)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 */ createMocksForClass(Class<?> clazz, SdkVersion version)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 */ getClassList(JarFile jar)101 List<Class<?>> getClassList(JarFile jar) throws ClassNotFoundException { 102 return getClassList(Collections.list(jar.entries())); 103 } 104 getClassList(Collection<JarEntry> jarEntries)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 */ jarEntryIsClassFile(JarEntry jarEntry)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 */ getJarFileNameForVersion(SdkVersion version)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 generateMocks( SdkVersion version, JarFile jar)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 getJarFile(SdkVersion version)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 main(String[] args)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