1 /*
2  * Copyright (C) 2019 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 org.junit.Test;
20 import org.objectweb.asm.ClassReader;
21 import org.objectweb.asm.ClassVisitor;
22 import org.objectweb.asm.Opcodes;
23 
24 import java.io.IOException;
25 import java.util.Arrays;
26 import java.util.Set;
27 import java.util.LinkedList;
28 import java.util.List;
29 import java.util.StringJoiner;
30 
31 import 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  */
37 class LoggingClassVisitor extends ClassVisitor {
38     List<String> mLog = new LinkedList<String>();
39 
LoggingClassVisitor()40     public LoggingClassVisitor() {
41         super(Main.ASM_VERSION);
42     }
43 
LoggingClassVisitor(ClassVisitor cv)44     public LoggingClassVisitor(ClassVisitor cv) {
45         super(Main.ASM_VERSION, cv);
46     }
47 
formatAccess(int access)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 
log(String method, String format, Object...args)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
visitOuterClass(String owner, String name, String desc)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
visitInnerClass(String name, String outerName, String innerName, int access)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
visit(int version, int access, String name, String signature, String superName, String[] interfaces)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 
111 class PackageProtectedClass {}
112 
113 public class PromoteClassClassAdapterTest {
114     private static class PrivateClass {}
115     private static class ClassWithPrivateInnerClass {
116         private class InnerPrivateClass {}
117     }
118 
119     @Test
testInnerClassPromotion()120     public void testInnerClassPromotion() throws IOException {
121         ClassReader reader = new ClassReader(PrivateClass.class.getName());
122         LoggingClassVisitor log = new LoggingClassVisitor();
123 
124         String rootClass = PromoteClassClassAdapterTest.class.getName();
125         PromoteClassClassAdapter adapter = new PromoteClassClassAdapter(log, Set.of(
126                 rootClass + "$PrivateClass",
127                 rootClass + "$ClassWithPrivateInnerClass$InnerPrivateClass"));
128         reader.accept(adapter, 0);
129         assertTrue(log.mLog.contains(
130                 "[visitInnerClass] - " +
131                         "name=com/android/tools/layoutlib/create" +
132                         "/PromoteClassClassAdapterTest$PrivateClass, " +
133                         "outerName=com/android/tools/layoutlib/create" +
134                         "/PromoteClassClassAdapterTest, innerName=PrivateClass, access=[public,static]"));
135 
136         // Test inner of inner class
137         log.mLog.clear();
138         reader = new ClassReader(ClassWithPrivateInnerClass.class.getName());
139         reader.accept(adapter, 0);
140 
141         assertTrue(log.mLog.contains("[visitInnerClass] - " +
142                 "name=com/android/tools/layoutlib/create" +
143                 "/PromoteClassClassAdapterTest$ClassWithPrivateInnerClass$InnerPrivateClass, " +
144                 "outerName=com/android/tools/layoutlib/create" +
145                 "/PromoteClassClassAdapterTest$ClassWithPrivateInnerClass, " +
146                 "innerName=InnerPrivateClass, access=[public]"));
147 
148     }
149 
150     @Test
testProtectedClassPromotion()151     public void testProtectedClassPromotion() throws IOException {
152         ClassReader reader = new ClassReader(PackageProtectedClass.class.getName());
153         LoggingClassVisitor log = new LoggingClassVisitor();
154 
155         PromoteClassClassAdapter adapter = new PromoteClassClassAdapter(log, Set.of(
156                 PackageProtectedClass.class.getName()));
157         reader.accept(adapter, 0);
158         assertTrue(log.mLog.contains("[visit] - version=55, access=[public], " +
159                 "name=com/android/tools/layoutlib/create/PackageProtectedClass, signature=null, " +
160                 "superName=java/lang/Object, interfaces=[]"));
161 
162     }
163 }