1 package org.scannotation;
3 import javassist.bytecode.AnnotationsAttribute;
4 import javassist.bytecode.ClassFile;
5 import javassist.bytecode.FieldInfo;
6 import javassist.bytecode.MethodInfo;
7 import javassist.bytecode.ParameterAnnotationsAttribute;
8 import javassist.bytecode.annotation.Annotation;
9 import org.scannotation.archiveiterator.Filter;
10 import org.scannotation.archiveiterator.IteratorFactory;
11 import org.scannotation.archiveiterator.StreamIterator;
13 import java.io.BufferedInputStream;
14 import java.io.DataInputStream;
15 import java.io.IOException;
16 import java.io.InputStream;
17 import java.io.PrintWriter;
18 import java.io.Serializable;
20 import java.util.HashMap;
21 import java.util.HashSet;
22 import java.util.Iterator;
23 import java.util.List;
28 * The class allows you to scan an arbitrary set of "archives" for .class files. These class files
29 * are parsed to see what annotations they use. Two indexes are created.
31 * One is a map of annotations and what classes
32 * use those annotations. This could be used, for example, by an EJB deployer to find all the EJBs contained
35 * Another is a mpa of classes and what annotations those classes use.
37 * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
38 * @version $Revision: 1 $
40 public class AnnotationDB implements Serializable
42 protected Map<String, Set<String>> annotationIndex = new HashMap<String, Set<String>>();
43 protected Map<String, Set<String>> implementsIndex = new HashMap<String, Set<String>>();
44 protected Map<String, Set<String>> classIndex = new HashMap<String, Set<String>>();
46 protected transient boolean scanClassAnnotations = true;
47 protected transient boolean scanMethodAnnotations = true;
48 protected transient boolean scanParameterAnnotations = true;
49 protected transient boolean scanFieldAnnotations = true;
51 public class CrossReferenceException extends RuntimeException
53 private Map<String, Set<String>> unresolved;
55 public CrossReferenceException(Map<String, Set<String>> unresolved)
57 this.unresolved = unresolved;
60 public Map<String, Set<String>> getUnresolved()
67 * Sometimes you want to see if a particular class implements an interface with certain annotations
68 * After you have loaded all your classpaths with the scanArchive() method, call this method to cross reference
69 * a class's implemented interfaces. The cross references will be added to the annotationIndex and
72 * @param ignoredPackages var arg list of packages to ignore
73 * @throws CrossReferenceException a RuntimeException thrown if referenced interfaces haven't been scanned
75 public void crossReferenceImplementedInterfaces(String... ignoredPackages) throws CrossReferenceException
77 Map<String, Set<String>> unresolved = new HashMap<String, Set<String>>();
78 for (String clazz : implementsIndex.keySet())
80 Set<String> intfs = implementsIndex.get(clazz);
81 for (String intf : intfs)
83 if (intf.startsWith("java.") || intf.startsWith("javax.")) continue;
84 boolean ignoreInterface = false;
85 for (String ignored : ignoredPackages)
87 if (intf.startsWith(ignored + "."))
89 ignoreInterface = true;
93 if (ignoreInterface) continue;
95 Set<String> unresolvedInterfaces = new HashSet<String>();
96 Set<String> xrefAnnotations = classIndex.get(intf);
97 if (xrefAnnotations == null)
99 unresolvedInterfaces.add(intf);
100 unresolved.put(clazz, unresolvedInterfaces);
102 Set<String> classAnnotations = classIndex.get(clazz);
103 classAnnotations.addAll(xrefAnnotations);
104 for (String annotation : xrefAnnotations)
106 Set<String> classes = annotationIndex.get(annotation);
115 * returns a map keyed by the fully qualified string name of a annotation class. The Set returne is
116 * a list of classes that use that annotation somehow.
119 public Map<String, Set<String>> getAnnotationIndex()
121 return annotationIndex;
125 * returns a map keyed by the list of classes scanned. The value set returned is a list of annotations
126 * used by that class.
129 public Map<String, Set<String>> getClassIndex()
136 * Whether or not you want AnnotationDB to scan for class level annotations
138 * @param scanClassAnnotations
140 public void setScanClassAnnotations(boolean scanClassAnnotations)
142 this.scanClassAnnotations = scanClassAnnotations;
146 * Wheter or not you want AnnotationDB to scan for method level annotations
148 * @param scanMethodAnnotations
150 public void setScanMethodAnnotations(boolean scanMethodAnnotations)
152 this.scanMethodAnnotations = scanMethodAnnotations;
156 * Whether or not you want AnnotationDB to scan for parameter level annotations
158 * @param scanParameterAnnotations
160 public void setScanParameterAnnotations(boolean scanParameterAnnotations)
162 this.scanParameterAnnotations = scanParameterAnnotations;
166 * Whether or not you want AnnotationDB to scan for parameter level annotations
168 * @param scanFieldAnnotations
170 public void setScanFieldAnnotations(boolean scanFieldAnnotations)
172 this.scanFieldAnnotations = scanFieldAnnotations;
177 * Scan a url that represents an "archive" this is a classpath directory or jar file
179 * @param urls variable list of URLs to scan as archives
180 * @throws IOException
182 public void scanArchives(URL... urls) throws IOException
186 Filter filter = new Filter()
188 public boolean accepts(String filename)
190 return filename.endsWith(".class");
194 StreamIterator it = IteratorFactory.create(url, filter);
197 while ((stream = it.next()) != null) scanClass(stream);
203 * Parse a .class file for annotations
205 * @param bits input stream pointing to .class file bits
206 * @throws IOException
208 public void scanClass(InputStream bits) throws IOException
210 DataInputStream dstream = new DataInputStream(new BufferedInputStream(bits));
214 cf = new ClassFile(dstream);
215 classIndex.put(cf.getName(), new HashSet<String>());
216 if (scanClassAnnotations) ;
218 if (scanMethodAnnotations || scanParameterAnnotations) scanMethods(cf);
219 if (scanFieldAnnotations) scanFields(cf);
221 // create an index of interfaces the class implements
222 if (cf.getInterfaces() != null)
224 Set<String> intfs = new HashSet<String>();
225 for (String intf : cf.getInterfaces()) intfs.add(intf);
226 implementsIndex.put(cf.getName(), intfs);
237 protected void scanClass(ClassFile cf)
239 String className = cf.getName();
240 AnnotationsAttribute visible = (AnnotationsAttribute) cf.getAttribute(AnnotationsAttribute.visibleTag);
241 AnnotationsAttribute invisible = (AnnotationsAttribute) cf.getAttribute(AnnotationsAttribute.invisibleTag);
243 if (visible != null) populate(visible.getAnnotations(), className);
244 if (invisible != null) populate(invisible.getAnnotations(), className);
248 * Scanns both the method and its parameters for annotations.
252 protected void scanMethods(ClassFile cf)
254 List methods = cf.getMethods();
255 if (methods == null) return;
256 for (Object obj : methods)
258 MethodInfo method = (MethodInfo) obj;
259 if (scanMethodAnnotations)
261 AnnotationsAttribute visible = (AnnotationsAttribute) method.getAttribute(AnnotationsAttribute.visibleTag);
262 AnnotationsAttribute invisible = (AnnotationsAttribute) method.getAttribute(AnnotationsAttribute.invisibleTag);
264 if (visible != null) populate(visible.getAnnotations(), cf.getName());
265 if (invisible != null) populate(invisible.getAnnotations(), cf.getName());
267 if (scanParameterAnnotations)
269 ParameterAnnotationsAttribute paramsVisible = (ParameterAnnotationsAttribute) method.getAttribute(ParameterAnnotationsAttribute.visibleTag);
270 ParameterAnnotationsAttribute paramsInvisible = (ParameterAnnotationsAttribute) method.getAttribute(ParameterAnnotationsAttribute.invisibleTag);
272 if (paramsVisible != null && paramsVisible.getAnnotations() != null)
274 for (Annotation[] anns : paramsVisible.getAnnotations())
276 populate(anns, cf.getName());
279 if (paramsInvisible != null && paramsInvisible.getAnnotations() != null)
281 for (Annotation[] anns : paramsInvisible.getAnnotations())
283 populate(anns, cf.getName());
290 protected void scanFields(ClassFile cf)
292 List fields = cf.getFields();
293 if (fields == null) return;
294 for (Object obj : fields)
296 FieldInfo field = (FieldInfo) obj;
297 AnnotationsAttribute visible = (AnnotationsAttribute) field.getAttribute(AnnotationsAttribute.visibleTag);
298 AnnotationsAttribute invisible = (AnnotationsAttribute) field.getAttribute(AnnotationsAttribute.invisibleTag);
300 if (visible != null) populate(visible.getAnnotations(), cf.getName());
301 if (invisible != null) populate(invisible.getAnnotations(), cf.getName());
307 protected void populate(Annotation[] annotations, String className)
309 if (annotations == null) return;
310 Set<String> classAnnotations = classIndex.get(className);
311 for (Annotation ann : annotations)
313 Set<String> classes = annotationIndex.get(ann.getTypeName());
316 classes = new HashSet<String>();
317 annotationIndex.put(ann.getTypeName(), classes);
319 classes.add(className);
320 classAnnotations.add(ann.getTypeName());
325 * Prints out annotationIndex
329 public void outputAnnotationIndex(PrintWriter writer)
331 for (String ann : annotationIndex.keySet())
335 Set<String> classes = annotationIndex.get(ann);
336 Iterator<String> it = classes.iterator();
339 writer.print(it.next());
340 if (it.hasNext()) writer.print(", ");