1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.tools.layoutlib.create; 18 19 import com.android.tools.layoutlib.create.dataclass.StubClass; 20 21 import org.junit.Assert; 22 import org.junit.Test; 23 import org.objectweb.asm.ClassReader; 24 import org.objectweb.asm.ClassVisitor; 25 import org.objectweb.asm.ClassWriter; 26 import org.objectweb.asm.MethodVisitor; 27 import org.objectweb.asm.Opcodes; 28 import org.objectweb.asm.Type; 29 30 import java.lang.reflect.Method; 31 import java.util.function.BiPredicate; 32 import java.util.function.Consumer; 33 34 import static org.junit.Assert.*; 35 36 public class StubMethodAdapterTest { 37 38 private static final String STUB_CLASS_NAME = StubClass.class.getName(); 39 40 /** 41 * Load a dummy class, stub one of its method and ensure that the modified class works as 42 * intended. 43 */ 44 @Test testBoolean()45 public void testBoolean() throws Exception { 46 final String methodName = "returnTrue"; 47 // First don't change the method and assert that it returns true 48 testBoolean((name, type) -> false, Assert::assertTrue, methodName); 49 // Change the method now and assert that it returns false. 50 testBoolean((name, type) -> methodName.equals(name) && 51 Type.BOOLEAN_TYPE.equals(type.getReturnType()), Assert::assertFalse, methodName); 52 } 53 54 /** 55 * @param methodPredicate tests if the method should be replaced 56 */ testBoolean(BiPredicate<String, Type> methodPredicate, Consumer<Boolean> assertion, String methodName)57 private void testBoolean(BiPredicate<String, Type> methodPredicate, Consumer<Boolean> assertion, 58 String methodName) throws Exception { 59 ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS); 60 // Always rename the class to avoid conflict with the original class. 61 String newClassName = STUB_CLASS_NAME + '_'; 62 new ClassReader(STUB_CLASS_NAME).accept( 63 new ClassAdapter(newClassName, writer, methodPredicate), 0); 64 MyClassLoader myClassLoader = new MyClassLoader(newClassName, writer.toByteArray()); 65 Class<?> aClass = myClassLoader.loadClass(newClassName); 66 assertTrue("StubClass not loaded by the classloader. Likely a bug in the test.", 67 myClassLoader.findClassCalled); 68 Method method = aClass.getMethod(methodName); 69 Object o = aClass.newInstance(); 70 assertion.accept((Boolean) method.invoke(o)); 71 } 72 73 private static class ClassAdapter extends ClassVisitor { 74 75 private final String mClassName; 76 private final BiPredicate<String, Type> mMethodPredicate; 77 ClassAdapter(String className, ClassVisitor cv, BiPredicate<String, Type> methodPredicate)78 private ClassAdapter(String className, ClassVisitor cv, 79 BiPredicate<String, Type> methodPredicate) { 80 super(Main.ASM_VERSION, cv); 81 mClassName = className.replace('.', '/'); 82 mMethodPredicate = methodPredicate; 83 } 84 85 @Override visit(int version, int access, String name, String signature, String superName, String[] interfaces)86 public void visit(int version, int access, String name, String signature, String superName, 87 String[] interfaces) { 88 super.visit(version, access, mClassName, signature, superName, 89 interfaces); 90 } 91 92 @Override visitMethod(int access, String name, String desc, String signature, String[] exceptions)93 public MethodVisitor visitMethod(int access, String name, String desc, String signature, 94 String[] exceptions) { 95 // Copied partly from 96 // com.android.tools.layoutlib.create.DelegateClassAdapter.visitMethod() 97 // but not generating the _Original method. 98 boolean isStatic = (access & Opcodes.ACC_STATIC) != 0; 99 boolean isNative = (access & Opcodes.ACC_NATIVE) != 0; 100 MethodVisitor originalMethod = 101 super.visitMethod(access, name, desc, signature, exceptions); 102 Type descriptor = Type.getMethodType(desc); 103 if (mMethodPredicate.test(name, descriptor)) { 104 String methodSignature = mClassName + "#" + name; 105 String invokeSignature = methodSignature + desc; 106 return new StubMethodAdapter(originalMethod, name, descriptor.getReturnType(), 107 invokeSignature, isStatic, isNative); 108 } 109 return originalMethod; 110 } 111 } 112 113 private static class MyClassLoader extends ClassLoader { 114 private final String mName; 115 private final byte[] mBytes; 116 private boolean findClassCalled; 117 MyClassLoader(String name, byte[] bytes)118 private MyClassLoader(String name, byte[] bytes) { 119 mName = name; 120 mBytes = bytes; 121 } 122 123 @Override findClass(String name)124 protected Class<?> findClass(String name) throws ClassNotFoundException { 125 if (name.equals(mName)) { 126 findClassCalled = true; 127 return defineClass(name, mBytes, 0, mBytes.length); 128 } 129 return super.findClass(name); 130 } 131 } 132 } 133