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