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"};
53 public class CrossReferenceException extends Exception
55 private Set<String> unresolved;
57 public CrossReferenceException(Set<String> unresolved)
59 this.unresolved = unresolved;
62 public Set<String> getUnresolved()
68 public String[] getIgnoredPackages()
70 return ignoredPackages;
74 * Override/overwrite any ignored packages
76 * @param ignoredPackages cannot be null
78 public void setIgnoredPackages(String[] ignoredPackages)
80 this.ignoredPackages = ignoredPackages;
83 public void addIgnoredPackages(String... ignored)
85 String[] tmp = new String[ignoredPackages.length + ignored.length];
87 for (String ign : ignoredPackages) tmp[i++] = ign;
88 for (String ign : ignored) tmp[i++] = ign;
92 * This method will cross reference annotations in the annotation index with any meta-annotations that they have
93 * and create additional entries as needed. For example:
95 * @HttpMethod("GET") public @interface GET {}
97 * The HttpMethod index will have additional classes added to it for any classes annotated with annotations that
98 * have the HttpMethod meta-annotation.
100 * WARNING: If the annotation class has not already been scaned, this method will load all annotation classes indexed
101 * as a resource so they must be in your classpath
103 public void crossReferenceMetaAnnotations() throws CrossReferenceException
105 Set<String> unresolved = new HashSet<String>();
107 Set<String> index = new HashSet<String>();
108 index.addAll(annotationIndex.keySet());
110 for (String annotation : index)
112 if (ignoreScan(annotation))
116 if (classIndex.containsKey(annotation))
118 for (String xref : classIndex.get(annotation))
120 annotationIndex.get(xref).addAll(annotationIndex.get(annotation));
124 InputStream bits = Thread.currentThread().getContextClassLoader().getResourceAsStream(annotation.replace('.', '/') + ".class");
127 unresolved.add(annotation);
134 catch (IOException e)
136 unresolved.add(annotation);
138 for (String xref : classIndex.get(annotation))
140 annotationIndex.get(xref).addAll(annotationIndex.get(annotation));
144 if (unresolved.size() > 0) throw new CrossReferenceException(unresolved);
148 * Sometimes you want to see if a particular class implements an interface with certain annotations
149 * After you have loaded all your classpaths with the scanArchive() method, call this method to cross reference
150 * a class's implemented interfaces. The cross references will be added to the annotationIndex and
153 * @param ignoredPackages var arg list of packages to ignore
154 * @throws CrossReferenceException an Exception thrown if referenced interfaces haven't been scanned
156 public void crossReferenceImplementedInterfaces() throws CrossReferenceException
158 Set<String> unresolved = new HashSet<String>();
159 for (String clazz : implementsIndex.keySet())
161 Set<String> intfs = implementsIndex.get(clazz);
162 for (String intf : intfs)
164 if (ignoreScan(intf)) continue;
166 Set<String> xrefAnnotations = classIndex.get(intf);
167 if (xrefAnnotations == null)
169 unresolved.add(intf);
173 Set<String> classAnnotations = classIndex.get(clazz);
174 if (classAnnotations == null)
176 classIndex.put(clazz, xrefAnnotations);
178 else classAnnotations.addAll(xrefAnnotations);
179 for (String annotation : xrefAnnotations)
181 Set<String> classes = annotationIndex.get(annotation);
187 if (unresolved.size() > 0) throw new CrossReferenceException(unresolved);
191 private boolean ignoreScan(String intf)
193 for (String ignored : ignoredPackages)
195 if (intf.startsWith(ignored + "."))
201 //System.out.println("NOT IGNORING: " + intf);
208 * returns a map keyed by the fully qualified string name of a annotation class. The Set returne is
209 * a list of classes that use that annotation somehow.
211 public Map<String, Set<String>> getAnnotationIndex()
213 return annotationIndex;
217 * returns a map keyed by the list of classes scanned. The value set returned is a list of annotations
218 * used by that class.
220 public Map<String, Set<String>> getClassIndex()
227 * Whether or not you want AnnotationDB to scan for class level annotations
229 * @param scanClassAnnotations
231 public void setScanClassAnnotations(boolean scanClassAnnotations)
233 this.scanClassAnnotations = scanClassAnnotations;
237 * Wheter or not you want AnnotationDB to scan for method level annotations
239 * @param scanMethodAnnotations
241 public void setScanMethodAnnotations(boolean scanMethodAnnotations)
243 this.scanMethodAnnotations = scanMethodAnnotations;
247 * Whether or not you want AnnotationDB to scan for parameter level annotations
249 * @param scanParameterAnnotations
251 public void setScanParameterAnnotations(boolean scanParameterAnnotations)
253 this.scanParameterAnnotations = scanParameterAnnotations;
257 * Whether or not you want AnnotationDB to scan for parameter level annotations
259 * @param scanFieldAnnotations
261 public void setScanFieldAnnotations(boolean scanFieldAnnotations)
263 this.scanFieldAnnotations = scanFieldAnnotations;
268 * Scan a url that represents an "archive" this is a classpath directory or jar file
270 * @param urls variable list of URLs to scan as archives
271 * @throws IOException
273 public void scanArchives(URL... urls) throws IOException
277 Filter filter = new Filter()
279 public boolean accepts(String filename)
281 if (filename.endsWith(".class"))
283 if (filename.startsWith("/")) filename = filename.substring(1);
284 if (!ignoreScan(filename.replace('/', '.'))) return true;
285 //System.out.println("IGNORED: " + filename);
291 StreamIterator it = IteratorFactory.create(url, filter);
294 while ((stream = it.next()) != null) scanClass(stream);
300 * Parse a .class file for annotations
302 * @param bits input stream pointing to .class file bits
303 * @throws IOException
305 public void scanClass(InputStream bits) throws IOException
307 DataInputStream dstream = new DataInputStream(new BufferedInputStream(bits));
311 cf = new ClassFile(dstream);
312 classIndex.put(cf.getName(), new HashSet<String>());
313 if (scanClassAnnotations) ;
315 if (scanMethodAnnotations || scanParameterAnnotations) scanMethods(cf);
316 if (scanFieldAnnotations) scanFields(cf);
318 // create an index of interfaces the class implements
319 if (cf.getInterfaces() != null)
321 Set<String> intfs = new HashSet<String>();
322 for (String intf : cf.getInterfaces()) intfs.add(intf);
323 implementsIndex.put(cf.getName(), intfs);
334 protected void scanClass(ClassFile cf)
336 String className = cf.getName();
337 AnnotationsAttribute visible = (AnnotationsAttribute) cf.getAttribute(AnnotationsAttribute.visibleTag);
338 AnnotationsAttribute invisible = (AnnotationsAttribute) cf.getAttribute(AnnotationsAttribute.invisibleTag);
340 if (visible != null) populate(visible.getAnnotations(), className);
341 if (invisible != null) populate(invisible.getAnnotations(), className);
345 * Scanns both the method and its parameters for annotations.
349 protected void scanMethods(ClassFile cf)
351 List methods = cf.getMethods();
352 if (methods == null) return;
353 for (Object obj : methods)
355 MethodInfo method = (MethodInfo) obj;
356 if (scanMethodAnnotations)
358 AnnotationsAttribute visible = (AnnotationsAttribute) method.getAttribute(AnnotationsAttribute.visibleTag);
359 AnnotationsAttribute invisible = (AnnotationsAttribute) method.getAttribute(AnnotationsAttribute.invisibleTag);
361 if (visible != null) populate(visible.getAnnotations(), cf.getName());
362 if (invisible != null) populate(invisible.getAnnotations(), cf.getName());
364 if (scanParameterAnnotations)
366 ParameterAnnotationsAttribute paramsVisible = (ParameterAnnotationsAttribute) method.getAttribute(ParameterAnnotationsAttribute.visibleTag);
367 ParameterAnnotationsAttribute paramsInvisible = (ParameterAnnotationsAttribute) method.getAttribute(ParameterAnnotationsAttribute.invisibleTag);
369 if (paramsVisible != null && paramsVisible.getAnnotations() != null)
371 for (Annotation[] anns : paramsVisible.getAnnotations())
373 populate(anns, cf.getName());
376 if (paramsInvisible != null && paramsInvisible.getAnnotations() != null)
378 for (Annotation[] anns : paramsInvisible.getAnnotations())
380 populate(anns, cf.getName());
387 protected void scanFields(ClassFile cf)
389 List fields = cf.getFields();
390 if (fields == null) return;
391 for (Object obj : fields)
393 FieldInfo field = (FieldInfo) obj;
394 AnnotationsAttribute visible = (AnnotationsAttribute) field.getAttribute(AnnotationsAttribute.visibleTag);
395 AnnotationsAttribute invisible = (AnnotationsAttribute) field.getAttribute(AnnotationsAttribute.invisibleTag);
397 if (visible != null) populate(visible.getAnnotations(), cf.getName());
398 if (invisible != null) populate(invisible.getAnnotations(), cf.getName());
404 protected void populate(Annotation[] annotations, String className)
406 if (annotations == null) return;
407 Set<String> classAnnotations = classIndex.get(className);
408 for (Annotation ann : annotations)
410 Set<String> classes = annotationIndex.get(ann.getTypeName());
413 classes = new HashSet<String>();
414 annotationIndex.put(ann.getTypeName(), classes);
416 classes.add(className);
417 classAnnotations.add(ann.getTypeName());
422 * Prints out annotationIndex
426 public void outputAnnotationIndex(PrintWriter writer)
428 for (String ann : annotationIndex.keySet())
432 Set<String> classes = annotationIndex.get(ann);
433 Iterator<String> it = classes.iterator();
436 writer.print(it.next());
437 if (it.hasNext()) writer.print(", ");