/* * Copyright (C) 2008 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.dx.command.annotool; import com.android.dx.cf.direct.ClassPathOpener; import com.android.dx.cf.direct.DirectClassFile; import com.android.dx.cf.direct.StdAttributeFactory; import com.android.dx.cf.iface.AttributeList; import com.android.dx.cf.iface.Attribute; import com.android.dx.cf.attrib.AttRuntimeInvisibleAnnotations; import com.android.dx.cf.attrib.BaseAnnotations; import com.android.dx.cf.attrib.AttRuntimeVisibleAnnotations; import com.android.dx.util.ByteArray; import com.android.dx.rop.annotation.Annotation; import java.io.File; import java.lang.annotation.ElementType; import java.util.HashSet; /** * Greps annotations on a set of class files and prints matching elements * to stdout. What counts as a match and what should be printed is controlled * by the {@code Main.Arguments} instance. */ class AnnotationLister { /** * The string name of the pseudo-class that * contains package-wide annotations */ private static final String PACKAGE_INFO = "package-info"; /** current match configuration */ private final Main.Arguments args; /** Set of classes whose inner classes should be considered matched */ HashSet<String> matchInnerClassesOf = new HashSet<String>(); /** set of packages whose classes should be considered matched */ HashSet<String> matchPackages = new HashSet<String>(); AnnotationLister (Main.Arguments args) { this.args = args; } /** Processes based on configuration specified in constructor. */ void process() { for (String path : args.files) { ClassPathOpener opener; opener = new ClassPathOpener(path, true, new ClassPathOpener.Consumer() { public boolean processFileBytes(String name, byte[] bytes) { if (!name.endsWith(".class")) { return true; } ByteArray ba = new ByteArray(bytes); DirectClassFile cf = new DirectClassFile(ba, name, true); cf.setAttributeFactory(StdAttributeFactory.THE_ONE); AttributeList attributes = cf.getAttributes(); Attribute att; String cfClassName = cf.getThisClass().getClassType().getClassName(); if (cfClassName.endsWith(PACKAGE_INFO)) { att = attributes.findFirst( AttRuntimeInvisibleAnnotations.ATTRIBUTE_NAME); for (;att != null; att = attributes.findNext(att)) { BaseAnnotations ann = (BaseAnnotations)att; visitPackageAnnotation(cf, ann); } att = attributes.findFirst( AttRuntimeVisibleAnnotations.ATTRIBUTE_NAME); for (;att != null; att = attributes.findNext(att)) { BaseAnnotations ann = (BaseAnnotations)att; visitPackageAnnotation(cf, ann); } } else if (isMatchingInnerClass(cfClassName) || isMatchingPackage(cfClassName)) { printMatch(cf); } else { att = attributes.findFirst( AttRuntimeInvisibleAnnotations.ATTRIBUTE_NAME); for (;att != null; att = attributes.findNext(att)) { BaseAnnotations ann = (BaseAnnotations)att; visitClassAnnotation(cf, ann); } att = attributes.findFirst( AttRuntimeVisibleAnnotations.ATTRIBUTE_NAME); for (;att != null; att = attributes.findNext(att)) { BaseAnnotations ann = (BaseAnnotations)att; visitClassAnnotation(cf, ann); } } return true; } public void onException(Exception ex) { throw new RuntimeException(ex); } public void onProcessArchiveStart(File file) { } }); opener.process(); } } /** * Inspects a class annotation. * * @param cf {@code non-null;} class file * @param ann {@code non-null;} annotation */ private void visitClassAnnotation(DirectClassFile cf, BaseAnnotations ann) { if (!args.eTypes.contains(ElementType.TYPE)) { return; } for (Annotation anAnn : ann.getAnnotations().getAnnotations()) { String annClassName = anAnn.getType().getClassType().getClassName(); if (args.aclass.equals(annClassName)) { printMatch(cf); } } } /** * Inspects a package annotation * * @param cf {@code non-null;} class file of "package-info" pseudo-class * @param ann {@code non-null;} annotation */ private void visitPackageAnnotation( DirectClassFile cf, BaseAnnotations ann) { if (!args.eTypes.contains(ElementType.PACKAGE)) { return; } String packageName = cf.getThisClass().getClassType().getClassName(); int slashIndex = packageName.lastIndexOf('/'); if (slashIndex == -1) { packageName = ""; } else { packageName = packageName.substring(0, slashIndex); } for (Annotation anAnn : ann.getAnnotations().getAnnotations()) { String annClassName = anAnn.getType().getClassType().getClassName(); if (args.aclass.equals(annClassName)) { printMatchPackage(packageName); } } } /** * Prints, or schedules for printing, elements related to a * matching package. * * @param packageName {@code non-null;} name of package */ private void printMatchPackage(String packageName) { for (Main.PrintType pt : args.printTypes) { switch (pt) { case CLASS: case INNERCLASS: case METHOD: matchPackages.add(packageName); break; case PACKAGE: System.out.println(packageName.replace('/','.')); break; } } } /** * Prints, or schedules for printing, elements related to a matching * class. * * @param cf {@code non-null;} matching class */ private void printMatch(DirectClassFile cf) { for (Main.PrintType pt : args.printTypes) { switch (pt) { case CLASS: String classname; classname = cf.getThisClass().getClassType().getClassName(); classname = classname.replace('/','.'); System.out.println(classname); break; case INNERCLASS: matchInnerClassesOf.add( cf.getThisClass().getClassType().getClassName()); break; case METHOD: //TODO break; case PACKAGE: break; } } } /** * Checks to see if a specified class name should be considered a match * due to previous matches. * * @param s {@code non-null;} class name * @return true if this class should be considered a match */ private boolean isMatchingInnerClass(String s) { int i; while (0 < (i = s.lastIndexOf('$'))) { s = s.substring(0, i); if (matchInnerClassesOf.contains(s)) { return true; } } return false; } /** * Checks to see if a specified package should be considered a match due * to previous matches. * * @param s {@code non-null;} package name * @return true if this package should be considered a match */ private boolean isMatchingPackage(String s) { int slashIndex = s.lastIndexOf('/'); String packageName; if (slashIndex == -1) { packageName = ""; } else { packageName = s.substring(0, slashIndex); } return matchPackages.contains(packageName); } }