19066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project/* 29066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Copyright (C) 2008 The Android Open Source Project 39066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * 49066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Licensed under the Apache License, Version 2.0 (the "License"); 59066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * you may not use this file except in compliance with the License. 69066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * You may obtain a copy of the License at 79066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * 89066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * http://www.apache.org/licenses/LICENSE-2.0 99066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * 109066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Unless required by applicable law or agreed to in writing, software 119066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * distributed under the License is distributed on an "AS IS" BASIS, 129066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 139066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * See the License for the specific language governing permissions and 149066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * limitations under the License. 159066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */ 169066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 179066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectpackage com.android.tools.layoutlib.create; 189066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 199066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport org.objectweb.asm.ClassReader; 209066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport org.objectweb.asm.ClassVisitor; 219066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport org.objectweb.asm.ClassWriter; 229066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 2358037a4404e639d28695a0f1c17053b53b96b970Deepanshu Guptaimport java.io.ByteArrayOutputStream; 249066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.io.FileOutputStream; 259066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.io.IOException; 269066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.io.InputStream; 279066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.util.Arrays; 289066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.util.HashMap; 299066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.util.HashSet; 309066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.util.Map; 31bc101806249eb883f89c4a770a8c27f9ac315837Raphaelimport java.util.Map.Entry; 329066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.util.Set; 339066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.util.TreeMap; 349066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.util.jar.JarEntry; 359066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.util.jar.JarOutputStream; 369066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 379066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project/** 389066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Class that generates a new JAR from a list of classes, some of which are to be kept as-is 399066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * and some of which are to be stubbed partially or totally. 409066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */ 419066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectpublic class AsmGenerator { 429066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 439066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project /** Output logger. */ 449066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project private final Log mLog; 459066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project /** The path of the destination JAR to create. */ 469066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project private final String mOsDestJar; 479066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project /** List of classes to inject in the final JAR from _this_ archive. */ 489066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project private final Class<?>[] mInjectClasses; 499066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project /** The set of methods to stub out. */ 509066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project private final Set<String> mStubMethods; 519066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project /** All classes to output as-is, except if they have native methods. */ 529066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project private Map<String, ClassReader> mKeep; 539066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project /** All dependencies that must be completely stubbed. */ 549066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project private Map<String, ClassReader> mDeps; 5558037a4404e639d28695a0f1c17053b53b96b970Deepanshu Gupta /** All files that are to be copied as-is. */ 5658037a4404e639d28695a0f1c17053b53b96b970Deepanshu Gupta private Map<String, InputStream> mCopyFiles; 571160e6d2f7018117b0c29a7e2adba9ece36faec1Deepanshu Gupta /** All classes where certain method calls need to be rewritten. */ 581160e6d2f7018117b0c29a7e2adba9ece36faec1Deepanshu Gupta private Set<String> mReplaceMethodCallsClasses; 599066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project /** Counter of number of classes renamed during transform. */ 609066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project private int mRenameCount; 619066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project /** FQCN Names of the classes to rename: map old-FQCN => new-FQCN */ 629066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project private final HashMap<String, String> mRenameClasses; 639066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project /** FQCN Names of "old" classes that were NOT renamed. This starts with the full list of 649066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * old-FQCN to rename and they get erased as they get renamed. At the end, classes still 659066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * left here are not in the code base anymore and thus were not renamed. */ 669066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project private HashSet<String> mClassesNotRenamed; 67bc101806249eb883f89c4a770a8c27f9ac315837Raphael /** A map { FQCN => set { list of return types to delete from the FQCN } }. */ 689066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project private HashMap<String, Set<String>> mDeleteReturns; 69bc101806249eb883f89c4a770a8c27f9ac315837Raphael /** A map { FQCN => set { method names } } of methods to rewrite as delegates. 70bc101806249eb883f89c4a770a8c27f9ac315837Raphael * The special name {@link DelegateClassAdapter#ALL_NATIVES} can be used as in internal set. */ 71bc101806249eb883f89c4a770a8c27f9ac315837Raphael private final HashMap<String, Set<String>> mDelegateMethods; 721cf5df38f4bdafa1beb2674ca548ad6d9650766bDeepanshu Gupta /** FQCN Names of classes to refactor. All reference to old-FQCN will be updated to new-FQCN. 731cf5df38f4bdafa1beb2674ca548ad6d9650766bDeepanshu Gupta * map old-FQCN => new-FQCN */ 741cf5df38f4bdafa1beb2674ca548ad6d9650766bDeepanshu Gupta private final HashMap<String, String> mRefactorClasses; 759066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 769066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project /** 779066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Creates a new generator that can generate the output JAR with the stubbed classes. 78bc101806249eb883f89c4a770a8c27f9ac315837Raphael * 799066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * @param log Output logger. 809066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * @param osDestJar The path of the destination JAR to create. 81bc101806249eb883f89c4a770a8c27f9ac315837Raphael * @param createInfo Creation parameters. Must not be null. 829066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */ 83bc101806249eb883f89c4a770a8c27f9ac315837Raphael public AsmGenerator(Log log, String osDestJar, ICreateInfo createInfo) { 849066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project mLog = log; 859066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project mOsDestJar = osDestJar; 86bc101806249eb883f89c4a770a8c27f9ac315837Raphael mInjectClasses = createInfo.getInjectedClasses(); 87bc101806249eb883f89c4a770a8c27f9ac315837Raphael mStubMethods = new HashSet<String>(Arrays.asList(createInfo.getOverriddenMethods())); 88bc101806249eb883f89c4a770a8c27f9ac315837Raphael 89bc101806249eb883f89c4a770a8c27f9ac315837Raphael // Create the map/set of methods to change to delegates 90bc101806249eb883f89c4a770a8c27f9ac315837Raphael mDelegateMethods = new HashMap<String, Set<String>>(); 91bc101806249eb883f89c4a770a8c27f9ac315837Raphael for (String signature : createInfo.getDelegateMethods()) { 92bc101806249eb883f89c4a770a8c27f9ac315837Raphael int pos = signature.indexOf('#'); 93bc101806249eb883f89c4a770a8c27f9ac315837Raphael if (pos <= 0 || pos >= signature.length() - 1) { 94bc101806249eb883f89c4a770a8c27f9ac315837Raphael continue; 95bc101806249eb883f89c4a770a8c27f9ac315837Raphael } 96bc101806249eb883f89c4a770a8c27f9ac315837Raphael String className = binaryToInternalClassName(signature.substring(0, pos)); 97bc101806249eb883f89c4a770a8c27f9ac315837Raphael String methodName = signature.substring(pos + 1); 98bc101806249eb883f89c4a770a8c27f9ac315837Raphael Set<String> methods = mDelegateMethods.get(className); 99bc101806249eb883f89c4a770a8c27f9ac315837Raphael if (methods == null) { 100bc101806249eb883f89c4a770a8c27f9ac315837Raphael methods = new HashSet<String>(); 101bc101806249eb883f89c4a770a8c27f9ac315837Raphael mDelegateMethods.put(className, methods); 102bc101806249eb883f89c4a770a8c27f9ac315837Raphael } 103bc101806249eb883f89c4a770a8c27f9ac315837Raphael methods.add(methodName); 104bc101806249eb883f89c4a770a8c27f9ac315837Raphael } 105bc101806249eb883f89c4a770a8c27f9ac315837Raphael for (String className : createInfo.getDelegateClassNatives()) { 106280372e2c7db128254b5f65395ea843ef285729eRaphael className = binaryToInternalClassName(className); 107bc101806249eb883f89c4a770a8c27f9ac315837Raphael Set<String> methods = mDelegateMethods.get(className); 108bc101806249eb883f89c4a770a8c27f9ac315837Raphael if (methods == null) { 109bc101806249eb883f89c4a770a8c27f9ac315837Raphael methods = new HashSet<String>(); 110bc101806249eb883f89c4a770a8c27f9ac315837Raphael mDelegateMethods.put(className, methods); 111bc101806249eb883f89c4a770a8c27f9ac315837Raphael } 112bc101806249eb883f89c4a770a8c27f9ac315837Raphael methods.add(DelegateClassAdapter.ALL_NATIVES); 113bc101806249eb883f89c4a770a8c27f9ac315837Raphael } 1149066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 1159066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // Create the map of classes to rename. 1169066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project mRenameClasses = new HashMap<String, String>(); 1179066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project mClassesNotRenamed = new HashSet<String>(); 118bc101806249eb883f89c4a770a8c27f9ac315837Raphael String[] renameClasses = createInfo.getRenamedClasses(); 119bc101806249eb883f89c4a770a8c27f9ac315837Raphael int n = renameClasses.length; 1209066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project for (int i = 0; i < n; i += 2) { 1219066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project assert i + 1 < n; 1229066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // The ASM class names uses "/" separators, whereas regular FQCN use "." 1239066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project String oldFqcn = binaryToInternalClassName(renameClasses[i]); 1249066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project String newFqcn = binaryToInternalClassName(renameClasses[i + 1]); 1259066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project mRenameClasses.put(oldFqcn, newFqcn); 1269066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project mClassesNotRenamed.add(oldFqcn); 1279066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 128bc101806249eb883f89c4a770a8c27f9ac315837Raphael 1291cf5df38f4bdafa1beb2674ca548ad6d9650766bDeepanshu Gupta // Create a map of classes to be refactored. 1301cf5df38f4bdafa1beb2674ca548ad6d9650766bDeepanshu Gupta mRefactorClasses = new HashMap<String, String>(); 1311cf5df38f4bdafa1beb2674ca548ad6d9650766bDeepanshu Gupta String[] refactorClasses = createInfo.getJavaPkgClasses(); 1321cf5df38f4bdafa1beb2674ca548ad6d9650766bDeepanshu Gupta n = refactorClasses.length; 1331cf5df38f4bdafa1beb2674ca548ad6d9650766bDeepanshu Gupta for (int i = 0; i < n; i += 2) { 1341cf5df38f4bdafa1beb2674ca548ad6d9650766bDeepanshu Gupta assert i + 1 < n; 1351cf5df38f4bdafa1beb2674ca548ad6d9650766bDeepanshu Gupta String oldFqcn = binaryToInternalClassName(refactorClasses[i]); 1361cf5df38f4bdafa1beb2674ca548ad6d9650766bDeepanshu Gupta String newFqcn = binaryToInternalClassName(refactorClasses[i + 1]); 1371160e6d2f7018117b0c29a7e2adba9ece36faec1Deepanshu Gupta mRefactorClasses.put(oldFqcn, newFqcn); 1381cf5df38f4bdafa1beb2674ca548ad6d9650766bDeepanshu Gupta } 1391cf5df38f4bdafa1beb2674ca548ad6d9650766bDeepanshu Gupta 1409066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // create the map of renamed class -> return type of method to delete. 1419066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project mDeleteReturns = new HashMap<String, Set<String>>(); 142bc101806249eb883f89c4a770a8c27f9ac315837Raphael String[] deleteReturns = createInfo.getDeleteReturns(); 143bc101806249eb883f89c4a770a8c27f9ac315837Raphael Set<String> returnTypes = null; 144bc101806249eb883f89c4a770a8c27f9ac315837Raphael String renamedClass = null; 145bc101806249eb883f89c4a770a8c27f9ac315837Raphael for (String className : deleteReturns) { 146bc101806249eb883f89c4a770a8c27f9ac315837Raphael // if we reach the end of a section, add it to the main map 147bc101806249eb883f89c4a770a8c27f9ac315837Raphael if (className == null) { 148bc101806249eb883f89c4a770a8c27f9ac315837Raphael if (returnTypes != null) { 149bc101806249eb883f89c4a770a8c27f9ac315837Raphael mDeleteReturns.put(renamedClass, returnTypes); 1509066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 151bc101806249eb883f89c4a770a8c27f9ac315837Raphael 152bc101806249eb883f89c4a770a8c27f9ac315837Raphael renamedClass = null; 153bc101806249eb883f89c4a770a8c27f9ac315837Raphael continue; 1549066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 155bc101806249eb883f89c4a770a8c27f9ac315837Raphael 156bc101806249eb883f89c4a770a8c27f9ac315837Raphael // if the renamed class is null, this is the beginning of a section 157bc101806249eb883f89c4a770a8c27f9ac315837Raphael if (renamedClass == null) { 158bc101806249eb883f89c4a770a8c27f9ac315837Raphael renamedClass = binaryToInternalClassName(className); 159bc101806249eb883f89c4a770a8c27f9ac315837Raphael continue; 160bc101806249eb883f89c4a770a8c27f9ac315837Raphael } 161bc101806249eb883f89c4a770a8c27f9ac315837Raphael 162bc101806249eb883f89c4a770a8c27f9ac315837Raphael // just a standard return type, we add it to the list. 163bc101806249eb883f89c4a770a8c27f9ac315837Raphael if (returnTypes == null) { 164bc101806249eb883f89c4a770a8c27f9ac315837Raphael returnTypes = new HashSet<String>(); 165bc101806249eb883f89c4a770a8c27f9ac315837Raphael } 166bc101806249eb883f89c4a770a8c27f9ac315837Raphael returnTypes.add(binaryToInternalClassName(className)); 1679066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 1689066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 169bc101806249eb883f89c4a770a8c27f9ac315837Raphael 1709066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project /** 1719066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Returns the list of classes that have not been renamed yet. 1729066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * <p/> 1739066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * The names are "internal class names" rather than FQCN, i.e. they use "/" instead "." 1749066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * as package separators. 1759066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */ 1769066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public Set<String> getClassesNotRenamed() { 1779066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project return mClassesNotRenamed; 1789066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 1799066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 1809066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project /** 1819066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Utility that returns the internal ASM class name from a fully qualified binary class 1829066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * name. E.g. it returns android/view/View from android.view.View. 1839066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */ 1849066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project String binaryToInternalClassName(String className) { 1859066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if (className == null) { 1869066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project return null; 1879066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } else { 1889066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project return className.replace('.', '/'); 1899066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 1909066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 1919066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 1929066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project /** Sets the map of classes to output as-is, except if they have native methods */ 1939066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public void setKeep(Map<String, ClassReader> keep) { 1949066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project mKeep = keep; 1959066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 1969066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 1979066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project /** Sets the map of dependencies that must be completely stubbed */ 1989066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public void setDeps(Map<String, ClassReader> deps) { 1999066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project mDeps = deps; 2009066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 201bc101806249eb883f89c4a770a8c27f9ac315837Raphael 20258037a4404e639d28695a0f1c17053b53b96b970Deepanshu Gupta /** Sets the map of files to output as-is. */ 20358037a4404e639d28695a0f1c17053b53b96b970Deepanshu Gupta public void setCopyFiles(Map<String, InputStream> copyFiles) { 20458037a4404e639d28695a0f1c17053b53b96b970Deepanshu Gupta mCopyFiles = copyFiles; 20558037a4404e639d28695a0f1c17053b53b96b970Deepanshu Gupta } 20658037a4404e639d28695a0f1c17053b53b96b970Deepanshu Gupta 2071160e6d2f7018117b0c29a7e2adba9ece36faec1Deepanshu Gupta public void setRewriteMethodCallClasses(Set<String> rewriteMethodCallClasses) { 2081160e6d2f7018117b0c29a7e2adba9ece36faec1Deepanshu Gupta mReplaceMethodCallsClasses = rewriteMethodCallClasses; 20958037a4404e639d28695a0f1c17053b53b96b970Deepanshu Gupta } 21058037a4404e639d28695a0f1c17053b53b96b970Deepanshu Gupta 2119066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project /** Generates the final JAR */ 2121160e6d2f7018117b0c29a7e2adba9ece36faec1Deepanshu Gupta public void generate() throws IOException { 2139066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project TreeMap<String, byte[]> all = new TreeMap<String, byte[]>(); 214bc101806249eb883f89c4a770a8c27f9ac315837Raphael 2159066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project for (Class<?> clazz : mInjectClasses) { 2169066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project String name = classToEntryPath(clazz); 2179066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project InputStream is = ClassLoader.getSystemResourceAsStream(name); 2189066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project ClassReader cr = new ClassReader(is); 219c5a58437e62698f34abde93ec785c4cbe36aec2dDeepanshu Gupta byte[] b = transform(cr, true); 2209066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project name = classNameToEntryPath(transformName(cr.getClassName())); 2219066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project all.put(name, b); 2229066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 223bc101806249eb883f89c4a770a8c27f9ac315837Raphael 2249066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project for (Entry<String, ClassReader> entry : mDeps.entrySet()) { 2259066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project ClassReader cr = entry.getValue(); 226c5a58437e62698f34abde93ec785c4cbe36aec2dDeepanshu Gupta byte[] b = transform(cr, true); 2279066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project String name = classNameToEntryPath(transformName(cr.getClassName())); 2289066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project all.put(name, b); 2299066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 2309066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 2319066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project for (Entry<String, ClassReader> entry : mKeep.entrySet()) { 2329066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project ClassReader cr = entry.getValue(); 233c5a58437e62698f34abde93ec785c4cbe36aec2dDeepanshu Gupta byte[] b = transform(cr, true); 2349066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project String name = classNameToEntryPath(transformName(cr.getClassName())); 2359066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project all.put(name, b); 2369066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 2379066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 23858037a4404e639d28695a0f1c17053b53b96b970Deepanshu Gupta for (Entry<String, InputStream> entry : mCopyFiles.entrySet()) { 23958037a4404e639d28695a0f1c17053b53b96b970Deepanshu Gupta try { 24058037a4404e639d28695a0f1c17053b53b96b970Deepanshu Gupta byte[] b = inputStreamToByteArray(entry.getValue()); 24158037a4404e639d28695a0f1c17053b53b96b970Deepanshu Gupta all.put(entry.getKey(), b); 24258037a4404e639d28695a0f1c17053b53b96b970Deepanshu Gupta } catch (IOException e) { 24358037a4404e639d28695a0f1c17053b53b96b970Deepanshu Gupta // Ignore. 24458037a4404e639d28695a0f1c17053b53b96b970Deepanshu Gupta } 24558037a4404e639d28695a0f1c17053b53b96b970Deepanshu Gupta 24658037a4404e639d28695a0f1c17053b53b96b970Deepanshu Gupta } 2479066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project mLog.info("# deps classes: %d", mDeps.size()); 2489066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project mLog.info("# keep classes: %d", mKeep.size()); 2499066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project mLog.info("# renamed : %d", mRenameCount); 2509066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 2519066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project createJar(new FileOutputStream(mOsDestJar), all); 2529066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project mLog.info("Created JAR file %s", mOsDestJar); 2539066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 2549066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 2559066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project /** 2569066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Writes the JAR file. 257bc101806249eb883f89c4a770a8c27f9ac315837Raphael * 258bc101806249eb883f89c4a770a8c27f9ac315837Raphael * @param outStream The file output stream were to write the JAR. 2599066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * @param all The map of all classes to output. 2609066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * @throws IOException if an I/O error has occurred 2619066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */ 2629066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project void createJar(FileOutputStream outStream, Map<String,byte[]> all) throws IOException { 2639066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project JarOutputStream jar = new JarOutputStream(outStream); 2649066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project for (Entry<String, byte[]> entry : all.entrySet()) { 2659066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project String name = entry.getKey(); 2669066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project JarEntry jar_entry = new JarEntry(name); 2679066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project jar.putNextEntry(jar_entry); 2689066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project jar.write(entry.getValue()); 2699066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project jar.closeEntry(); 2709066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 2719066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project jar.flush(); 2729066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project jar.close(); 2739066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 2749066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 2759066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project /** 2769066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Utility method that converts a fully qualified java name into a JAR entry path 2779066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * e.g. for the input "android.view.View" it returns "android/view/View.class" 2789066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */ 2799066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project String classNameToEntryPath(String className) { 2809066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project return className.replaceAll("\\.", "/").concat(".class"); 2819066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 282bc101806249eb883f89c4a770a8c27f9ac315837Raphael 2839066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project /** 2849066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Utility method to get the JAR entry path from a Class name. 285c5a58437e62698f34abde93ec785c4cbe36aec2dDeepanshu Gupta * e.g. it returns something like "com/foo/OuterClass$InnerClass1$InnerClass2.class" 2869066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */ 2879066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project private String classToEntryPath(Class<?> clazz) { 2889066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project String name = ""; 2899066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project Class<?> parent; 2909066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project while ((parent = clazz.getEnclosingClass()) != null) { 2919066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project name = "$" + clazz.getSimpleName() + name; 2929066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project clazz = parent; 2939066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 294bc101806249eb883f89c4a770a8c27f9ac315837Raphael return classNameToEntryPath(clazz.getCanonicalName() + name); 2959066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 2969066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 2979066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project /** 2989066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Transforms a class. 2999066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * <p/> 3009066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * There are 3 kind of transformations: 301bc101806249eb883f89c4a770a8c27f9ac315837Raphael * 3029066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * 1- For "mock" dependencies classes, we want to remove all code from methods and replace 3039066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * by a stub. Native methods must be implemented with this stub too. Abstract methods are 3049066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * left intact. Modified classes must be overridable (non-private, non-final). 3059066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Native methods must be made non-final, non-private. 306bc101806249eb883f89c4a770a8c27f9ac315837Raphael * 3079066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * 2- For "keep" classes, we want to rewrite all native methods as indicated above. 3089066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * If a class has native methods, it must also be made non-private, non-final. 309bc101806249eb883f89c4a770a8c27f9ac315837Raphael * 3109066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Note that unfortunately static methods cannot be changed to non-static (since static and 3119066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * non-static are invoked differently.) 3129066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */ 3139066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project byte[] transform(ClassReader cr, boolean stubNativesOnly) { 3149066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 3159066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project boolean hasNativeMethods = hasNativeMethods(cr); 316bc101806249eb883f89c4a770a8c27f9ac315837Raphael 317bc101806249eb883f89c4a770a8c27f9ac315837Raphael // Get the class name, as an internal name (e.g. com/android/SomeClass$InnerClass) 3189066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project String className = cr.getClassName(); 319bc101806249eb883f89c4a770a8c27f9ac315837Raphael 3209066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project String newName = transformName(className); 3219066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // transformName returns its input argument if there's no need to rename the class 3221160e6d2f7018117b0c29a7e2adba9ece36faec1Deepanshu Gupta if (!newName.equals(className)) { 3239066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project mRenameCount++; 3249066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // This class is being renamed, so remove it from the list of classes not renamed. 3259066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project mClassesNotRenamed.remove(className); 3269066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 3279066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 3289066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project mLog.debug("Transform %s%s%s%s", className, 3291160e6d2f7018117b0c29a7e2adba9ece36faec1Deepanshu Gupta newName.equals(className) ? "" : " (renamed to " + newName + ")", 3309066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project hasNativeMethods ? " -- has natives" : "", 3319066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project stubNativesOnly ? " -- stub natives only" : ""); 3329066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 3339066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // Rewrite the new class from scratch, without reusing the constant pool from the 3349066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // original class reader. 3359066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); 336bc101806249eb883f89c4a770a8c27f9ac315837Raphael 3371160e6d2f7018117b0c29a7e2adba9ece36faec1Deepanshu Gupta ClassVisitor cv = cw; 3381160e6d2f7018117b0c29a7e2adba9ece36faec1Deepanshu Gupta 3391160e6d2f7018117b0c29a7e2adba9ece36faec1Deepanshu Gupta if (mReplaceMethodCallsClasses.contains(className)) { 3401160e6d2f7018117b0c29a7e2adba9ece36faec1Deepanshu Gupta cv = new ReplaceMethodCallsAdapter(cv); 3411160e6d2f7018117b0c29a7e2adba9ece36faec1Deepanshu Gupta } 3421160e6d2f7018117b0c29a7e2adba9ece36faec1Deepanshu Gupta 3431160e6d2f7018117b0c29a7e2adba9ece36faec1Deepanshu Gupta cv = new RefactorClassAdapter(cv, mRefactorClasses); 3441160e6d2f7018117b0c29a7e2adba9ece36faec1Deepanshu Gupta if (!newName.equals(className)) { 3451cf5df38f4bdafa1beb2674ca548ad6d9650766bDeepanshu Gupta cv = new RenameClassAdapter(cv, className, newName); 3469066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 347bc101806249eb883f89c4a770a8c27f9ac315837Raphael 348c5a58437e62698f34abde93ec785c4cbe36aec2dDeepanshu Gupta cv = new TransformClassAdapter(mLog, mStubMethods, mDeleteReturns.get(className), 349c5a58437e62698f34abde93ec785c4cbe36aec2dDeepanshu Gupta newName, cv, stubNativesOnly); 350e2e8ba904b306f11dfc5b71e00ab9ac013358cd7Raphael 351bc101806249eb883f89c4a770a8c27f9ac315837Raphael Set<String> delegateMethods = mDelegateMethods.get(className); 352bc101806249eb883f89c4a770a8c27f9ac315837Raphael if (delegateMethods != null && !delegateMethods.isEmpty()) { 353bc101806249eb883f89c4a770a8c27f9ac315837Raphael // If delegateMethods only contains one entry ALL_NATIVES and the class is 354bc101806249eb883f89c4a770a8c27f9ac315837Raphael // known to have no native methods, just skip this step. 355bc101806249eb883f89c4a770a8c27f9ac315837Raphael if (hasNativeMethods || 356bc101806249eb883f89c4a770a8c27f9ac315837Raphael !(delegateMethods.size() == 1 && 357bc101806249eb883f89c4a770a8c27f9ac315837Raphael delegateMethods.contains(DelegateClassAdapter.ALL_NATIVES))) { 358e2e8ba904b306f11dfc5b71e00ab9ac013358cd7Raphael cv = new DelegateClassAdapter(mLog, cv, className, delegateMethods); 359bc101806249eb883f89c4a770a8c27f9ac315837Raphael } 360bc101806249eb883f89c4a770a8c27f9ac315837Raphael } 361bc101806249eb883f89c4a770a8c27f9ac315837Raphael 362c5a58437e62698f34abde93ec785c4cbe36aec2dDeepanshu Gupta cr.accept(cv, 0); 3639066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project return cw.toByteArray(); 3649066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 3659066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 3669066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project /** 3679066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Should this class be renamed, this returns the new name. Otherwise it returns the 3689066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * original name. 3699066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * 3709066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * @param className The internal ASM name of the class that may have to be renamed 3719066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * @return A new transformed name or the original input argument. 3729066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */ 3739066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project String transformName(String className) { 3749066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project String newName = mRenameClasses.get(className); 3759066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if (newName != null) { 3769066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project return newName; 3779066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 3789066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project int pos = className.indexOf('$'); 3799066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if (pos > 0) { 3809066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // Is this an inner class of a renamed class? 3819066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project String base = className.substring(0, pos); 3829066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project newName = mRenameClasses.get(base); 3839066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if (newName != null) { 3849066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project return newName + className.substring(pos); 3859066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 3869066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 387bc101806249eb883f89c4a770a8c27f9ac315837Raphael 3889066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project return className; 3899066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 3909066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 3919066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project /** 3929066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Returns true if a class has any native methods. 3939066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */ 3949066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project boolean hasNativeMethods(ClassReader cr) { 3959066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project ClassHasNativeVisitor cv = new ClassHasNativeVisitor(); 396c5a58437e62698f34abde93ec785c4cbe36aec2dDeepanshu Gupta cr.accept(cv, 0); 3979066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project return cv.hasNativeMethods(); 3989066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 3999066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 40058037a4404e639d28695a0f1c17053b53b96b970Deepanshu Gupta private byte[] inputStreamToByteArray(InputStream is) throws IOException { 40158037a4404e639d28695a0f1c17053b53b96b970Deepanshu Gupta ByteArrayOutputStream buffer = new ByteArrayOutputStream(); 40258037a4404e639d28695a0f1c17053b53b96b970Deepanshu Gupta byte[] data = new byte[8192]; // 8KB 40358037a4404e639d28695a0f1c17053b53b96b970Deepanshu Gupta int n; 40458037a4404e639d28695a0f1c17053b53b96b970Deepanshu Gupta while ((n = is.read(data, 0, data.length)) != -1) { 40558037a4404e639d28695a0f1c17053b53b96b970Deepanshu Gupta buffer.write(data, 0, n); 40658037a4404e639d28695a0f1c17053b53b96b970Deepanshu Gupta } 40758037a4404e639d28695a0f1c17053b53b96b970Deepanshu Gupta return buffer.toByteArray(); 40858037a4404e639d28695a0f1c17053b53b96b970Deepanshu Gupta } 4099066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project} 410