summaryrefslogtreecommitdiffstats
path: root/src/com/google/android/testing/mocking/AndroidFrameworkMockGenerator.java
blob: d634c2431d0bc0d193ea5e996eead608b58588d1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
/*
 * Copyright 2010 Google Inc.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.google.android.testing.mocking;

import javassist.CannotCompileException;
import javassist.CtClass;
import javassist.NotFoundException;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * Mock Generator for Android Framework classes.
 * 
 * <p>
 * This class will pull pre-defined mocks for Android framework classes. This is
 * needed because a project might build against version X and then run against
 * version Y, where the specification of the class being mocked will have
 * changed, rendering the mock invalid.
 * 
 * @author swoodward@google.com (Stephen Woodward)
 */
public class AndroidFrameworkMockGenerator {
  private static final String LIB_FOLDER = "lib";
  private static final String JAR_NAME = "android_";

  /**
   * Returns a set of mock support classes for the specified Class for all versions of
   * the Android SDK. If the requested class is not part of the Android framework, then the class
   * will not be found and an exception will be thrown.
   * 
   * @param clazz the class to mock.
   * @return All available mock support classes (for all known Android SDKs) for
   *         the requested Class.
   */
  public List<GeneratedClassFile> getMocksForClass(Class<?> clazz) throws ClassNotFoundException,
      IOException {
    List<Class<?>> prebuiltClasses = getPrebuiltClassesFor(clazz);
    List<GeneratedClassFile> classList = new ArrayList<GeneratedClassFile>();
    for (Class<?> prebuiltClass : prebuiltClasses) {
      try {
        CtClass ctClass = AndroidMockGenerator.getClassPool().get(prebuiltClass.getName());
        classList.add(new GeneratedClassFile(ctClass.getName(), ctClass.toBytecode()));
      } catch (NotFoundException e) {
        throw new ClassNotFoundException("Missing class while fetching prebuilt mocks: "
            + prebuiltClass.getName(), e);
      } catch (CannotCompileException e) {
        throw new RuntimeException("Internal Error copying a prebuilt mock: "
            + prebuiltClass.getName(), e);
      }
    }
    return classList;
  }

  private List<Class<?>> getPrebuiltClassesFor(Class<?> clazz) throws ClassNotFoundException {
    List<Class<?>> classes = new ArrayList<Class<?>>();
    SdkVersion[] versions = SdkVersion.getAllVersions();
    for (SdkVersion sdkVersion : versions) {
      classes.add(Class.forName(FileUtils.getSubclassNameFor(clazz, sdkVersion)));
      classes.add(Class.forName(FileUtils.getInterfaceNameFor(clazz, sdkVersion)));
    }
    return classes;
  }

  /**
   * @return a List of {@link GeneratedClassFile} objects representing the mocks for the specified
   *         class for a single version of the Android SDK.
   */
  public List<GeneratedClassFile> createMocksForClass(Class<?> clazz, SdkVersion version)
      throws ClassNotFoundException, IOException, CannotCompileException {
    AndroidMockGenerator mockGenerator = new AndroidMockGenerator();
    List<GeneratedClassFile> mocks = new ArrayList<GeneratedClassFile>();
    mocks.addAll(mockGenerator.createMocksForClass(clazz, version));
    return mocks;
  }

  /**
   * @return A list of all class files inside the provided jar file.
   */
  List<Class<?>> getClassList(JarFile jar) throws ClassNotFoundException {
    return getClassList(Collections.list(jar.entries()));
  }

  List<Class<?>> getClassList(Collection<JarEntry> jarEntries) throws ClassNotFoundException {
    List<Class<?>> classList = new ArrayList<Class<?>>();
    for (JarEntry jarEntry : jarEntries) {
      if (jarEntryIsClassFile(jarEntry)) {
        classList.add(Class.forName(FileUtils.getClassNameFor(jarEntry.getName())));
      }
    }
    return classList;
  }

  /**
   * @return true if the provided JarEntry represents a class file.
   */
  boolean jarEntryIsClassFile(JarEntry jarEntry) {
    return jarEntry.getName().endsWith(".class");
  }

  /**
   * @return the Android framework jar file for the specified {@link SdkVersion}.
   */
  static String getJarFileNameForVersion(SdkVersion version) {
    return new StringBuilder()
        .append(LIB_FOLDER)
        .append(File.separator)
        .append("android")
        .append(File.separator)
        .append(JAR_NAME)
        .append(version.getVersionName())
        .append(".jar").toString();
  }

  private static Set<GeneratedClassFile> generateMocks(
      SdkVersion version, JarFile jar)
      throws ClassNotFoundException, IOException, CannotCompileException {
    AndroidFrameworkMockGenerator mockGenerator = new AndroidFrameworkMockGenerator();
    List<Class<?>> classList = mockGenerator.getClassList(jar);
    Set<GeneratedClassFile> classes = new HashSet<GeneratedClassFile>();
    for (Class<?> clazz : classList) {
      classes.addAll(mockGenerator.createMocksForClass(clazz, version));
    }
    return classes;
  }

  private static JarFile getJarFile(SdkVersion version) throws IOException {
    File jarFile = new File(getJarFileNameForVersion(version)).getAbsoluteFile();
    System.out.println("Using Jar File: " + jarFile.getAbsolutePath());
    return new JarFile(jarFile);
  }

  public static void main(String[] args) {
    try {
      String outputFolderName = args[0];
      SdkVersion version = SdkVersion.getVersionFor(Integer.parseInt(args[1]));
      System.out.println("Generating files for " + version.getPackagePrefix());

      JarFile jar = getJarFile(version);

      Set<GeneratedClassFile> classes = generateMocks(version, jar);
      for (GeneratedClassFile clazz : classes) {
        FileUtils.saveClassToFolder(clazz, outputFolderName);
      }
    } catch (Exception e) {
      throw new RuntimeException("Internal error generating framework mocks", e);
    }
  }
}