Added documentation
[scannotation] / src / main / java / org / scannotation / AnnotationDB.java
1 package org.scannotation;
2
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;
12
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;
19 import java.net.URL;
20 import java.util.HashMap;
21 import java.util.HashSet;
22 import java.util.Iterator;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.Set;
26
27 /**
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.
30  *
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
33  * in the archive
34  *
35  * Another is a mpa of classes and what annotations those classes use.
36  *
37  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
38  * @version $Revision: 1 $
39  */
40 public class AnnotationDB implements Serializable
41 {
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>>();
45
46    protected transient boolean scanClassAnnotations = true;
47    protected transient boolean scanMethodAnnotations = true;
48    protected transient boolean scanParameterAnnotations = true;
49    protected transient boolean scanFieldAnnotations = true;
50
51    public class CrossReferenceException extends RuntimeException
52    {
53       private Map<String, Set<String>> unresolved;
54
55       public CrossReferenceException(Map<String, Set<String>> unresolved)
56       {
57          this.unresolved = unresolved;
58       }
59
60       public Map<String, Set<String>> getUnresolved()
61       {
62          return unresolved;
63       }
64    }
65
66    /**
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
70     * classIndex indexes
71     *
72     * @param ignoredPackages var arg list of packages to ignore
73     * @throws CrossReferenceException a RuntimeException thrown if referenced interfaces haven't been scanned
74     */
75    public void crossReferenceImplementedInterfaces(String... ignoredPackages) throws CrossReferenceException
76    {
77       Map<String, Set<String>> unresolved = new HashMap<String, Set<String>>();
78       for (String clazz : implementsIndex.keySet())
79       {
80          Set<String> intfs = implementsIndex.get(clazz);
81          for (String intf : intfs)
82          {
83             if (intf.startsWith("java.") || intf.startsWith("javax.")) continue;
84             boolean ignoreInterface = false;
85             for (String ignored : ignoredPackages)
86             {
87                if (intf.startsWith(ignored + "."))
88                {
89                   ignoreInterface = true;
90                   break;
91                }
92             }
93             if (ignoreInterface) continue;
94
95             Set<String> unresolvedInterfaces = new HashSet<String>();
96             Set<String> xrefAnnotations = classIndex.get(intf);
97             if (xrefAnnotations == null)
98             {
99                unresolvedInterfaces.add(intf);
100                unresolved.put(clazz, unresolvedInterfaces);
101             }
102             Set<String> classAnnotations = classIndex.get(clazz);
103             classAnnotations.addAll(xrefAnnotations);
104             for (String annotation : xrefAnnotations)
105             {
106                Set<String> classes = annotationIndex.get(annotation);
107                classes.add(clazz);
108             }
109          }
110       }
111
112    }
113
114    /**
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.
117     *
118     */
119    public Map<String, Set<String>> getAnnotationIndex()
120    {
121       return annotationIndex;
122    }
123
124    /**
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.
127     *
128     */
129    public Map<String, Set<String>> getClassIndex()
130    {
131       return classIndex;
132    }
133
134
135    /**
136     * Whether or not you want AnnotationDB to scan for class level annotations
137     *
138     * @param scanClassAnnotations
139     */
140    public void setScanClassAnnotations(boolean scanClassAnnotations)
141    {
142       this.scanClassAnnotations = scanClassAnnotations;
143    }
144
145    /**
146     * Wheter or not you want AnnotationDB to scan for method level annotations
147     *
148     * @param scanMethodAnnotations
149     */
150    public void setScanMethodAnnotations(boolean scanMethodAnnotations)
151    {
152       this.scanMethodAnnotations = scanMethodAnnotations;
153    }
154
155    /**
156     * Whether or not you want AnnotationDB to scan for parameter level annotations
157     *
158     * @param scanParameterAnnotations
159     */
160    public void setScanParameterAnnotations(boolean scanParameterAnnotations)
161    {
162       this.scanParameterAnnotations = scanParameterAnnotations;
163    }
164
165    /**
166     * Whether or not you want AnnotationDB to scan for parameter level annotations
167     *
168     * @param scanFieldAnnotations
169     */
170    public void setScanFieldAnnotations(boolean scanFieldAnnotations)
171    {
172       this.scanFieldAnnotations = scanFieldAnnotations;
173    }
174
175
176    /**
177     * Scan a url that represents an "archive"  this is a classpath directory or jar file
178     *
179     * @param urls variable list of URLs to scan as archives
180     * @throws IOException
181     */
182    public void scanArchives(URL... urls) throws IOException
183    {
184       for (URL url : urls)
185       {
186          Filter filter = new Filter()
187          {
188             public boolean accepts(String filename)
189             {
190                return filename.endsWith(".class");
191             }
192          };
193
194          StreamIterator it = IteratorFactory.create(url, filter);
195
196          InputStream stream;
197          while ((stream = it.next()) != null) scanClass(stream);
198       }
199
200    }
201
202    /**
203     * Parse a .class file for annotations
204     *
205     * @param bits input stream pointing to .class file bits
206     * @throws IOException
207     */
208    public void scanClass(InputStream bits) throws IOException
209    {
210       DataInputStream dstream = new DataInputStream(new BufferedInputStream(bits));
211       ClassFile cf = null;
212       try
213       {
214          cf = new ClassFile(dstream);
215          classIndex.put(cf.getName(), new HashSet<String>());
216          if (scanClassAnnotations) ;
217          scanClass(cf);
218          if (scanMethodAnnotations || scanParameterAnnotations) scanMethods(cf);
219          if (scanFieldAnnotations) scanFields(cf);
220
221          // create an index of interfaces the class implements
222          if (cf.getInterfaces() != null)
223          {
224             Set<String> intfs = new HashSet<String>();
225             for (String intf : cf.getInterfaces()) intfs.add(intf);
226             implementsIndex.put(cf.getName(), intfs);
227          }
228
229       }
230       finally
231       {
232          dstream.close();
233          bits.close();
234       }
235    }
236
237    protected void scanClass(ClassFile cf)
238    {
239       String className = cf.getName();
240       AnnotationsAttribute visible = (AnnotationsAttribute) cf.getAttribute(AnnotationsAttribute.visibleTag);
241       AnnotationsAttribute invisible = (AnnotationsAttribute) cf.getAttribute(AnnotationsAttribute.invisibleTag);
242
243       if (visible != null) populate(visible.getAnnotations(), className);
244       if (invisible != null) populate(invisible.getAnnotations(), className);
245    }
246
247    /**
248     * Scanns both the method and its parameters for annotations.
249     *
250     * @param cf
251     */
252    protected void scanMethods(ClassFile cf)
253    {
254       List methods = cf.getMethods();
255       if (methods == null) return;
256       for (Object obj : methods)
257       {
258          MethodInfo method = (MethodInfo) obj;
259          if (scanMethodAnnotations)
260          {
261             AnnotationsAttribute visible = (AnnotationsAttribute) method.getAttribute(AnnotationsAttribute.visibleTag);
262             AnnotationsAttribute invisible = (AnnotationsAttribute) method.getAttribute(AnnotationsAttribute.invisibleTag);
263
264             if (visible != null) populate(visible.getAnnotations(), cf.getName());
265             if (invisible != null) populate(invisible.getAnnotations(), cf.getName());
266          }
267          if (scanParameterAnnotations)
268          {
269             ParameterAnnotationsAttribute paramsVisible = (ParameterAnnotationsAttribute) method.getAttribute(ParameterAnnotationsAttribute.visibleTag);
270             ParameterAnnotationsAttribute paramsInvisible = (ParameterAnnotationsAttribute) method.getAttribute(ParameterAnnotationsAttribute.invisibleTag);
271
272             if (paramsVisible != null && paramsVisible.getAnnotations() != null)
273             {
274                for (Annotation[] anns : paramsVisible.getAnnotations())
275                {
276                   populate(anns, cf.getName());
277                }
278             }
279             if (paramsInvisible != null && paramsInvisible.getAnnotations() != null)
280             {
281                for (Annotation[] anns : paramsInvisible.getAnnotations())
282                {
283                   populate(anns, cf.getName());
284                }
285             }
286          }
287       }
288    }
289
290    protected void scanFields(ClassFile cf)
291    {
292       List fields = cf.getFields();
293       if (fields == null) return;
294       for (Object obj : fields)
295       {
296          FieldInfo field = (FieldInfo) obj;
297          AnnotationsAttribute visible = (AnnotationsAttribute) field.getAttribute(AnnotationsAttribute.visibleTag);
298          AnnotationsAttribute invisible = (AnnotationsAttribute) field.getAttribute(AnnotationsAttribute.invisibleTag);
299
300          if (visible != null) populate(visible.getAnnotations(), cf.getName());
301          if (invisible != null) populate(invisible.getAnnotations(), cf.getName());
302
303       }
304
305    }
306
307    protected void populate(Annotation[] annotations, String className)
308    {
309       if (annotations == null) return;
310       Set<String> classAnnotations = classIndex.get(className);
311       for (Annotation ann : annotations)
312       {
313          Set<String> classes = annotationIndex.get(ann.getTypeName());
314          if (classes == null)
315          {
316             classes = new HashSet<String>();
317             annotationIndex.put(ann.getTypeName(), classes);
318          }
319          classes.add(className);
320          classAnnotations.add(ann.getTypeName());
321       }
322    }
323
324    /**
325     * Prints out annotationIndex
326     *
327     * @param writer
328     */
329    public void outputAnnotationIndex(PrintWriter writer)
330    {
331       for (String ann : annotationIndex.keySet())
332       {
333          writer.print(ann);
334          writer.print(": ");
335          Set<String> classes = annotationIndex.get(ann);
336          Iterator<String> it = classes.iterator();
337          while (it.hasNext())
338          {
339             writer.print(it.next());
340             if (it.hasNext()) writer.print(", ");
341          }
342          writer.println();
343       }
344    }
345
346 }