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