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 }