diff options
author | Deepanshu Gupta <deepanshu@google.com> | 2015-08-26 20:18:09 +0000 |
---|---|---|
committer | Android Git Automerger <android-git-automerger@android.com> | 2015-08-26 20:18:09 +0000 |
commit | aae9c9309ca5155124f740b10a40c68b09ed0ce6 (patch) | |
tree | a7d57b62c42fd346920156cd8bf1f79d2ba0e9fd /tools | |
parent | 522e67e4ac600fb19c18f55f0e51096af9ae37d3 (diff) | |
parent | 2f50403fe0a8a035757b047af00c7520fadb9015 (diff) | |
download | frameworks_base-aae9c9309ca5155124f740b10a40c68b09ed0ce6.zip frameworks_base-aae9c9309ca5155124f740b10a40c68b09ed0ce6.tar.gz frameworks_base-aae9c9309ca5155124f740b10a40c68b09ed0ce6.tar.bz2 |
am 2f50403f: am afedbc47: Make Context.getClassLoader() work. [DO NOT MERGE]
* commit '2f50403fe0a8a035757b047af00c7520fadb9015':
Make Context.getClassLoader() work. [DO NOT MERGE]
Diffstat (limited to 'tools')
11 files changed, 347 insertions, 31 deletions
diff --git a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java index 2e649c3..b495e26 100644 --- a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java +++ b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java @@ -53,9 +53,14 @@ public final class BridgeInflater extends LayoutInflater { */ private static final String[] sClassPrefixList = { "android.widget.", - "android.webkit." + "android.webkit.", + "android.app." }; + public static String[] getClassPrefixList() { + return sClassPrefixList; + } + protected BridgeInflater(LayoutInflater original, Context newContext) { super(original, newContext); newContext = getBaseContext(newContext); diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java index 2cbbeba..ac3a3ce 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java @@ -137,8 +137,9 @@ public final class BridgeContext extends Context { private final Stack<BridgeXmlBlockParser> mParserStack = new Stack<BridgeXmlBlockParser>(); private SharedPreferences mSharedPreferences; + private ClassLoader mClassLoader; - /** + /** * @param projectKey An Object identifying the project. This is used for the cache mechanism. * @param metrics the {@link DisplayMetrics}. * @param renderResources the configured resources (both framework and projects) for this @@ -461,7 +462,21 @@ public final class BridgeContext extends Context { @Override public ClassLoader getClassLoader() { - return this.getClass().getClassLoader(); + if (mClassLoader == null) { + mClassLoader = new ClassLoader(getClass().getClassLoader()) { + @Override + protected Class<?> findClass(String name) throws ClassNotFoundException { + for (String prefix : BridgeInflater.getClassPrefixList()) { + if (name.startsWith(prefix)) { + // These are framework classes and should not be loaded from the app. + throw new ClassNotFoundException(name + " not found"); + } + } + return BridgeContext.this.mLayoutlibCallback.findClass(name); + } + }; + } + return mClassLoader; } @Override diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java index 2b95488..f3a0d58 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java @@ -275,7 +275,6 @@ public abstract class RenderAction<T extends RenderParams> extends FrameworkReso mContext.getRenderResources().setLogger(null); } ParserFactory.setParserFactory(null); - } public static BridgeContext getCurrentContext() { diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java index aa51c46..c8b2b84 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java @@ -728,7 +728,7 @@ public class AsmAnalyzer { // Check if method needs to replaced by a call to a different method. - if (ReplaceMethodCallsAdapter.isReplacementNeeded(owner, name, desc)) { + if (ReplaceMethodCallsAdapter.isReplacementNeeded(owner, name, desc, mOwnerClass)) { mReplaceMethodCallClasses.add(mOwnerClass); } } diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java index bd6f070..3aa7cdf 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java @@ -72,6 +72,9 @@ public class AsmGenerator { /** FQCN Names of classes to refactor. All reference to old-FQCN will be updated to new-FQCN. * map old-FQCN => new-FQCN */ private final HashMap<String, String> mRefactorClasses; + /** Methods to inject. FQCN of class in which method should be injected => runnable that does + * the injection. */ + private final Map<String, ICreateInfo.InjectMethodRunnable> mInjectedMethodsMap; /** * Creates a new generator that can generate the output JAR with the stubbed classes. @@ -165,6 +168,8 @@ public class AsmGenerator { } returnTypes.add(binaryToInternalClassName(className)); } + + mInjectedMethodsMap = createInfo.getInjectedMethodsMap(); } /** @@ -337,7 +342,7 @@ public class AsmGenerator { ClassVisitor cv = cw; if (mReplaceMethodCallsClasses.contains(className)) { - cv = new ReplaceMethodCallsAdapter(cv); + cv = new ReplaceMethodCallsAdapter(cv, className); } cv = new RefactorClassAdapter(cv, mRefactorClasses); @@ -345,6 +350,10 @@ public class AsmGenerator { cv = new RenameClassAdapter(cv, className, newName); } + String binaryNewName = newName.replace('/', '.'); + if (mInjectedMethodsMap.keySet().contains(binaryNewName)) { + cv = new InjectMethodsAdapter(cv, mInjectedMethodsMap.get(binaryNewName)); + } cv = new TransformClassAdapter(mLog, mStubMethods, mDeleteReturns.get(className), newName, cv, stubNativesOnly); diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java index 8f50c5d..3ae0363 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java @@ -26,7 +26,9 @@ import com.android.tools.layoutlib.java.System_Delegate; import com.android.tools.layoutlib.java.UnsafeByteSequence; import java.util.Arrays; +import java.util.HashMap; import java.util.HashSet; +import java.util.Map; import java.util.Set; /** @@ -105,6 +107,7 @@ public final class CreateInfo implements ICreateInfo { return JAVA_PKG_CLASSES; } + @Override public Set<String> getExcludedClasses() { String[] refactoredClasses = getJavaPkgClasses(); int count = refactoredClasses.length / 2 + EXCLUDED_CLASSES.length; @@ -115,6 +118,12 @@ public final class CreateInfo implements ICreateInfo { excludedClasses.addAll(Arrays.asList(EXCLUDED_CLASSES)); return excludedClasses; } + + @Override + public Map<String, InjectMethodRunnable> getInjectedMethodsMap() { + return INJECTED_METHODS; + } + //----- /** @@ -287,5 +296,11 @@ public final class CreateInfo implements ICreateInfo { private final static String[] DELETE_RETURNS = new String[] { null }; // separator, for next class/methods list. + + private final static Map<String, InjectMethodRunnable> INJECTED_METHODS = + new HashMap<String, InjectMethodRunnable>(1) {{ + put("android.content.Context", + InjectMethodRunnables.CONTEXT_GET_FRAMEWORK_CLASS_LOADER); + }}; } diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java index e49a668..ac10639 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java @@ -16,6 +16,9 @@ package com.android.tools.layoutlib.create; +import org.objectweb.asm.ClassVisitor; + +import java.util.Map; import java.util.Set; /** @@ -27,33 +30,33 @@ public interface ICreateInfo { * Returns the list of class from layoutlib_create to inject in layoutlib. * The list can be empty but must not be null. */ - public abstract Class<?>[] getInjectedClasses(); + Class<?>[] getInjectedClasses(); /** * Returns the list of methods to rewrite as delegates. * The list can be empty but must not be null. */ - public abstract String[] getDelegateMethods(); + String[] getDelegateMethods(); /** * Returns the list of classes on which to delegate all native methods. * The list can be empty but must not be null. */ - public abstract String[] getDelegateClassNatives(); + String[] getDelegateClassNatives(); /** * Returns The list of methods to stub out. Each entry must be in the form * "package.package.OuterClass$InnerClass#MethodName". * The list can be empty but must not be null. */ - public abstract String[] getOverriddenMethods(); + String[] getOverriddenMethods(); /** * Returns the list of classes to rename, must be an even list: the binary FQCN * of class to replace followed by the new FQCN. * The list can be empty but must not be null. */ - public abstract String[] getRenamedClasses(); + String[] getRenamedClasses(); /** * Returns the list of classes for which the methods returning them should be deleted. @@ -62,7 +65,7 @@ public interface ICreateInfo { * the methods to delete. * The list can be empty but must not be null. */ - public abstract String[] getDeleteReturns(); + String[] getDeleteReturns(); /** * Returns the list of classes to refactor, must be an even list: the @@ -70,7 +73,18 @@ public interface ICreateInfo { * to the old class should be updated to the new class. * The list can be empty but must not be null. */ - public abstract String[] getJavaPkgClasses(); + String[] getJavaPkgClasses(); + + Set<String> getExcludedClasses(); + + /** + * Returns a map from binary FQCN className to {@link InjectMethodRunnable} which will be + * called to inject methods into a class. + * Can be empty but must not be null. + */ + Map<String, InjectMethodRunnable> getInjectedMethodsMap(); - public abstract Set<String> getExcludedClasses(); + abstract class InjectMethodRunnable { + public abstract void generateMethods(ClassVisitor cv); + } } diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/InjectMethodRunnables.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/InjectMethodRunnables.java new file mode 100644 index 0000000..39d46d7 --- /dev/null +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/InjectMethodRunnables.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * 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.android.tools.layoutlib.create; + +import com.android.tools.layoutlib.create.ICreateInfo.InjectMethodRunnable; + +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; + +import static org.objectweb.asm.Opcodes.ACC_PUBLIC; +import static org.objectweb.asm.Opcodes.ALOAD; +import static org.objectweb.asm.Opcodes.ARETURN; +import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL; + +public class InjectMethodRunnables { + public static final ICreateInfo.InjectMethodRunnable CONTEXT_GET_FRAMEWORK_CLASS_LOADER + = new InjectMethodRunnable() { + @Override + public void generateMethods(ClassVisitor cv) { + // generated by compiling the class: + // class foo { public ClassLoader getFrameworkClassLoader() { return getClass().getClassLoader(); } } + // and then running ASMifier on it: + // java -classpath asm-debug-all-5.0.2.jar:. org.objectweb.asm.util.ASMifier foo + MethodVisitor mv = cv.visitMethod(ACC_PUBLIC, "getFrameworkClassLoader", + "()Ljava/lang/ClassLoader;", null, null); + mv.visitCode(); + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Object", "getClass", + "()Ljava/lang/Class;"); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getClassLoader", + "()Ljava/lang/ClassLoader;"); + mv.visitInsn(ARETURN); + mv.visitMaxs(1, 1); + mv.visitEnd(); + // generated code ends. + } + }; +} diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/InjectMethodsAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/InjectMethodsAdapter.java new file mode 100644 index 0000000..ea2b9c9 --- /dev/null +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/InjectMethodsAdapter.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * 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.android.tools.layoutlib.create; + +import com.android.tools.layoutlib.create.ICreateInfo.InjectMethodRunnable; + +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Opcodes; + +/** + * Injects methods into some classes. + */ +public class InjectMethodsAdapter extends ClassVisitor { + + private final ICreateInfo.InjectMethodRunnable mRunnable; + + public InjectMethodsAdapter(ClassVisitor cv, InjectMethodRunnable runnable) { + super(Opcodes.ASM4, cv); + mRunnable = runnable; + } + + @Override + public void visitEnd() { + mRunnable.generateMethods(this); + super.visitEnd(); + } +} diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java index 384d8ca..4369148 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java @@ -62,14 +62,13 @@ public class ReplaceMethodCallsAdapter extends ClassVisitor { // Case 1: java.lang.System.arraycopy() METHOD_REPLACERS.add(new MethodReplacer() { @Override - public boolean isNeeded(String owner, String name, String desc) { + public boolean isNeeded(String owner, String name, String desc, String sourceClass) { return JAVA_LANG_SYSTEM.equals(owner) && "arraycopy".equals(name) && ARRAYCOPY_DESCRIPTORS.contains(desc); } @Override public void replace(MethodInformation mi) { - assert isNeeded(mi.owner, mi.name, mi.desc); mi.desc = "(Ljava/lang/Object;ILjava/lang/Object;II)V"; } }); @@ -81,14 +80,13 @@ public class ReplaceMethodCallsAdapter extends ClassVisitor { Type.getMethodDescriptor(STRING, Type.getType(Locale.class)); @Override - public boolean isNeeded(String owner, String name, String desc) { + public boolean isNeeded(String owner, String name, String desc, String sourceClass) { return JAVA_LOCALE_CLASS.equals(owner) && "()Ljava/lang/String;".equals(desc) && ("toLanguageTag".equals(name) || "getScript".equals(name)); } @Override public void replace(MethodInformation mi) { - assert isNeeded(mi.owner, mi.name, mi.desc); mi.opcode = Opcodes.INVOKESTATIC; mi.owner = ANDROID_LOCALE_CLASS; mi.desc = LOCALE_TO_STRING; @@ -103,7 +101,7 @@ public class ReplaceMethodCallsAdapter extends ClassVisitor { Type.getType(Locale.class), STRING); @Override - public boolean isNeeded(String owner, String name, String desc) { + public boolean isNeeded(String owner, String name, String desc, String sourceClass) { return JAVA_LOCALE_CLASS.equals(owner) && ("adjustLanguageCode".equals(name) && desc.equals(STRING_TO_STRING) || "forLanguageTag".equals(name) && desc.equals(STRING_TO_LOCALE)); @@ -111,7 +109,6 @@ public class ReplaceMethodCallsAdapter extends ClassVisitor { @Override public void replace(MethodInformation mi) { - assert isNeeded(mi.owner, mi.name, mi.desc); mi.owner = ANDROID_LOCALE_CLASS; } }); @@ -119,14 +116,13 @@ public class ReplaceMethodCallsAdapter extends ClassVisitor { // Case 4: java.lang.System.log?() METHOD_REPLACERS.add(new MethodReplacer() { @Override - public boolean isNeeded(String owner, String name, String desc) { + public boolean isNeeded(String owner, String name, String desc, String sourceClass) { return JAVA_LANG_SYSTEM.equals(owner) && name.length() == 4 && name.startsWith("log"); } @Override public void replace(MethodInformation mi) { - assert isNeeded(mi.owner, mi.name, mi.desc); assert mi.desc.equals("(Ljava/lang/String;Ljava/lang/Throwable;)V") || mi.desc.equals("(Ljava/lang/String;)V"); mi.name = "log"; @@ -142,7 +138,7 @@ public class ReplaceMethodCallsAdapter extends ClassVisitor { private final String LINKED_HASH_MAP = Type.getInternalName(LinkedHashMap.class); @Override - public boolean isNeeded(String owner, String name, String desc) { + public boolean isNeeded(String owner, String name, String desc, String sourceClass) { return LINKED_HASH_MAP.equals(owner) && "eldest".equals(name) && VOID_TO_MAP_ENTRY.equals(desc); @@ -150,26 +146,64 @@ public class ReplaceMethodCallsAdapter extends ClassVisitor { @Override public void replace(MethodInformation mi) { - assert isNeeded(mi.owner, mi.name, mi.desc); mi.opcode = Opcodes.INVOKESTATIC; mi.owner = Type.getInternalName(LinkedHashMap_Delegate.class); mi.desc = Type.getMethodDescriptor( Type.getType(Map.Entry.class), Type.getType(LinkedHashMap.class)); } }); + + // Case 6: android.content.Context.getClassLoader() in LayoutInflater + METHOD_REPLACERS.add(new MethodReplacer() { + // When LayoutInflater asks for a class loader, we must return the class loader that + // cannot return app's custom views/classes. This is so that in case of any failure + // or exception when instantiating the views, the IDE can replace it with a mock view + // and have proper error handling. However, if a custom view asks for the class + // loader, we must return a class loader that can find app's custom views as well. + // Thus, we rewrite the call to get class loader in LayoutInflater to + // getFrameworkClassLoader and inject a new method in Context. This leaves the normal + // method: Context.getClassLoader() free to be used by the apps. + private final String VOID_TO_CLASS_LOADER = + Type.getMethodDescriptor(Type.getType(ClassLoader.class)); + + @Override + public boolean isNeeded(String owner, String name, String desc, String sourceClass) { + return owner.equals("android/content/Context") && + sourceClass.equals("android/view/LayoutInflater") && + name.equals("getClassLoader") && + desc.equals(VOID_TO_CLASS_LOADER); + } + + @Override + public void replace(MethodInformation mi) { + mi.name = "getFrameworkClassLoader"; + } + }); } - public static boolean isReplacementNeeded(String owner, String name, String desc) { + /** + * If a method some.package.Class.Method(args) is called from some.other.Class, + * @param owner some/package/Class + * @param name Method + * @param desc (args)returnType + * @param sourceClass some/other/Class + * @return if the method invocation needs to be replaced by some other class. + */ + public static boolean isReplacementNeeded(String owner, String name, String desc, + String sourceClass) { for (MethodReplacer replacer : METHOD_REPLACERS) { - if (replacer.isNeeded(owner, name, desc)) { + if (replacer.isNeeded(owner, name, desc, sourceClass)) { return true; } } return false; } - public ReplaceMethodCallsAdapter(ClassVisitor cv) { + private final String mOriginalClassName; + + public ReplaceMethodCallsAdapter(ClassVisitor cv, String originalClassName) { super(Opcodes.ASM4, cv); + mOriginalClassName = originalClassName; } @Override @@ -187,7 +221,7 @@ public class ReplaceMethodCallsAdapter extends ClassVisitor { @Override public void visitMethodInsn(int opcode, String owner, String name, String desc) { for (MethodReplacer replacer : METHOD_REPLACERS) { - if (replacer.isNeeded(owner, name, desc)) { + if (replacer.isNeeded(owner, name, desc, mOriginalClassName)) { MethodInformation mi = new MethodInformation(opcode, owner, name, desc); replacer.replace(mi); opcode = mi.opcode; @@ -216,13 +250,12 @@ public class ReplaceMethodCallsAdapter extends ClassVisitor { } private interface MethodReplacer { - public boolean isNeeded(String owner, String name, String desc); + boolean isNeeded(String owner, String name, String desc, String sourceClass); /** * Updates the MethodInformation with the new values of the method attributes - * opcode, owner, name and desc. - * */ - public void replace(MethodInformation mi); + void replace(MethodInformation mi); } } diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java index cf91386..2c21470 100644 --- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java +++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java @@ -19,7 +19,9 @@ package com.android.tools.layoutlib.create; import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import org.junit.After; @@ -32,13 +34,17 @@ import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; +import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; @@ -130,6 +136,11 @@ public class AsmGeneratorTest { // methods deleted from their return type. return new String[0]; } + + @Override + public Map<String, InjectMethodRunnable> getInjectedMethodsMap() { + return new HashMap<String, InjectMethodRunnable>(0); + } }; AsmGenerator agen = new AsmGenerator(mLog, mOsDestJar, ci); @@ -200,6 +211,11 @@ public class AsmGeneratorTest { // methods deleted from their return type. return new String[0]; } + + @Override + public Map<String, InjectMethodRunnable> getInjectedMethodsMap() { + return new HashMap<String, InjectMethodRunnable>(0); + } }; AsmGenerator agen = new AsmGenerator(mLog, mOsDestJar, ci); @@ -278,6 +294,11 @@ public class AsmGeneratorTest { // methods deleted from their return type. return new String[0]; } + + @Override + public Map<String, InjectMethodRunnable> getInjectedMethodsMap() { + return new HashMap<String, InjectMethodRunnable>(0); + } }; AsmGenerator agen = new AsmGenerator(mLog, mOsDestJar, ci); @@ -303,6 +324,118 @@ public class AsmGeneratorTest { filesFound.keySet().toArray()); } + @Test + public void testMethodInjection() throws IOException, LogAbortException, + ClassNotFoundException, IllegalAccessException, InstantiationException, + NoSuchMethodException, InvocationTargetException { + ICreateInfo ci = new ICreateInfo() { + @Override + public Class<?>[] getInjectedClasses() { + return new Class<?>[0]; + } + + @Override + public String[] getDelegateMethods() { + return new String[0]; + } + + @Override + public String[] getDelegateClassNatives() { + return new String[0]; + } + + @Override + public String[] getOverriddenMethods() { + // methods to force override + return new String[0]; + } + + @Override + public String[] getRenamedClasses() { + // classes to rename (so that we can replace them) + return new String[0]; + } + + @Override + public String[] getJavaPkgClasses() { + // classes to refactor (so that we can replace them) + return new String[0]; + } + + @Override + public Set<String> getExcludedClasses() { + return new HashSet<String>(0); + } + + @Override + public String[] getDeleteReturns() { + // methods deleted from their return type. + return new String[0]; + } + + @Override + public Map<String, InjectMethodRunnable> getInjectedMethodsMap() { + HashMap<String, InjectMethodRunnable> map = + new HashMap<String, InjectMethodRunnable>(1); + map.put("mock_android.util.EmptyArray", + InjectMethodRunnables.CONTEXT_GET_FRAMEWORK_CLASS_LOADER); + return map; + } + }; + + AsmGenerator agen = new AsmGenerator(mLog, mOsDestJar, ci); + AsmAnalyzer aa = new AsmAnalyzer(mLog, mOsJarPath, agen, + null, // derived from + new String[] { // include classes + "**" + }, + ci.getExcludedClasses(), + new String[] { /* include files */ + "mock_android/data/data*" + }); + aa.analyze(); + agen.generate(); + Map<String, ClassReader> output = new TreeMap<String, ClassReader>(); + Map<String, InputStream> filesFound = new TreeMap<String, InputStream>(); + parseZip(mOsDestJar, output, filesFound); + final String modifiedClass = "mock_android.util.EmptyArray"; + final String modifiedClassPath = modifiedClass.replace('.', '/').concat(".class"); + ZipFile zipFile = new ZipFile(mOsDestJar); + ZipEntry entry = zipFile.getEntry(modifiedClassPath); + assertNotNull(entry); + final byte[] bytes; + final InputStream inputStream = zipFile.getInputStream(entry); + try { + bytes = getByteArray(inputStream); + } finally { + inputStream.close(); + } + ClassLoader classLoader = new ClassLoader(getClass().getClassLoader()) { + @Override + protected Class<?> findClass(String name) throws ClassNotFoundException { + if (name.equals(modifiedClass)) { + return defineClass(null, bytes, 0, bytes.length); + } + throw new ClassNotFoundException(name + " not found."); + } + }; + Class<?> emptyArrayClass = classLoader.loadClass(modifiedClass); + Object emptyArrayInstance = emptyArrayClass.newInstance(); + Method method = emptyArrayClass.getMethod("getFrameworkClassLoader"); + Object cl = method.invoke(emptyArrayInstance); + assertEquals(classLoader, cl); + } + + private static byte[] getByteArray(InputStream stream) throws IOException { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int read; + while ((read = stream.read(buffer, 0, buffer.length)) > -1) { + bos.write(buffer, 0, read); + } + return bos.toByteArray(); + } + private void parseZip(String jarPath, Map<String, ClassReader> classes, Map<String, InputStream> filesFound) throws IOException { |