1/*
2 * Copyright (C) 2017 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
17package com.android.tools.layoutlib.create;
18
19import org.junit.Test;
20import org.objectweb.asm.ClassReader;
21import org.objectweb.asm.ClassVisitor;
22import org.objectweb.asm.Opcodes;
23
24import java.io.IOException;
25import java.util.Arrays;
26import java.util.HashSet;
27import java.util.LinkedList;
28import java.util.List;
29import java.util.StringJoiner;
30
31import static org.junit.Assert.assertTrue;
32
33/**
34 * {@link ClassVisitor} that logs all the calls to the different visit methods so they can be later
35 * inspected.
36 */
37class LoggingClassVisitor extends ClassVisitor {
38    List<String> mLog = new LinkedList<String>();
39
40    public LoggingClassVisitor() {
41        super(Main.ASM_VERSION);
42    }
43
44    public LoggingClassVisitor(ClassVisitor cv) {
45        super(Main.ASM_VERSION, cv);
46    }
47
48    private static String formatAccess(int access) {
49        StringJoiner modifiers = new StringJoiner(",");
50
51        if ((access & Opcodes.ACC_PUBLIC) != 0) {
52            modifiers.add("public");
53        }
54        if ((access & Opcodes.ACC_PRIVATE) != 0) {
55            modifiers.add("private");
56        }
57        if ((access & Opcodes.ACC_PROTECTED) != 0) {
58            modifiers.add("protected");
59        }
60        if ((access & Opcodes.ACC_STATIC) != 0) {
61            modifiers.add("static");
62        }
63        if ((access & Opcodes.ACC_FINAL) != 0) {
64            modifiers.add("static");
65        }
66
67        return "[" + modifiers.toString() + "]";
68    }
69
70    private void log(String method, String format, Object...args) {
71        mLog.add(
72                String.format("[%s] - %s", method, String.format(format, (Object[]) args))
73        );
74    }
75
76    @Override
77    public void visitOuterClass(String owner, String name, String desc) {
78        log(
79                "visitOuterClass",
80                "owner=%s, name=%s, desc=%s",
81                owner, name, desc
82        );
83
84        super.visitOuterClass(owner, name, desc);
85    }
86
87    @Override
88    public void visitInnerClass(String name, String outerName, String innerName, int access) {
89        log(
90                "visitInnerClass",
91                "name=%s, outerName=%s, innerName=%s, access=%s",
92                name, outerName, innerName, formatAccess(access)
93        );
94
95        super.visitInnerClass(name, outerName, innerName, access);
96    }
97
98    @Override
99    public void visit(int version, int access, String name, String signature, String superName,
100            String[] interfaces) {
101        log(
102                "visit",
103                "version=%d, access=%s, name=%s, signature=%s, superName=%s, interfaces=%s",
104                version, formatAccess(access), name, signature, superName, Arrays.toString(interfaces)
105        );
106
107        super.visit(version, access, name, signature, superName, interfaces);
108    }
109}
110
111class PackageProtectedClass {}
112
113public class PromoteClassClassAdapterTest {
114    private static class PrivateClass {}
115    private static class ClassWithPrivateInnerClass {
116        private class InnerPrivateClass {}
117    }
118
119    @Test
120    public void testInnerClassPromotion() throws IOException {
121        ClassReader reader = new ClassReader(PrivateClass.class.getName());
122        LoggingClassVisitor log = new LoggingClassVisitor();
123
124        PromoteClassClassAdapter adapter = new PromoteClassClassAdapter(log, new HashSet<String>() {
125            {
126                add("com.android.tools.layoutlib.create.PromoteClassClassAdapterTest$PrivateClass");
127                add("com.android.tools.layoutlib.create" +
128                        ".PromoteClassClassAdapterTest$ClassWithPrivateInnerClass$InnerPrivateClass");
129            }
130        });
131        reader.accept(adapter, 0);
132        assertTrue(log.mLog.contains(
133                "[visitInnerClass] - " +
134                        "name=com/android/tools/layoutlib/create" +
135                        "/PromoteClassClassAdapterTest$PrivateClass, " +
136                        "outerName=com/android/tools/layoutlib/create" +
137                        "/PromoteClassClassAdapterTest, innerName=PrivateClass, access=[public,static]"));
138
139        // Test inner of inner class
140        log.mLog.clear();
141        reader = new ClassReader(ClassWithPrivateInnerClass.class.getName());
142        reader.accept(adapter, 0);
143
144        assertTrue(log.mLog.contains("[visitInnerClass] - " +
145                "name=com/android/tools/layoutlib/create" +
146                "/PromoteClassClassAdapterTest$ClassWithPrivateInnerClass$InnerPrivateClass, " +
147                "outerName=com/android/tools/layoutlib/create" +
148                "/PromoteClassClassAdapterTest$ClassWithPrivateInnerClass, " +
149                "innerName=InnerPrivateClass, access=[public]"));
150
151    }
152
153    @Test
154    public void testProtectedClassPromotion() throws IOException {
155        ClassReader reader = new ClassReader(PackageProtectedClass.class.getName());
156        LoggingClassVisitor log = new LoggingClassVisitor();
157
158        PromoteClassClassAdapter adapter = new PromoteClassClassAdapter(log, new HashSet<String>() {
159            {
160                add("com.android.tools.layoutlib.create.PackageProtectedClass");
161            }
162        });
163
164        reader.accept(adapter, 0);
165        assertTrue(log.mLog.contains("[visit] - version=52, access=[public], " +
166                "name=com/android/tools/layoutlib/create/PackageProtectedClass, signature=null, " +
167                "superName=java/lang/Object, interfaces=[]"));
168
169    }
170}