package org.audit4j.core.extra.scannotation;
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.Serializable;
import java.net.URL;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.ClassFile;
import javassist.bytecode.FieldInfo;
import javassist.bytecode.MethodInfo;
import javassist.bytecode.ParameterAnnotationsAttribute;
import javassist.bytecode.annotation.Annotation;
public class AnnotationDB implements Serializable {
protected Map<String, Set<String>> annotationIndex = new HashMap<String, Set<String>>();
protected Map<String, Set<String>> implementsIndex = new HashMap<String, Set<String>>();
protected Map<String, Set<String>> classIndex = new HashMap<String, Set<String>>();
protected transient boolean scanClassAnnotations = true;
protected transient boolean scanMethodAnnotations = true;
protected transient boolean scanParameterAnnotations = true;
protected transient boolean scanFieldAnnotations = true;
protected transient String[] ignoredPackages = { "javax", "java", "sun", "com.sun", "javassist" };
protected transient String[] scanPackages = null;
protected transient boolean ignoreBadURLs = false;
public String[] getScanPackages() {
return scanPackages;
}
/**
* Set explicit packages to scan. Set to null to enable ignore list.
*
* @param scanPackages
* packages to scan or null
*/
public void setScanPackages(String[] scanPackages) {
this.scanPackages = scanPackages;
}
public String[] getIgnoredPackages() {
return ignoredPackages;
}
/**
* Override/overwrite any ignored packages
*
* @param ignoredPackages
* cannot be null
*/
public void setIgnoredPackages(String[] ignoredPackages) {
this.ignoredPackages = ignoredPackages;
}
public void addIgnoredPackages(String... ignored) {
String[] tmp = new String[ignoredPackages.length + ignored.length];
int i = 0;
for (String ign : ignoredPackages)
tmp[i++] = ign;
for (String ign : ignored)
tmp[i++] = ign;
this.ignoredPackages = tmp;
}
private boolean ignoreScan(String intf) {
if (scanPackages != null) {
for (String scan : scanPackages) {
// do not ignore if on packages to scan list
if (intf.startsWith(scan + ".")) {
return false;
}
}
return true; // didn't match whitelist, ignore
}
for (String ignored : ignoredPackages) {
if (intf.startsWith(ignored + ".")) {
return true;
} else {
// System.out.println("NOT IGNORING: " + intf);
}
}
return false;
}
/**
* returns a map keyed by the fully qualified string name of a annotation
* class. The Set returne is a list of classes that use that annotation
* somehow.
*/
public Map<String, Set<String>> getAnnotationIndex() {
return annotationIndex;
}
/**
* returns a map keyed by the list of classes scanned. The value set
* returned is a list of annotations used by that class.
*/
public Map<String, Set<String>> getClassIndex() {
return classIndex;
}
/**
* Whether or not you want AnnotationDB to scan for class level annotations
*
* @param scanClassAnnotations
*/
public void setScanClassAnnotations(boolean scanClassAnnotations) {
this.scanClassAnnotations = scanClassAnnotations;
}
/**
* Wheter or not you want AnnotationDB to scan for method level annotations
*
* @param scanMethodAnnotations
*/
public void setScanMethodAnnotations(boolean scanMethodAnnotations) {
this.scanMethodAnnotations = scanMethodAnnotations;
}
/**
* Whether or not you want AnnotationDB to scan for parameter level
* annotations
*
* @param scanParameterAnnotations
*/
public void setScanParameterAnnotations(boolean scanParameterAnnotations) {
this.scanParameterAnnotations = scanParameterAnnotations;
}
/**
* Whether or not you want AnnotationDB to scan for parameter level
* annotations
*
* @param scanFieldAnnotations
*/
public void setScanFieldAnnotations(boolean scanFieldAnnotations) {
this.scanFieldAnnotations = scanFieldAnnotations;
}
/**
* Whether or not you want AnnotationDB to ignore bad URLs passed to
* scanArchives. Default is to throw an IOException.
*
* @param ignoreBadURLs
*/
public void setIgnoreBadURLs(boolean ignoreBadURLs) {
this.ignoreBadURLs = ignoreBadURLs;
}
/**
* Scan a url that represents an "archive" this is a classpath directory or
* jar file
*
* @param urls
* variable list of URLs to scan as archives
* @throws IOException
*/
public void scanArchives(URL... urls) throws IOException {
for (URL url : urls) {
Filter filter = new Filter() {
@Override
public boolean accepts(String filename) {
if (filename.endsWith(".class")) {
if (filename.startsWith("/") || filename.startsWith("\\"))
filename = filename.substring(1);
if (!ignoreScan(filename.replace('/', '.')))
return true;
// System.out.println("IGNORED: " + filename);
}
return false;
}
};
try {
StreamIterator it = IteratorFactory.create(url, filter);
InputStream stream;
while ((stream = it.next()) != null)
scanClass(stream);
} catch (IOException e) {
if (ignoreBadURLs)
continue;
else
throw e;
}
}
}
/**
* Parse a .class file for annotations
*
* @param bits
* input stream pointing to .class file bits
* @throws IOException
*/
public void scanClass(InputStream bits) throws IOException {
DataInputStream dstream = new DataInputStream(new BufferedInputStream(bits));
ClassFile cf = null;
try {
cf = new ClassFile(dstream);
classIndex.put(cf.getName(), new HashSet<String>());
if (scanClassAnnotations)
scanClass(cf);
if (scanMethodAnnotations || scanParameterAnnotations)
scanMethods(cf);
if (scanFieldAnnotations)
scanFields(cf);
// create an index of interfaces the class implements
if (cf.getInterfaces() != null) {
Set<String> intfs = new HashSet<String>();
for (String intf : cf.getInterfaces())
intfs.add(intf);
implementsIndex.put(cf.getName(), intfs);
}
} finally {
dstream.close();
bits.close();
}
}
protected void scanClass(ClassFile cf) {
String className = cf.getName();
AnnotationsAttribute visible = (AnnotationsAttribute) cf.getAttribute(AnnotationsAttribute.visibleTag);
AnnotationsAttribute invisible = (AnnotationsAttribute) cf.getAttribute(AnnotationsAttribute.invisibleTag);
if (visible != null)
populate(visible.getAnnotations(), className);
if (invisible != null)
populate(invisible.getAnnotations(), className);
}
/**
* Scanns both the method and its parameters for annotations.
*
* @param cf
*/
protected void scanMethods(ClassFile cf) {
List<ClassFile> methods = cf.getMethods();
if (methods == null)
return;
for (Object obj : methods) {
MethodInfo method = (MethodInfo) obj;
if (scanMethodAnnotations) {
AnnotationsAttribute visible = (AnnotationsAttribute) method
.getAttribute(AnnotationsAttribute.visibleTag);
AnnotationsAttribute invisible = (AnnotationsAttribute) method
.getAttribute(AnnotationsAttribute.invisibleTag);
if (visible != null)
populate(visible.getAnnotations(), cf.getName());
if (invisible != null)
populate(invisible.getAnnotations(), cf.getName());
}
if (scanParameterAnnotations) {
ParameterAnnotationsAttribute paramsVisible = (ParameterAnnotationsAttribute) method
.getAttribute(ParameterAnnotationsAttribute.visibleTag);
ParameterAnnotationsAttribute paramsInvisible = (ParameterAnnotationsAttribute) method
.getAttribute(ParameterAnnotationsAttribute.invisibleTag);
if (paramsVisible != null && paramsVisible.getAnnotations() != null) {
for (Annotation[] anns : paramsVisible.getAnnotations()) {
populate(anns, cf.getName());
}
}
if (paramsInvisible != null && paramsInvisible.getAnnotations() != null) {
for (Annotation[] anns : paramsInvisible.getAnnotations()) {
populate(anns, cf.getName());
}
}
}
}
}
protected void scanFields(ClassFile cf) {
List<ClassFile> fields = cf.getFields();
if (fields == null)
return;
for (Object obj : fields) {
FieldInfo field = (FieldInfo) obj;
AnnotationsAttribute visible = (AnnotationsAttribute) field.getAttribute(AnnotationsAttribute.visibleTag);
AnnotationsAttribute invisible = (AnnotationsAttribute) field
.getAttribute(AnnotationsAttribute.invisibleTag);
if (visible != null)
populate(visible.getAnnotations(), cf.getName());
if (invisible != null)
populate(invisible.getAnnotations(), cf.getName());
}
}
protected void populate(Annotation[] annotations, String className) {
if (annotations == null)
return;
Set<String> classAnnotations = classIndex.get(className);
for (Annotation ann : annotations) {
Set<String> classes = annotationIndex.get(ann.getTypeName());
if (classes == null) {
classes = new HashSet<String>();
annotationIndex.put(ann.getTypeName(), classes);
}
classes.add(className);
classAnnotations.add(ann.getTypeName());
}
}
/**
* Prints out annotationIndex
*
* @param writer
*/
public void outputAnnotationIndex(PrintWriter writer) {
for (String ann : annotationIndex.keySet()) {
writer.print(ann);
writer.print(": ");
Set<String> classes = annotationIndex.get(ann);
Iterator<String> it = classes.iterator();
while (it.hasNext()) {
writer.print(it.next());
if (it.hasNext())
writer.print(", ");
}
writer.println();
}
}
}