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;
53 protected transient boolean ignoreBadURLs = false;
55 public class CrossReferenceException extends Exception
57 private Set<String> unresolved;
59 public CrossReferenceException(Set<String> unresolved)
61 this.unresolved = unresolved;
64 public Set<String> getUnresolved()
70 public String[] getScanPackages()
76 * Set explicit packages to scan.
77 * Set to null to enable ignore list.
79 * @param scanPackages packages to scan or null
81 public void setScanPackages(String[] scanPackages)
83 this.scanPackages = scanPackages;
86 public String[] getIgnoredPackages()
88 return ignoredPackages;
92 * Override/overwrite any ignored packages
94 * @param ignoredPackages cannot be null
96 public void setIgnoredPackages(String[] ignoredPackages)
98 this.ignoredPackages = ignoredPackages;
101 public void addIgnoredPackages(String... ignored)
103 String[] tmp = new String[ignoredPackages.length + ignored.length];
105 for (String ign : ignoredPackages) tmp[i++] = ign;
106 for (String ign : ignored) tmp[i++] = ign;
107 this.ignoredPackages = tmp;
111 * This method will cross reference annotations in the annotation index with any meta-annotations that they have
112 * and create additional entries as needed. For example:
115 * @ HttpMethod("GET") public @interface GET {}
118 * The HttpMethod index will have additional classes added to it for any classes annotated with annotations that
119 * have the HttpMethod meta-annotation.
121 * WARNING: If the annotation class has not already been scaned, this method will load all annotation classes indexed
122 * as a resource so they must be in your classpath
124 public void crossReferenceMetaAnnotations() throws CrossReferenceException
126 Set<String> unresolved = new HashSet<String>();
128 Set<String> index = new HashSet<String>();
129 index.addAll(annotationIndex.keySet());
131 for (String annotation : index)
133 if (ignoreScan(annotation))
137 if (classIndex.containsKey(annotation))
139 for (String xref : classIndex.get(annotation))
141 annotationIndex.get(xref).addAll(annotationIndex.get(annotation));
145 InputStream bits = Thread.currentThread().getContextClassLoader().getResourceAsStream(annotation.replace('.', '/') + ".class");
148 unresolved.add(annotation);
155 catch (IOException e)
157 unresolved.add(annotation);
159 for (String xref : classIndex.get(annotation))
161 annotationIndex.get(xref).addAll(annotationIndex.get(annotation));
165 if (unresolved.size() > 0) throw new CrossReferenceException(unresolved);
169 * Sometimes you want to see if a particular class implements an interface with certain annotations
170 * After you have loaded all your classpaths with the scanArchive() method, call this method to cross reference
171 * a class's implemented interfaces. The cross references will be added to the annotationIndex and
174 * @throws CrossReferenceException an Exception thrown if referenced interfaces haven't been scanned
176 public void crossReferenceImplementedInterfaces() throws CrossReferenceException
178 Set<String> unresolved = new HashSet<String>();
179 for (String clazz : implementsIndex.keySet())
181 Set<String> intfs = implementsIndex.get(clazz);
182 for (String intf : intfs)
184 if (ignoreScan(intf)) continue;
186 Set<String> xrefAnnotations = classIndex.get(intf);
187 if (xrefAnnotations == null)
189 unresolved.add(intf);
193 Set<String> classAnnotations = classIndex.get(clazz);
194 if (classAnnotations == null)
196 classIndex.put(clazz, xrefAnnotations);
198 else classAnnotations.addAll(xrefAnnotations);
199 for (String annotation : xrefAnnotations)
201 Set<String> classes = annotationIndex.get(annotation);
207 if (unresolved.size() > 0) throw new CrossReferenceException(unresolved);
211 private boolean ignoreScan(String intf)
213 if (scanPackages != null)
215 for (String scan : scanPackages)
217 // do not ignore if on packages to scan list
218 if (intf.startsWith(scan + "."))
223 return true; // didn't match whitelist, ignore
225 for (String ignored : ignoredPackages)
227 if (intf.startsWith(ignored + "."))
233 //System.out.println("NOT IGNORING: " + intf);
240 * returns a map keyed by the fully qualified string name of a annotation class. The Set returne is
241 * a list of classes that use that annotation somehow.
243 public Map<String, Set<String>> getAnnotationIndex()
245 return annotationIndex;
249 * returns a map keyed by the list of classes scanned. The value set returned is a list of annotations
250 * used by that class.
252 public Map<String, Set<String>> getClassIndex()
259 * Whether or not you want AnnotationDB to scan for class level annotations
261 * @param scanClassAnnotations
263 public void setScanClassAnnotations(boolean scanClassAnnotations)
265 this.scanClassAnnotations = scanClassAnnotations;
269 * Wheter or not you want AnnotationDB to scan for method level annotations
271 * @param scanMethodAnnotations
273 public void setScanMethodAnnotations(boolean scanMethodAnnotations)
275 this.scanMethodAnnotations = scanMethodAnnotations;
279 * Whether or not you want AnnotationDB to scan for parameter level annotations
281 * @param scanParameterAnnotations
283 public void setScanParameterAnnotations(boolean scanParameterAnnotations)
285 this.scanParameterAnnotations = scanParameterAnnotations;
289 * Whether or not you want AnnotationDB to scan for parameter level annotations
291 * @param scanFieldAnnotations
293 public void setScanFieldAnnotations(boolean scanFieldAnnotations)
295 this.scanFieldAnnotations = scanFieldAnnotations;
299 * Whether or not you want AnnotationDB to ignore bad URLs passed to scanArchives.
300 * Default is to throw an IOException.
302 * @param ignoreBadURLs
304 public void setIgnoreBadURLs(boolean ignoreBadURLs)
306 this.ignoreBadURLs = ignoreBadURLs;
310 * Scan a url that represents an "archive" this is a classpath directory or jar file
312 * @param urls variable list of URLs to scan as archives
313 * @throws IOException
315 public void scanArchives(URL... urls) throws IOException
319 Filter filter = new Filter()
321 public boolean accepts(String filename)
323 if (filename.endsWith(".class"))
325 if (filename.startsWith("/") || filename.startsWith("\\"))
326 filename = filename.substring(1);
327 if (!ignoreScan(filename.replace('/', '.')))
329 //System.out.println("IGNORED: " + filename);
337 StreamIterator it = IteratorFactory.create(url, filter);
340 while ((stream = it.next()) != null) scanClass(stream);
342 catch (IOException e)
354 * Parse a .class file for annotations
356 * @param bits input stream pointing to .class file bits
357 * @throws IOException
359 public void scanClass(InputStream bits) throws IOException
361 DataInputStream dstream = new DataInputStream(new BufferedInputStream(bits));
365 cf = new ClassFile(dstream);
366 classIndex.put(cf.getName(), new HashSet<String>());
367 if (scanClassAnnotations)
369 if (scanMethodAnnotations || scanParameterAnnotations) scanMethods(cf);
370 if (scanFieldAnnotations) scanFields(cf);
372 // create an index of interfaces the class implements
373 if (cf.getInterfaces() != null)
375 Set<String> intfs = new HashSet<String>();
376 for (String intf : cf.getInterfaces()) intfs.add(intf);
377 implementsIndex.put(cf.getName(), intfs);
388 protected void scanClass(ClassFile cf)
390 String className = cf.getName();
391 AnnotationsAttribute visible = (AnnotationsAttribute) cf.getAttribute(AnnotationsAttribute.visibleTag);
392 AnnotationsAttribute invisible = (AnnotationsAttribute) cf.getAttribute(AnnotationsAttribute.invisibleTag);
394 if (visible != null) populate(visible.getAnnotations(), className);
395 if (invisible != null) populate(invisible.getAnnotations(), className);
399 * Scanns both the method and its parameters for annotations.
403 protected void scanMethods(ClassFile cf)
405 List methods = cf.getMethods();
406 if (methods == null) return;
407 for (Object obj : methods)
409 MethodInfo method = (MethodInfo) obj;
410 if (scanMethodAnnotations)
412 AnnotationsAttribute visible = (AnnotationsAttribute) method.getAttribute(AnnotationsAttribute.visibleTag);
413 AnnotationsAttribute invisible = (AnnotationsAttribute) method.getAttribute(AnnotationsAttribute.invisibleTag);
415 if (visible != null) populate(visible.getAnnotations(), cf.getName());
416 if (invisible != null) populate(invisible.getAnnotations(), cf.getName());
418 if (scanParameterAnnotations)
420 ParameterAnnotationsAttribute paramsVisible = (ParameterAnnotationsAttribute) method.getAttribute(ParameterAnnotationsAttribute.visibleTag);
421 ParameterAnnotationsAttribute paramsInvisible = (ParameterAnnotationsAttribute) method.getAttribute(ParameterAnnotationsAttribute.invisibleTag);
423 if (paramsVisible != null && paramsVisible.getAnnotations() != null)
425 for (Annotation[] anns : paramsVisible.getAnnotations())
427 populate(anns, cf.getName());
430 if (paramsInvisible != null && paramsInvisible.getAnnotations() != null)
432 for (Annotation[] anns : paramsInvisible.getAnnotations())
434 populate(anns, cf.getName());
441 protected void scanFields(ClassFile cf)
443 List fields = cf.getFields();
444 if (fields == null) return;
445 for (Object obj : fields)
447 FieldInfo field = (FieldInfo) obj;
448 AnnotationsAttribute visible = (AnnotationsAttribute) field.getAttribute(AnnotationsAttribute.visibleTag);
449 AnnotationsAttribute invisible = (AnnotationsAttribute) field.getAttribute(AnnotationsAttribute.invisibleTag);
451 if (visible != null) populate(visible.getAnnotations(), cf.getName());
452 if (invisible != null) populate(invisible.getAnnotations(), cf.getName());
458 protected void populate(Annotation[] annotations, String className)
460 if (annotations == null) return;
461 Set<String> classAnnotations = classIndex.get(className);
462 for (Annotation ann : annotations)
464 Set<String> classes = annotationIndex.get(ann.getTypeName());
467 classes = new HashSet<String>();
468 annotationIndex.put(ann.getTypeName(), classes);
470 classes.add(className);
471 classAnnotations.add(ann.getTypeName());
476 * Prints out annotationIndex
480 public void outputAnnotationIndex(PrintWriter writer)
482 for (String ann : annotationIndex.keySet())
486 Set<String> classes = annotationIndex.get(ann);
487 Iterator<String> it = classes.iterator();
490 writer.print(it.next());
491 if (it.hasNext()) writer.print(", ");