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:
96 * public @interface GET {}
98 * The HttpMethod index will have additional classes added to it for any classes annotated with annotations that
99 * have the HttpMethod meta-annotation.
101 * WARNING: If the annotation class has not already been scaned, this method will load all annotation classes indexed
102 * as a resource so they must be in your classpath
106 public void crossReferenceMetaAnnotations() throws CrossReferenceException
108 Set<String> unresolved = new HashSet<String>();
110 Set<String> index = new HashSet<String>();
111 index.addAll(annotationIndex.keySet());
113 for (String annotation : index)
115 if (ignoreScan(annotation))
119 if (classIndex.containsKey(annotation))
121 for (String xref : classIndex.get(annotation))
123 annotationIndex.get(xref).addAll(annotationIndex.get(annotation));
127 InputStream bits = Thread.currentThread().getContextClassLoader().getResourceAsStream(annotation.replace('.', '/') + ".class");
130 unresolved.add(annotation);
137 catch (IOException e)
139 unresolved.add(annotation);
141 for (String xref : classIndex.get(annotation))
143 annotationIndex.get(xref).addAll(annotationIndex.get(annotation));
147 if (unresolved.size() > 0) throw new CrossReferenceException(unresolved);
151 * Sometimes you want to see if a particular class implements an interface with certain annotations
152 * After you have loaded all your classpaths with the scanArchive() method, call this method to cross reference
153 * a class's implemented interfaces. The cross references will be added to the annotationIndex and
156 * @param ignoredPackages var arg list of packages to ignore
157 * @throws CrossReferenceException an Exception thrown if referenced interfaces haven't been scanned
159 public void crossReferenceImplementedInterfaces() throws CrossReferenceException
161 Set<String> unresolved = new HashSet<String>();
162 for (String clazz : implementsIndex.keySet())
164 Set<String> intfs = implementsIndex.get(clazz);
165 for (String intf : intfs)
167 if (ignoreScan(intf)) continue;
169 Set<String> xrefAnnotations = classIndex.get(intf);
170 if (xrefAnnotations == null)
172 unresolved.add(intf);
174 Set<String> classAnnotations = classIndex.get(clazz);
175 classAnnotations.addAll(xrefAnnotations);
176 for (String annotation : xrefAnnotations)
178 Set<String> classes = annotationIndex.get(annotation);
183 if (unresolved.size() > 0) throw new CrossReferenceException(unresolved);
187 private boolean ignoreScan(String intf)
189 for (String ignored : ignoredPackages)
191 if (intf.startsWith(ignored + "."))
197 //System.out.println("NOT IGNORING: " + intf);
204 * returns a map keyed by the fully qualified string name of a annotation class. The Set returne is
205 * a list of classes that use that annotation somehow.
208 public Map<String, Set<String>> getAnnotationIndex()
210 return annotationIndex;
214 * returns a map keyed by the list of classes scanned. The value set returned is a list of annotations
215 * used by that class.
218 public Map<String, Set<String>> getClassIndex()
225 * Whether or not you want AnnotationDB to scan for class level annotations
227 * @param scanClassAnnotations
229 public void setScanClassAnnotations(boolean scanClassAnnotations)
231 this.scanClassAnnotations = scanClassAnnotations;
235 * Wheter or not you want AnnotationDB to scan for method level annotations
237 * @param scanMethodAnnotations
239 public void setScanMethodAnnotations(boolean scanMethodAnnotations)
241 this.scanMethodAnnotations = scanMethodAnnotations;
245 * Whether or not you want AnnotationDB to scan for parameter level annotations
247 * @param scanParameterAnnotations
249 public void setScanParameterAnnotations(boolean scanParameterAnnotations)
251 this.scanParameterAnnotations = scanParameterAnnotations;
255 * Whether or not you want AnnotationDB to scan for parameter level annotations
257 * @param scanFieldAnnotations
259 public void setScanFieldAnnotations(boolean scanFieldAnnotations)
261 this.scanFieldAnnotations = scanFieldAnnotations;
267 * Scan a url that represents an "archive" this is a classpath directory or jar file
269 * @param urls variable list of URLs to scan as archives
270 * @throws IOException
272 public void scanArchives(URL... urls) throws IOException
276 Filter filter = new Filter()
278 public boolean accepts(String filename)
280 if (filename.endsWith(".class"))
282 if (filename.startsWith("/")) filename = filename.substring(1);
283 if (!ignoreScan(filename.replace('/', '.'))) return true;
284 //System.out.println("IGNORED: " + filename);
290 StreamIterator it = IteratorFactory.create(url, filter);
293 while ((stream = it.next()) != null) scanClass(stream);
299 * Parse a .class file for annotations
301 * @param bits input stream pointing to .class file bits
302 * @throws IOException
304 public void scanClass(InputStream bits) throws IOException
306 DataInputStream dstream = new DataInputStream(new BufferedInputStream(bits));
310 cf = new ClassFile(dstream);
311 classIndex.put(cf.getName(), new HashSet<String>());
312 if (scanClassAnnotations) ;
314 if (scanMethodAnnotations || scanParameterAnnotations) scanMethods(cf);
315 if (scanFieldAnnotations) scanFields(cf);
317 // create an index of interfaces the class implements
318 if (cf.getInterfaces() != null)
320 Set<String> intfs = new HashSet<String>();
321 for (String intf : cf.getInterfaces()) intfs.add(intf);
322 implementsIndex.put(cf.getName(), intfs);
333 protected void scanClass(ClassFile cf)
335 String className = cf.getName();
336 AnnotationsAttribute visible = (AnnotationsAttribute) cf.getAttribute(AnnotationsAttribute.visibleTag);
337 AnnotationsAttribute invisible = (AnnotationsAttribute) cf.getAttribute(AnnotationsAttribute.invisibleTag);
339 if (visible != null) populate(visible.getAnnotations(), className);
340 if (invisible != null) populate(invisible.getAnnotations(), className);
344 * Scanns both the method and its parameters for annotations.
348 protected void scanMethods(ClassFile cf)
350 List methods = cf.getMethods();
351 if (methods == null) return;
352 for (Object obj : methods)
354 MethodInfo method = (MethodInfo) obj;
355 if (scanMethodAnnotations)
357 AnnotationsAttribute visible = (AnnotationsAttribute) method.getAttribute(AnnotationsAttribute.visibleTag);
358 AnnotationsAttribute invisible = (AnnotationsAttribute) method.getAttribute(AnnotationsAttribute.invisibleTag);
360 if (visible != null) populate(visible.getAnnotations(), cf.getName());
361 if (invisible != null) populate(invisible.getAnnotations(), cf.getName());
363 if (scanParameterAnnotations)
365 ParameterAnnotationsAttribute paramsVisible = (ParameterAnnotationsAttribute) method.getAttribute(ParameterAnnotationsAttribute.visibleTag);
366 ParameterAnnotationsAttribute paramsInvisible = (ParameterAnnotationsAttribute) method.getAttribute(ParameterAnnotationsAttribute.invisibleTag);
368 if (paramsVisible != null && paramsVisible.getAnnotations() != null)
370 for (Annotation[] anns : paramsVisible.getAnnotations())
372 populate(anns, cf.getName());
375 if (paramsInvisible != null && paramsInvisible.getAnnotations() != null)
377 for (Annotation[] anns : paramsInvisible.getAnnotations())
379 populate(anns, cf.getName());
386 protected void scanFields(ClassFile cf)
388 List fields = cf.getFields();
389 if (fields == null) return;
390 for (Object obj : fields)
392 FieldInfo field = (FieldInfo) obj;
393 AnnotationsAttribute visible = (AnnotationsAttribute) field.getAttribute(AnnotationsAttribute.visibleTag);
394 AnnotationsAttribute invisible = (AnnotationsAttribute) field.getAttribute(AnnotationsAttribute.invisibleTag);
396 if (visible != null) populate(visible.getAnnotations(), cf.getName());
397 if (invisible != null) populate(invisible.getAnnotations(), cf.getName());
403 protected void populate(Annotation[] annotations, String className)
405 if (annotations == null) return;
406 Set<String> classAnnotations = classIndex.get(className);
407 for (Annotation ann : annotations)
409 Set<String> classes = annotationIndex.get(ann.getTypeName());
412 classes = new HashSet<String>();
413 annotationIndex.put(ann.getTypeName(), classes);
415 classes.add(className);
416 classAnnotations.add(ann.getTypeName());
421 * Prints out annotationIndex
425 public void outputAnnotationIndex(PrintWriter writer)
427 for (String ann : annotationIndex.keySet())
431 Set<String> classes = annotationIndex.get(ann);
432 Iterator<String> it = classes.iterator();
435 writer.print(it.next());
436 if (it.hasNext()) writer.print(", ");