initial commit
[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.classpath.Filter;
10 import org.scannotation.classpath.IteratorFactory;
11 import org.scannotation.classpath.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  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
29  * @version $Revision: 1 $
30  */
31 public class AnnotationDB implements Serializable
32 {
33    protected Map<String, Set<String>> annotationIndex = new HashMap<String, Set<String>>();
34    protected Map<String, Set<String>> implementsIndex = new HashMap<String, Set<String>>();
35    protected Map<String, Set<String>> classIndex = new HashMap<String, Set<String>>();
36
37    protected transient boolean scanClassAnnotations = true;
38    protected transient boolean scanMethodAnnotations = true;
39    protected transient boolean scanParameterAnnotations = true;
40    protected transient boolean scanFieldAnnotations = true;
41
42    public class CrossReferenceException extends RuntimeException
43    {
44       private Map<String, Set<String>> unresolved;
45
46       public CrossReferenceException(Map<String, Set<String>> unresolved)
47       {
48          this.unresolved = unresolved;
49       }
50
51       public Map<String, Set<String>> getUnresolved()
52       {
53          return unresolved;
54       }
55    }
56
57    /**
58     * Sometimes you want to see if a particular class implements an interface with certain annotations
59     * After you have loaded all your classpaths with the scanArchive() method, call this method to cross reference
60     * a class's implemented interfaces.  The cross references will be added to the annotationIndex and
61     * classIndex indexes
62     *
63     * @param ignoredPackages var arg list of packages to ignore
64     * @throws CrossReferenceException a RuntimeException thrown if referenced interfaces haven't been scanned
65     */
66    public void crossReferenceImplementedInterfaces(String... ignoredPackages) throws CrossReferenceException
67    {
68       Map<String, Set<String>> unresolved = new HashMap<String, Set<String>>();
69       for (String clazz : implementsIndex.keySet())
70       {
71          Set<String> intfs = implementsIndex.get(clazz);
72          for (String intf : intfs)
73          {
74             if (intf.startsWith("java.") || intf.startsWith("javax.")) continue;
75             boolean ignoreInterface = false;
76             for (String ignored : ignoredPackages)
77             {
78                if (intf.startsWith(ignored + "."))
79                {
80                   ignoreInterface = true;
81                   break;
82                }
83             }
84             if (ignoreInterface) continue;
85
86             Set<String> unresolvedInterfaces = new HashSet<String>();
87             Set<String> xrefAnnotations = classIndex.get(intf);
88             if (xrefAnnotations == null)
89             {
90                unresolvedInterfaces.add(intf);
91                unresolved.put(clazz, unresolvedInterfaces);
92             }
93             Set<String> classAnnotations = classIndex.get(clazz);
94             classAnnotations.addAll(xrefAnnotations);
95             for (String annotation : xrefAnnotations)
96             {
97                Set<String> classes = annotationIndex.get(annotation);
98                classes.add(clazz);
99             }
100          }
101       }
102
103    }
104
105    public Map<String, Set<String>> getAnnotationIndex()
106    {
107       return annotationIndex;
108    }
109
110    public Map<String, Set<String>> getClassIndex()
111    {
112       return classIndex;
113    }
114
115    public void setScanClassAnnotations(boolean scanClassAnnotations)
116    {
117       this.scanClassAnnotations = scanClassAnnotations;
118    }
119
120    public void setScanMethodAnnotations(boolean scanMethodAnnotations)
121    {
122       this.scanMethodAnnotations = scanMethodAnnotations;
123    }
124
125    public void setScanParameterAnnotations(boolean scanParameterAnnotations)
126    {
127       this.scanParameterAnnotations = scanParameterAnnotations;
128    }
129
130    public void setScanFieldAnnotations(boolean scanFieldAnnotations)
131    {
132       this.scanFieldAnnotations = scanFieldAnnotations;
133    }
134
135
136    /**
137     * Scan a url that represents an "archive"  this is a classpath directory or jar file
138     *
139     * @param url
140     * @throws IOException
141     */
142    public void scanArchives(URL... urls) throws IOException
143    {
144       for (URL url : urls)
145       {
146          Filter filter = new Filter()
147          {
148             public boolean accepts(String filename)
149             {
150                return filename.endsWith(".class");
151             }
152          };
153
154          StreamIterator it = IteratorFactory.create(url, filter);
155
156          InputStream stream;
157          while ((stream = it.next()) != null) scanClass(stream);
158       }
159
160    }
161
162    /**
163     * Can a .class file for annotations
164     *
165     * @param bits
166     * @throws IOException
167     */
168    public void scanClass(InputStream bits) throws IOException
169    {
170       DataInputStream dstream = new DataInputStream(new BufferedInputStream(bits));
171       ClassFile cf = null;
172       try
173       {
174          cf = new ClassFile(dstream);
175          classIndex.put(cf.getName(), new HashSet<String>());
176          if (scanClassAnnotations) ;
177          scanClass(cf);
178          if (scanMethodAnnotations || scanParameterAnnotations) scanMethods(cf);
179          if (scanFieldAnnotations) scanFields(cf);
180
181          // create an index of interfaces the class implements
182          if (cf.getInterfaces() != null)
183          {
184             Set<String> intfs = new HashSet<String>();
185             for (String intf : cf.getInterfaces()) intfs.add(intf);
186             implementsIndex.put(cf.getName(), intfs);
187          }
188
189       }
190       finally
191       {
192          dstream.close();
193          bits.close();
194       }
195    }
196
197    protected void scanClass(ClassFile cf)
198    {
199       String className = cf.getName();
200       AnnotationsAttribute visible = (AnnotationsAttribute) cf.getAttribute(AnnotationsAttribute.visibleTag);
201       AnnotationsAttribute invisible = (AnnotationsAttribute) cf.getAttribute(AnnotationsAttribute.invisibleTag);
202
203       if (visible != null) populate(visible.getAnnotations(), className);
204       if (invisible != null) populate(invisible.getAnnotations(), className);
205    }
206
207    /**
208     * Scanns both the method and its parameters for annotations.
209     *
210     * @param cf
211     */
212    protected void scanMethods(ClassFile cf)
213    {
214       List methods = cf.getMethods();
215       if (methods == null) return;
216       for (Object obj : methods)
217       {
218          MethodInfo method = (MethodInfo) obj;
219          if (scanMethodAnnotations)
220          {
221             AnnotationsAttribute visible = (AnnotationsAttribute) method.getAttribute(AnnotationsAttribute.visibleTag);
222             AnnotationsAttribute invisible = (AnnotationsAttribute) method.getAttribute(AnnotationsAttribute.invisibleTag);
223
224             if (visible != null) populate(visible.getAnnotations(), cf.getName());
225             if (invisible != null) populate(invisible.getAnnotations(), cf.getName());
226          }
227          if (scanParameterAnnotations)
228          {
229             ParameterAnnotationsAttribute paramsVisible = (ParameterAnnotationsAttribute) method.getAttribute(ParameterAnnotationsAttribute.visibleTag);
230             ParameterAnnotationsAttribute paramsInvisible = (ParameterAnnotationsAttribute) method.getAttribute(ParameterAnnotationsAttribute.invisibleTag);
231
232             if (paramsVisible != null && paramsVisible.getAnnotations() != null)
233             {
234                for (Annotation[] anns : paramsVisible.getAnnotations())
235                {
236                   populate(anns, cf.getName());
237                }
238             }
239             if (paramsInvisible != null && paramsInvisible.getAnnotations() != null)
240             {
241                for (Annotation[] anns : paramsInvisible.getAnnotations())
242                {
243                   populate(anns, cf.getName());
244                }
245             }
246          }
247       }
248    }
249
250    protected void scanFields(ClassFile cf)
251    {
252       List fields = cf.getFields();
253       if (fields == null) return;
254       for (Object obj : fields)
255       {
256          FieldInfo field = (FieldInfo) obj;
257          AnnotationsAttribute visible = (AnnotationsAttribute) field.getAttribute(AnnotationsAttribute.visibleTag);
258          AnnotationsAttribute invisible = (AnnotationsAttribute) field.getAttribute(AnnotationsAttribute.invisibleTag);
259
260          if (visible != null) populate(visible.getAnnotations(), cf.getName());
261          if (invisible != null) populate(invisible.getAnnotations(), cf.getName());
262
263       }
264
265    }
266
267    protected void populate(Annotation[] annotations, String className)
268    {
269       if (annotations == null) return;
270       Set<String> classAnnotations = classIndex.get(className);
271       for (Annotation ann : annotations)
272       {
273          Set<String> classes = annotationIndex.get(ann.getTypeName());
274          if (classes == null)
275          {
276             classes = new HashSet<String>();
277             annotationIndex.put(ann.getTypeName(), classes);
278          }
279          classes.add(className);
280          classAnnotations.add(ann.getTypeName());
281       }
282    }
283
284    public void outputAnnotationDB(PrintWriter writer)
285    {
286       for (String ann : annotationIndex.keySet())
287       {
288          writer.print(ann);
289          writer.print(": ");
290          Set<String> classes = annotationIndex.get(ann);
291          Iterator<String> it = classes.iterator();
292          while (it.hasNext())
293          {
294             writer.print(it.next());
295             if (it.hasNext()) writer.print(", ");
296          }
297          writer.println();
298       }
299    }
300
301 }