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