blob: d3fb27c4256e2a98d53dd3c3851b2b047030e00e [file] [log] [blame]
/*
* 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);
}
}