am 1c5bf7f1: Merge "Make Context.getClassLoader() work." into mnc-dev
* commit '1c5bf7f1ddc8c9c9844a1bc1a7e4ce53dcc5d5bd': Make Context.getClassLoader() work.
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -138,8 +138,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
|
||||
@@ -462,7 +463,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
|
||||
|
||||
@@ -275,7 +275,6 @@ public abstract class RenderAction<T extends RenderParams> extends FrameworkReso
|
||||
mContext.getRenderResources().setLogger(null);
|
||||
}
|
||||
ParserFactory.setParserFactory(null);
|
||||
|
||||
}
|
||||
|
||||
public static BridgeContext getCurrentContext() {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
//-----
|
||||
|
||||
/**
|
||||
@@ -286,5 +295,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);
|
||||
}};
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
public abstract Set<String> getExcludedClasses();
|
||||
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();
|
||||
|
||||
abstract class InjectMethodRunnable {
|
||||
public abstract void generateMethods(ClassVisitor cv);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user