org.scannotation.AnnotationDB Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of scannotation Show documentation
Show all versions of scannotation Show documentation
Scannotation is a Java library that creates an annotation database from a set of .class files
The newest version!
package org.scannotation;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.ClassFile;
import javassist.bytecode.FieldInfo;
import javassist.bytecode.MethodInfo;
import javassist.bytecode.ParameterAnnotationsAttribute;
import javassist.bytecode.annotation.Annotation;
import org.scannotation.archiveiterator.Filter;
import org.scannotation.archiveiterator.IteratorFactory;
import org.scannotation.archiveiterator.StreamIterator;
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.Serializable;
import java.net.URL;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* The class allows you to scan an arbitrary set of "archives" for .class files. These class files
* are parsed to see what annotations they use. Two indexes are created. The javax, java, sun, com.sun, and javassist
* packages will not be scanned by default.
*
* One is a map of annotations and what classes
* use those annotations. This could be used, for example, by an EJB deployer to find all the EJBs contained
* in the archive
*
* Another is a mpa of classes and what annotations those classes use.
*
* @author Bill Burke
* @version $Revision: 1 $
*/
public class AnnotationDB implements Serializable
{
protected Map> annotationIndex = new HashMap>();
protected Map> implementsIndex = new HashMap>();
protected Map> classIndex = new HashMap>();
protected transient boolean scanClassAnnotations = true;
protected transient boolean scanMethodAnnotations = true;
protected transient boolean scanParameterAnnotations = true;
protected transient boolean scanFieldAnnotations = true;
protected transient String[] ignoredPackages = {"javax", "java", "sun", "com.sun", "javassist"};
public class CrossReferenceException extends Exception
{
private Set unresolved;
public CrossReferenceException(Set unresolved)
{
this.unresolved = unresolved;
}
public Set getUnresolved()
{
return unresolved;
}
}
public String[] getIgnoredPackages()
{
return ignoredPackages;
}
/**
* Override/overwrite any ignored packages
*
* @param ignoredPackages cannot be null
*/
public void setIgnoredPackages(String[] ignoredPackages)
{
this.ignoredPackages = ignoredPackages;
}
public void addIgnoredPackages(String... ignored)
{
String[] tmp = new String[ignoredPackages.length + ignored.length];
int i = 0;
for (String ign : ignoredPackages) tmp[i++] = ign;
for (String ign : ignored) tmp[i++] = ign;
}
/**
* This method will cross reference annotations in the annotation index with any meta-annotations that they have
* and create additional entries as needed. For example:
*
* @HttpMethod("GET") public @interface GET {}
*
* The HttpMethod index will have additional classes added to it for any classes annotated with annotations that
* have the HttpMethod meta-annotation.
*
* WARNING: If the annotation class has not already been scaned, this method will load all annotation classes indexed
* as a resource so they must be in your classpath
*/
public void crossReferenceMetaAnnotations() throws CrossReferenceException
{
Set unresolved = new HashSet();
Set index = new HashSet();
index.addAll(annotationIndex.keySet());
for (String annotation : index)
{
if (ignoreScan(annotation))
{
continue;
}
if (classIndex.containsKey(annotation))
{
for (String xref : classIndex.get(annotation))
{
annotationIndex.get(xref).addAll(annotationIndex.get(annotation));
}
continue;
}
InputStream bits = Thread.currentThread().getContextClassLoader().getResourceAsStream(annotation.replace('.', '/') + ".class");
if (bits == null)
{
unresolved.add(annotation);
continue;
}
try
{
scanClass(bits);
}
catch (IOException e)
{
unresolved.add(annotation);
}
for (String xref : classIndex.get(annotation))
{
annotationIndex.get(xref).addAll(annotationIndex.get(annotation));
}
}
if (unresolved.size() > 0) throw new CrossReferenceException(unresolved);
}
/**
* Sometimes you want to see if a particular class implements an interface with certain annotations
* After you have loaded all your classpaths with the scanArchive() method, call this method to cross reference
* a class's implemented interfaces. The cross references will be added to the annotationIndex and
* classIndex indexes
*
* @param ignoredPackages var arg list of packages to ignore
* @throws CrossReferenceException an Exception thrown if referenced interfaces haven't been scanned
*/
public void crossReferenceImplementedInterfaces() throws CrossReferenceException
{
Set unresolved = new HashSet();
for (String clazz : implementsIndex.keySet())
{
Set intfs = implementsIndex.get(clazz);
for (String intf : intfs)
{
if (ignoreScan(intf)) continue;
Set xrefAnnotations = classIndex.get(intf);
if (xrefAnnotations == null)
{
unresolved.add(intf);
}
else
{
Set classAnnotations = classIndex.get(clazz);
if (classAnnotations == null)
{
classIndex.put(clazz, xrefAnnotations);
}
else classAnnotations.addAll(xrefAnnotations);
for (String annotation : xrefAnnotations)
{
Set classes = annotationIndex.get(annotation);
classes.add(clazz);
}
}
}
}
if (unresolved.size() > 0) throw new CrossReferenceException(unresolved);
}
private boolean ignoreScan(String intf)
{
for (String ignored : ignoredPackages)
{
if (intf.startsWith(ignored + "."))
{
return true;
}
else
{
//System.out.println("NOT IGNORING: " + intf);
}
}
return false;
}
/**
* returns a map keyed by the fully qualified string name of a annotation class. The Set returne is
* a list of classes that use that annotation somehow.
*/
public Map> getAnnotationIndex()
{
return annotationIndex;
}
/**
* returns a map keyed by the list of classes scanned. The value set returned is a list of annotations
* used by that class.
*/
public Map> getClassIndex()
{
return classIndex;
}
/**
* Whether or not you want AnnotationDB to scan for class level annotations
*
* @param scanClassAnnotations
*/
public void setScanClassAnnotations(boolean scanClassAnnotations)
{
this.scanClassAnnotations = scanClassAnnotations;
}
/**
* Wheter or not you want AnnotationDB to scan for method level annotations
*
* @param scanMethodAnnotations
*/
public void setScanMethodAnnotations(boolean scanMethodAnnotations)
{
this.scanMethodAnnotations = scanMethodAnnotations;
}
/**
* Whether or not you want AnnotationDB to scan for parameter level annotations
*
* @param scanParameterAnnotations
*/
public void setScanParameterAnnotations(boolean scanParameterAnnotations)
{
this.scanParameterAnnotations = scanParameterAnnotations;
}
/**
* Whether or not you want AnnotationDB to scan for parameter level annotations
*
* @param scanFieldAnnotations
*/
public void setScanFieldAnnotations(boolean scanFieldAnnotations)
{
this.scanFieldAnnotations = scanFieldAnnotations;
}
/**
* Scan a url that represents an "archive" this is a classpath directory or jar file
*
* @param urls variable list of URLs to scan as archives
* @throws IOException
*/
public void scanArchives(URL... urls) throws IOException
{
for (URL url : urls)
{
Filter filter = new Filter()
{
public boolean accepts(String filename)
{
if (filename.endsWith(".class"))
{
if (filename.startsWith("/")) filename = filename.substring(1);
if (!ignoreScan(filename.replace('/', '.'))) return true;
//System.out.println("IGNORED: " + filename);
}
return false;
}
};
StreamIterator it = IteratorFactory.create(url, filter);
InputStream stream;
while ((stream = it.next()) != null) scanClass(stream);
}
}
/**
* Parse a .class file for annotations
*
* @param bits input stream pointing to .class file bits
* @throws IOException
*/
public void scanClass(InputStream bits) throws IOException
{
DataInputStream dstream = new DataInputStream(new BufferedInputStream(bits));
ClassFile cf = null;
try
{
cf = new ClassFile(dstream);
classIndex.put(cf.getName(), new HashSet());
if (scanClassAnnotations) ;
scanClass(cf);
if (scanMethodAnnotations || scanParameterAnnotations) scanMethods(cf);
if (scanFieldAnnotations) scanFields(cf);
// create an index of interfaces the class implements
if (cf.getInterfaces() != null)
{
Set intfs = new HashSet();
for (String intf : cf.getInterfaces()) intfs.add(intf);
implementsIndex.put(cf.getName(), intfs);
}
}
finally
{
dstream.close();
bits.close();
}
}
protected void scanClass(ClassFile cf)
{
String className = cf.getName();
AnnotationsAttribute visible = (AnnotationsAttribute) cf.getAttribute(AnnotationsAttribute.visibleTag);
AnnotationsAttribute invisible = (AnnotationsAttribute) cf.getAttribute(AnnotationsAttribute.invisibleTag);
if (visible != null) populate(visible.getAnnotations(), className);
if (invisible != null) populate(invisible.getAnnotations(), className);
}
/**
* Scanns both the method and its parameters for annotations.
*
* @param cf
*/
protected void scanMethods(ClassFile cf)
{
List methods = cf.getMethods();
if (methods == null) return;
for (Object obj : methods)
{
MethodInfo method = (MethodInfo) obj;
if (scanMethodAnnotations)
{
AnnotationsAttribute visible = (AnnotationsAttribute) method.getAttribute(AnnotationsAttribute.visibleTag);
AnnotationsAttribute invisible = (AnnotationsAttribute) method.getAttribute(AnnotationsAttribute.invisibleTag);
if (visible != null) populate(visible.getAnnotations(), cf.getName());
if (invisible != null) populate(invisible.getAnnotations(), cf.getName());
}
if (scanParameterAnnotations)
{
ParameterAnnotationsAttribute paramsVisible = (ParameterAnnotationsAttribute) method.getAttribute(ParameterAnnotationsAttribute.visibleTag);
ParameterAnnotationsAttribute paramsInvisible = (ParameterAnnotationsAttribute) method.getAttribute(ParameterAnnotationsAttribute.invisibleTag);
if (paramsVisible != null && paramsVisible.getAnnotations() != null)
{
for (Annotation[] anns : paramsVisible.getAnnotations())
{
populate(anns, cf.getName());
}
}
if (paramsInvisible != null && paramsInvisible.getAnnotations() != null)
{
for (Annotation[] anns : paramsInvisible.getAnnotations())
{
populate(anns, cf.getName());
}
}
}
}
}
protected void scanFields(ClassFile cf)
{
List fields = cf.getFields();
if (fields == null) return;
for (Object obj : fields)
{
FieldInfo field = (FieldInfo) obj;
AnnotationsAttribute visible = (AnnotationsAttribute) field.getAttribute(AnnotationsAttribute.visibleTag);
AnnotationsAttribute invisible = (AnnotationsAttribute) field.getAttribute(AnnotationsAttribute.invisibleTag);
if (visible != null) populate(visible.getAnnotations(), cf.getName());
if (invisible != null) populate(invisible.getAnnotations(), cf.getName());
}
}
protected void populate(Annotation[] annotations, String className)
{
if (annotations == null) return;
Set classAnnotations = classIndex.get(className);
for (Annotation ann : annotations)
{
Set classes = annotationIndex.get(ann.getTypeName());
if (classes == null)
{
classes = new HashSet();
annotationIndex.put(ann.getTypeName(), classes);
}
classes.add(className);
classAnnotations.add(ann.getTypeName());
}
}
/**
* Prints out annotationIndex
*
* @param writer
*/
public void outputAnnotationIndex(PrintWriter writer)
{
for (String ann : annotationIndex.keySet())
{
writer.print(ann);
writer.print(": ");
Set classes = annotationIndex.get(ann);
Iterator it = classes.iterator();
while (it.hasNext())
{
writer.print(it.next());
if (it.hasNext()) writer.print(", ");
}
writer.println();
}
}
}