AnnotationLister.java revision dc86cd9edc8b80953c8b698a83cdaebf6825d798
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 * The string name of the pseudo-class that 42 * contains package-wide annotations 43 */ 44 private static final String PACKAGE_INFO = "package-info"; 45 46 /** current match configuration */ 47 private final Main.Arguments args; 48 49 /** Set of classes whose inner classes should be considered matched */ 50 HashSet<String> matchInnerClassesOf = new HashSet<String>(); 51 52 /** set of packages whose classes should be considered matched */ 53 HashSet<String> matchPackages = new HashSet<String>(); 54 55 AnnotationLister (Main.Arguments args) { 56 this.args = args; 57 } 58 59 /** Processes based on configuration specified in constructor. */ 60 void process() { 61 for (String path : args.files) { 62 ClassPathOpener opener; 63 64 opener = new ClassPathOpener(path, true, 65 new ClassPathOpener.Consumer() { 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 public void onException(Exception ex) { 123 throw new RuntimeException(ex); 124 } 125 126 public void onProcessArchiveStart(File file) { 127 128 } 129 130 }); 131 132 opener.process(); 133 } 134 } 135 136 /** 137 * Inspects a class annotation. 138 * 139 * @param cf {@code non-null;} class file 140 * @param ann {@code non-null;} annotation 141 */ 142 private void visitClassAnnotation(DirectClassFile cf, 143 BaseAnnotations ann) { 144 145 if (!args.eTypes.contains(ElementType.TYPE)) { 146 return; 147 } 148 149 for (Annotation anAnn : ann.getAnnotations().getAnnotations()) { 150 String annClassName 151 = anAnn.getType().getClassType().getClassName(); 152 if (args.aclass.equals(annClassName)) { 153 printMatch(cf); 154 } 155 } 156 } 157 158 /** 159 * Inspects a package annotation 160 * 161 * @param cf {@code non-null;} class file of "package-info" pseudo-class 162 * @param ann {@code non-null;} annotation 163 */ 164 private void visitPackageAnnotation( 165 DirectClassFile cf, BaseAnnotations ann) { 166 167 if (!args.eTypes.contains(ElementType.PACKAGE)) { 168 return; 169 } 170 171 String packageName = cf.getThisClass().getClassType().getClassName(); 172 173 int slashIndex = packageName.lastIndexOf('/'); 174 175 if (slashIndex == -1) { 176 packageName = ""; 177 } else { 178 packageName 179 = packageName.substring(0, slashIndex); 180 } 181 182 183 for (Annotation anAnn : ann.getAnnotations().getAnnotations()) { 184 String annClassName 185 = anAnn.getType().getClassType().getClassName(); 186 if (args.aclass.equals(annClassName)) { 187 printMatchPackage(packageName); 188 } 189 } 190 } 191 192 193 /** 194 * Prints, or schedules for printing, elements related to a 195 * matching package. 196 * 197 * @param packageName {@code non-null;} name of package 198 */ 199 private void printMatchPackage(String packageName) { 200 for (Main.PrintType pt : args.printTypes) { 201 switch (pt) { 202 case CLASS: 203 case INNERCLASS: 204 case METHOD: 205 matchPackages.add(packageName); 206 break; 207 case PACKAGE: 208 System.out.println(packageName.replace('/','.')); 209 break; 210 } 211 } 212 } 213 214 /** 215 * Prints, or schedules for printing, elements related to a matching 216 * class. 217 * 218 * @param cf {@code non-null;} matching class 219 */ 220 private void printMatch(DirectClassFile cf) { 221 for (Main.PrintType pt : args.printTypes) { 222 switch (pt) { 223 case CLASS: 224 String classname; 225 classname = 226 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