package org.scannotation; 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; import org.scannotation.classpath.Filter; import org.scannotation.classpath.IteratorFactory; import org.scannotation.classpath.StreamIterator; 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; /** * @author Bill Burke * @version $Revision: 1 $ */ public class AnnotationDB implements Serializable { protected Map> annotationIndex = new HashMap>(); protected Map> implementsIndex = new HashMap>(); protected Map> classIndex = new HashMap>(); protected transient boolean scanClassAnnotations = true; protected transient boolean scanMethodAnnotations = true; protected transient boolean scanParameterAnnotations = true; protected transient boolean scanFieldAnnotations = true; public class CrossReferenceException extends RuntimeException { private Map> unresolved; public CrossReferenceException(Map> unresolved) { this.unresolved = unresolved; } public Map> getUnresolved() { return unresolved; } } /** * Sometimes you want to see if a particular class implements an interface with certain annotations * After you have loaded all your classpaths with the scanArchive() method, call this method to cross reference * a class's implemented interfaces. The cross references will be added to the annotationIndex and * classIndex indexes * * @param ignoredPackages var arg list of packages to ignore * @throws CrossReferenceException a RuntimeException thrown if referenced interfaces haven't been scanned */ public void crossReferenceImplementedInterfaces(String... ignoredPackages) throws CrossReferenceException { Map> unresolved = new HashMap>(); for (String clazz : implementsIndex.keySet()) { Set intfs = implementsIndex.get(clazz); for (String intf : intfs) { if (intf.startsWith("java.") || intf.startsWith("javax.")) continue; boolean ignoreInterface = false; for (String ignored : ignoredPackages) { if (intf.startsWith(ignored + ".")) { ignoreInterface = true; break; } } if (ignoreInterface) continue; Set unresolvedInterfaces = new HashSet(); Set xrefAnnotations = classIndex.get(intf); if (xrefAnnotations == null) { unresolvedInterfaces.add(intf); unresolved.put(clazz, unresolvedInterfaces); } Set classAnnotations = classIndex.get(clazz); classAnnotations.addAll(xrefAnnotations); for (String annotation : xrefAnnotations) { Set classes = annotationIndex.get(annotation); classes.add(clazz); } } } } public Map> getAnnotationIndex() { return annotationIndex; } public Map> getClassIndex() { return classIndex; } public void setScanClassAnnotations(boolean scanClassAnnotations) { this.scanClassAnnotations = scanClassAnnotations; } public void setScanMethodAnnotations(boolean scanMethodAnnotations) { this.scanMethodAnnotations = scanMethodAnnotations; } public void setScanParameterAnnotations(boolean scanParameterAnnotations) { this.scanParameterAnnotations = scanParameterAnnotations; } public void setScanFieldAnnotations(boolean scanFieldAnnotations) { this.scanFieldAnnotations = scanFieldAnnotations; } /** * Scan a url that represents an "archive" this is a classpath directory or jar file * * @param url * @throws IOException */ public void scanArchives(URL... urls) throws IOException { for (URL url : urls) { Filter filter = new Filter() { public boolean accepts(String filename) { return filename.endsWith(".class"); } }; StreamIterator it = IteratorFactory.create(url, filter); InputStream stream; while ((stream = it.next()) != null) scanClass(stream); } } /** * Can a .class file for annotations * * @param 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()); 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 intfs = new HashSet(); 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 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 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 classAnnotations = classIndex.get(className); for (Annotation ann : annotations) { Set classes = annotationIndex.get(ann.getTypeName()); if (classes == null) { classes = new HashSet(); annotationIndex.put(ann.getTypeName(), classes); } classes.add(className); classAnnotations.add(ann.getTypeName()); } } public void outputAnnotationDB(PrintWriter writer) { for (String ann : annotationIndex.keySet()) { writer.print(ann); writer.print(": "); Set classes = annotationIndex.get(ann); Iterator it = classes.iterator(); while (it.hasNext()) { writer.print(it.next()); if (it.hasNext()) writer.print(", "); } writer.println(); } } }