1 /* 2 * Copyright (C) 2008 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.dx.command.annotool; 18 19 import com.android.dx.cf.attrib.AttRuntimeInvisibleAnnotations; 20 import com.android.dx.cf.attrib.AttRuntimeVisibleAnnotations; 21 import com.android.dx.cf.attrib.BaseAnnotations; 22 import com.android.dx.cf.direct.ClassPathOpener; 23 import com.android.dx.cf.direct.DirectClassFile; 24 import com.android.dx.cf.direct.StdAttributeFactory; 25 import com.android.dx.cf.iface.Attribute; 26 import com.android.dx.cf.iface.AttributeList; 27 import com.android.dx.rop.annotation.Annotation; 28 import com.android.dx.util.ByteArray; 29 import java.io.File; 30 import java.lang.annotation.ElementType; 31 import java.util.HashSet; 32 33 /** 34 * Greps annotations on a set of class files and prints matching elements 35 * to stdout. What counts as a match and what should be printed is controlled 36 * by the {@code Main.Arguments} instance. 37 */ 38 class AnnotationLister { 39 /** 40 * The string name of the pseudo-class that 41 * contains package-wide annotations 42 */ 43 private static final String PACKAGE_INFO = "package-info"; 44 45 /** current match configuration */ 46 private final Main.Arguments args; 47 48 /** Set of classes whose inner classes should be considered matched */ 49 HashSet<String> matchInnerClassesOf = new HashSet<String>(); 50 51 /** set of packages whose classes should be considered matched */ 52 HashSet<String> matchPackages = new HashSet<String>(); 53 54 AnnotationLister (Main.Arguments args) { 55 this.args = args; 56 } 57 58 /** Processes based on configuration specified in constructor. */ 59 void process() { 60 for (String path : args.files) { 61 ClassPathOpener opener; 62 63 opener = new ClassPathOpener(path, true, 64 new ClassPathOpener.Consumer() { 65 @Override 66 public boolean processFileBytes(String name, long lastModified, byte[] bytes) { 67 if (!name.endsWith(".class")) { 68 return true; 69 } 70 71 ByteArray ba = new ByteArray(bytes); 72 DirectClassFile cf 73 = new DirectClassFile(ba, name, true); 74 75 cf.setAttributeFactory(StdAttributeFactory.THE_ONE); 76 AttributeList attributes = cf.getAttributes(); 77 Attribute att; 78 79 String cfClassName 80 = cf.getThisClass().getClassType().getClassName(); 81 82 if (cfClassName.endsWith(PACKAGE_INFO)) { 83 att = attributes.findFirst( 84 AttRuntimeInvisibleAnnotations.ATTRIBUTE_NAME); 85 86 for (;att != null; att = attributes.findNext(att)) { 87 BaseAnnotations ann = (BaseAnnotations)att; 88 visitPackageAnnotation(cf, ann); 89 } 90 91 att = attributes.findFirst( 92 AttRuntimeVisibleAnnotations.ATTRIBUTE_NAME); 93 94 for (;att != null; att = attributes.findNext(att)) { 95 BaseAnnotations ann = (BaseAnnotations)att; 96 visitPackageAnnotation(cf, ann); 97 } 98 } else if (isMatchingInnerClass(cfClassName) 99 || isMatchingPackage(cfClassName)) { 100 printMatch(cf); 101 } else { 102 att = attributes.findFirst( 103 AttRuntimeInvisibleAnnotations.ATTRIBUTE_NAME); 104 105 for (;att != null; att = attributes.findNext(att)) { 106 BaseAnnotations ann = (BaseAnnotations)att; 107 visitClassAnnotation(cf, ann); 108 } 109 110 att = attributes.findFirst( 111 AttRuntimeVisibleAnnotations.ATTRIBUTE_NAME); 112 113 for (;att != null; att = attributes.findNext(att)) { 114 BaseAnnotations ann = (BaseAnnotations)att; 115 visitClassAnnotation(cf, ann); 116 } 117 } 118 119 return true; 120 } 121 122 @Override 123 public void onException(Exception ex) { 124 throw new RuntimeException(ex); 125 } 126 127 @Override 128 public void onProcessArchiveStart(File file) { 129 130 } 131 132 }); 133 134 opener.process(); 135 } 136 } 137 138 /** 139 * Inspects a class annotation. 140 * 141 * @param cf {@code non-null;} class file 142 * @param ann {@code non-null;} annotation 143 */ 144 private void visitClassAnnotation(DirectClassFile cf, 145 BaseAnnotations ann) { 146 147 if (!args.eTypes.contains(ElementType.TYPE)) { 148 return; 149 } 150 151 for (Annotation anAnn : ann.getAnnotations().getAnnotations()) { 152 String annClassName 153 = anAnn.getType().getClassType().getClassName(); 154 if (args.aclass.equals(annClassName)) { 155 printMatch(cf); 156 } 157 } 158 } 159 160 /** 161 * Inspects a package annotation 162 * 163 * @param cf {@code non-null;} class file of "package-info" pseudo-class 164 * @param ann {@code non-null;} annotation 165 */ 166 private void visitPackageAnnotation( 167 DirectClassFile cf, BaseAnnotations ann) { 168 169 if (!args.eTypes.contains(ElementType.PACKAGE)) { 170 return; 171 } 172 173 String packageName = cf.getThisClass().getClassType().getClassName(); 174 175 int slashIndex = packageName.lastIndexOf('/'); 176 177 if (slashIndex == -1) { 178 packageName = ""; 179 } else { 180 packageName 181 = packageName.substring(0, slashIndex); 182 } 183 184 185 for (Annotation anAnn : ann.getAnnotations().getAnnotations()) { 186 String annClassName 187 = anAnn.getType().getClassType().getClassName(); 188 if (args.aclass.equals(annClassName)) { 189 printMatchPackage(packageName); 190 } 191 } 192 } 193 194 195 /** 196 * Prints, or schedules for printing, elements related to a 197 * matching package. 198 * 199 * @param packageName {@code non-null;} name of package 200 */ 201 private void printMatchPackage(String packageName) { 202 for (Main.PrintType pt : args.printTypes) { 203 switch (pt) { 204 case CLASS: 205 case INNERCLASS: 206 case METHOD: 207 matchPackages.add(packageName); 208 break; 209 case PACKAGE: 210 System.out.println(packageName.replace('/','.')); 211 break; 212 } 213 } 214 } 215 216 /** 217 * Prints, or schedules for printing, elements related to a matching 218 * class. 219 * 220 * @param cf {@code non-null;} matching class 221 */ 222 private void printMatch(DirectClassFile cf) { 223 for (Main.PrintType pt : args.printTypes) { 224 switch (pt) { 225 case CLASS: 226 String classname; 227 classname = 228 cf.getThisClass().getClassType().getClassName(); 229 classname = classname.replace('/','.'); 230 System.out.println(classname); 231 break; 232 case INNERCLASS: 233 matchInnerClassesOf.add( 234 cf.getThisClass().getClassType().getClassName()); 235 break; 236 case METHOD: 237 //TODO 238 break; 239 case PACKAGE: 240 break; 241 } 242 } 243 } 244 245 /** 246 * Checks to see if a specified class name should be considered a match 247 * due to previous matches. 248 * 249 * @param s {@code non-null;} class name 250 * @return true if this class should be considered a match 251 */ 252 private boolean isMatchingInnerClass(String s) { 253 int i; 254 255 while (0 < (i = s.lastIndexOf('$'))) { 256 s = s.substring(0, i); 257 if (matchInnerClassesOf.contains(s)) { 258 return true; 259 } 260 } 261 262 return false; 263 } 264 265 /** 266 * Checks to see if a specified package should be considered a match due 267 * to previous matches. 268 * 269 * @param s {@code non-null;} package name 270 * @return true if this package should be considered a match 271 */ 272 private boolean isMatchingPackage(String s) { 273 int slashIndex = s.lastIndexOf('/'); 274 275 String packageName; 276 if (slashIndex == -1) { 277 packageName = ""; 278 } else { 279 packageName 280 = s.substring(0, slashIndex); 281 } 282 283 return matchPackages.contains(packageName); 284 } 285 } 286