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. The javax, java, sun, com.sun, and javassist
30 * packages will not be scanned by default.
32 * One is a map of annotations and what classes
33 * use those annotations. This could be used, for example, by an EJB deployer to find all the EJBs contained
36 * Another is a mpa of classes and what annotations those classes use.
38 * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
39 * @version $Revision: 1 $
41 public class AnnotationDB implements Serializable
43 protected Map<String, Set<String>> annotationIndex = new HashMap<String, Set<String>>();
44 protected Map<String, Set<String>> implementsIndex = new HashMap<String, Set<String>>();
45 protected Map<String, Set<String>> classIndex = new HashMap<String, Set<String>>();
47 protected transient boolean scanClassAnnotations = true;
48 protected transient boolean scanMethodAnnotations = true;
49 protected transient boolean scanParameterAnnotations = true;
50 protected transient boolean scanFieldAnnotations = true;
51 protected transient String[] ignoredPackages = {"javax", "java", "sun", "com.sun", "javassist"};
52 protected transient String[] scanPackages = null;
54 public class CrossReferenceException extends Exception
56 private Set<String> unresolved;
58 public CrossReferenceException(Set<String> unresolved)
60 this.unresolved = unresolved;
63 public Set<String> getUnresolved()
69 public String[] getScanPackages()
75 * Set explicit packages to scan.
76 * Set to null to enable ignore list.
78 * @param scanPackages packages to scan or null
80 public void setScanPackages(String[] scanPackages)
82 this.scanPackages = scanPackages;
85 public String[] getIgnoredPackages()
87 return ignoredPackages;
91 * Override/overwrite any ignored packages
93 * @param ignoredPackages cannot be null
95 public void setIgnoredPackages(String[] ignoredPackages)
97 this.ignoredPackages = ignoredPackages;
100 public void addIgnoredPackages(String... ignored)
102 String[] tmp = new String[ignoredPackages.length + ignored.length];
104 for (String ign : ignoredPackages) tmp[i++] = ign;
105 for (String ign : ignored) tmp[i++] = ign;
106 this.ignoredPackages = tmp;
110 * This method will cross reference annotations in the annotation index with any meta-annotations that they have
111 * and create additional entries as needed. For example:
113 * @HttpMethod("GET") public @interface GET {}
115 * The HttpMethod index will have additional classes added to it for any classes annotated with annotations that
116 * have the HttpMethod meta-annotation.
118 * WARNING: If the annotation class has not already been scaned, this method will load all annotation classes indexed
119 * as a resource so they must be in your classpath
121 public void crossReferenceMetaAnnotations() throws CrossReferenceException
123 Set<String> unresolved = new HashSet<String>();
125 Set<String> index = new HashSet<String>();
126 index.addAll(annotationIndex.keySet());
128 for (String annotation : index)
130 if (ignoreScan(annotation))
134 if (classIndex.containsKey(annotation))
136 for (String xref : classIndex.get(annotation))
138 annotationIndex.get(xref).addAll(annotationIndex.get(annotation));
142 InputStream bits = Thread.currentThread().getContextClassLoader().getResourceAsStream(annotation.replace('.', '/') + ".class");
145 unresolved.add(annotation);
152 catch (IOException e)
154 unresolved.add(annotation);
156 for (String xref : classIndex.get(annotation))
158 annotationIndex.get(xref).addAll(annotationIndex.get(annotation));
162 if (unresolved.size() > 0) throw new CrossReferenceException(unresolved);
166 * Sometimes you want to see if a particular class implements an interface with certain annotations
167 * After you have loaded all your classpaths with the scanArchive() method, call this method to cross reference
168 * a class's implemented interfaces. The cross references will be added to the annotationIndex and
171 * @param ignoredPackages var arg list of packages to ignore
172 * @throws CrossReferenceException an Exception thrown if referenced interfaces haven't been scanned
174 public void crossReferenceImplementedInterfaces() throws CrossReferenceException
176 Set<String> unresolved = new HashSet<String>();
177 for (String clazz : implementsIndex.keySet())
179 Set<String> intfs = implementsIndex.get(clazz);
180 for (String intf : intfs)
182 if (ignoreScan(intf)) continue;
184 Set<String> xrefAnnotations = classIndex.get(intf);
185 if (xrefAnnotations == null)
187 unresolved.add(intf);
191 Set<String> classAnnotations = classIndex.get(clazz);
192 if (classAnnotations == null)
194 classIndex.put(clazz, xrefAnnotations);
196 else classAnnotations.addAll(xrefAnnotations);
197 for (String annotation : xrefAnnotations)
199 Set<String> classes = annotationIndex.get(annotation);
205 if (unresolved.size() > 0) throw new CrossReferenceException(unresolved);
209 private boolean ignoreScan(String intf)
211 if (scanPackages != null)
213 for (String scan : scanPackages)
215 // do not ignore if on packages to scan list
216 if (intf.startsWith(scan + "."))
221 return true; // didn't match whitelist, ignore
223 for (String ignored : ignoredPackages)
225 if (intf.startsWith(ignored + "."))
231 //System.out.println("NOT IGNORING: " + intf);
238 * returns a map keyed by the fully qualified string name of a annotation class. The Set returne is
239 * a list of classes that use that annotation somehow.
241 public Map<String, Set<String>> getAnnotationIndex()
243 return annotationIndex;
247 * returns a map keyed by the list of classes scanned. The value set returned is a list of annotations
248 * used by that class.
250 public Map<String, Set<String>> getClassIndex()
257 * Whether or not you want AnnotationDB to scan for class level annotations
259 * @param scanClassAnnotations
261 public void setScanClassAnnotations(boolean scanClassAnnotations)
263 this.scanClassAnnotations = scanClassAnnotations;
267 * Wheter or not you want AnnotationDB to scan for method level annotations
269 * @param scanMethodAnnotations
271 public void setScanMethodAnnotations(boolean scanMethodAnnotations)
273 this.scanMethodAnnotations = scanMethodAnnotations;
277 * Whether or not you want AnnotationDB to scan for parameter level annotations
279 * @param scanParameterAnnotations
281 public void setScanParameterAnnotations(boolean scanParameterAnnotations)
283 this.scanParameterAnnotations = scanParameterAnnotations;
287 * Whether or not you want AnnotationDB to scan for parameter level annotations
289 * @param scanFieldAnnotations
291 public void setScanFieldAnnotations(boolean scanFieldAnnotations)
293 this.scanFieldAnnotations = scanFieldAnnotations;
298 * Scan a url that represents an "archive" this is a classpath directory or jar file
300 * @param urls variable list of URLs to scan as archives
301 * @throws IOException
303 public void scanArchives(URL... urls) throws IOException
307 Filter filter = new Filter()
309 public boolean accepts(String filename)
311 if (filename.endsWith(".class"))
313 if (filename.startsWith("/") || filename.startsWith("\\"))
314 filename = filename.substring(1);
315 if (!ignoreScan(filename.replace('/', '.')))
317 //System.out.println("IGNORED: " + filename);
323 StreamIterator it = IteratorFactory.create(url, filter);
326 while ((stream = it.next()) != null) scanClass(stream);
332 * Parse a .class file for annotations
334 * @param bits input stream pointing to .class file bits
335 * @throws IOException
337 public void scanClass(InputStream bits) throws IOException
339 DataInputStream dstream = new DataInputStream(new BufferedInputStream(bits));
343 cf = new ClassFile(dstream);
344 classIndex.put(cf.getName(), new HashSet<String>());
345 if (scanClassAnnotations)
347 if (scanMethodAnnotations || scanParameterAnnotations) scanMethods(cf);
348 if (scanFieldAnnotations) scanFields(cf);
350 // create an index of interfaces the class implements
351 if (cf.getInterfaces() != null)
353 Set<String> intfs = new HashSet<String>();
354 for (String intf : cf.getInterfaces()) intfs.add(intf);
355 implementsIndex.put(cf.getName(), intfs);
366 protected void scanClass(ClassFile cf)
368 String className = cf.getName();
369 AnnotationsAttribute visible = (AnnotationsAttribute) cf.getAttribute(AnnotationsAttribute.visibleTag);
370 AnnotationsAttribute invisible = (AnnotationsAttribute) cf.getAttribute(AnnotationsAttribute.invisibleTag);
372 if (visible != null) populate(visible.getAnnotations(), className);
373 if (invisible != null) populate(invisible.getAnnotations(), className);
377 * Scanns both the method and its parameters for annotations.
381 protected void scanMethods(ClassFile cf)
383 List methods = cf.getMethods();
384 if (methods == null) return;
385 for (Object obj : methods)
387 MethodInfo method = (MethodInfo) obj;
388 if (scanMethodAnnotations)
390 AnnotationsAttribute visible = (AnnotationsAttribute) method.getAttribute(AnnotationsAttribute.visibleTag);
391 AnnotationsAttribute invisible = (AnnotationsAttribute) method.getAttribute(AnnotationsAttribute.invisibleTag);
393 if (visible != null) populate(visible.getAnnotations(), cf.getName());
394 if (invisible != null) populate(invisible.getAnnotations(), cf.getName());
396 if (scanParameterAnnotations)
398 ParameterAnnotationsAttribute paramsVisible = (ParameterAnnotationsAttribute) method.getAttribute(ParameterAnnotationsAttribute.visibleTag);
399 ParameterAnnotationsAttribute paramsInvisible = (ParameterAnnotationsAttribute) method.getAttribute(ParameterAnnotationsAttribute.invisibleTag);
401 if (paramsVisible != null && paramsVisible.getAnnotations() != null)
403 for (Annotation[] anns : paramsVisible.getAnnotations())
405 populate(anns, cf.getName());
408 if (paramsInvisible != null && paramsInvisible.getAnnotations() != null)
410 for (Annotation[] anns : paramsInvisible.getAnnotations())
412 populate(anns, cf.getName());
419 protected void scanFields(ClassFile cf)
421 List fields = cf.getFields();
422 if (fields == null) return;
423 for (Object obj : fields)
425 FieldInfo field = (FieldInfo) obj;
426 AnnotationsAttribute visible = (AnnotationsAttribute) field.getAttribute(AnnotationsAttribute.visibleTag);
427 AnnotationsAttribute invisible = (AnnotationsAttribute) field.getAttribute(AnnotationsAttribute.invisibleTag);
429 if (visible != null) populate(visible.getAnnotations(), cf.getName());
430 if (invisible != null) populate(invisible.getAnnotations(), cf.getName());
436 protected void populate(Annotation[] annotations, String className)
438 if (annotations == null) return;
439 Set<String> classAnnotations = classIndex.get(className);
440 for (Annotation ann : annotations)
442 Set<String> classes = annotationIndex.get(ann.getTypeName());
445 classes = new HashSet<String>();
446 annotationIndex.put(ann.getTypeName(), classes);
448 classes.add(className);
449 classAnnotations.add(ann.getTypeName());
454 * Prints out annotationIndex
458 public void outputAnnotationIndex(PrintWriter writer)
460 for (String ann : annotationIndex.keySet())
464 Set<String> classes = annotationIndex.get(ann);
465 Iterator<String> it = classes.iterator();
468 writer.print(it.next());
469 if (it.hasNext()) writer.print(", ");