/* * Copyright 2014 cruxframework.org. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package org.cruxframework.crux.scanner; 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; 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.cruxframework.crux.scanner.ScannerRegistration.ScannerMatch; import org.cruxframework.crux.scanner.Scanners.ScannerCallback; import org.cruxframework.crux.scanner.archiveiterator.Filter; /** * A class scanner that builds an index of classes by annotations and * implemented interfaces. * * Based on Scannotation library from Bill Burke. * * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author Thiago da Rosa de Bustamante */ public class AnnotationDB extends AbstractScanner implements Serializable { private static final long serialVersionUID = 7685125058283200626L; protected Map<String, Set<String>> annotationIndex = new HashMap<String, Set<String>>(); protected Map<String, Set<String>> implementsIndex = new HashMap<String, Set<String>>(); protected Map<String, Set<String>> classIndex = new HashMap<String, Set<String>>(); protected Set<URL> scannedURLs = new HashSet<URL>(); protected Map<String, Set<String>> interfacesIndex = new HashMap<String, Set<String>>(); protected Map<String, String> superClasses = new HashMap<String, String>(); protected Map<String, Set<String>> classInterfaces = new HashMap<String, Set<String>>(); protected transient boolean scanMethodAnnotations = true; protected transient boolean scanParameterAnnotations = true; protected transient boolean scanFieldAnnotations = true; /** * 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<String, Set<String>> 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<String, Set<String>> getClassIndex() { return classIndex; } /** * * @return */ public Map<String, Set<String>> getInterfacesIndex() { return interfacesIndex; } /** * 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; } @Override public Filter getScannerFilter() { return new Filter() { public boolean accepts(String fileName) { if (fileName.endsWith(".class")) { return true; } return false; } }; } @Override public ScannerCallback getScannerCallback() { return new ScannerCallback() { @Override public void onFound(List<ScannerMatch> scanResult) { for (ScannerMatch match : scanResult) { URL found = match.getMatch(); if (!scannedURLs.contains(found)) { scannedURLs.add(found); URLStreamManager manager = new URLStreamManager(found); try { scanClass(manager.open()); } catch (IOException e) { throw new ScannerException("Error creating index of annotations.", e); } manager.close(); } } ClassScanner.setInitialized(); populateInterfacesFromSuperClass(); } }; } /** * 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 */ public void scanArchives() { annotationIndex.clear(); implementsIndex.clear(); classIndex.clear(); scannedURLs.clear(); interfacesIndex.clear(); superClasses.clear(); classInterfaces.clear(); runScanner(); } @Override public void resetScanner() { annotationIndex.clear(); implementsIndex.clear(); classIndex.clear(); scannedURLs.clear(); interfacesIndex.clear(); superClasses.clear(); classInterfaces.clear(); ClassScanner.reset(); } /** * 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<String>()); scanClass(cf); if (scanMethodAnnotations || scanParameterAnnotations) { scanMethods(cf); } if (scanFieldAnnotations) { scanFields(cf); } // create an index of interfaces the class implements if (cf.getInterfaces() != null) { Set<String> intfs = new HashSet<String>(); for (String intf : cf.getInterfaces()) { intfs.add(intf); } implementsIndex.put(cf.getName(), intfs); } } finally { dstream.close(); bits.close(); } } protected void populateInterfaces(ClassFile cf) { String className = cf.getName(); String superClassName = cf.getSuperclass(); populateInterfaces(cf.getInterfaces(), className); superClasses.put(className, superClassName); } protected void populateInterfaces(String[] interfaces, String className) { if (interfaces == null) { return; } Set<String> classesIndex = classIndex.get(className); Set<String> classesInterfaces = classInterfaces.get(className); if (classesInterfaces == null) { classesInterfaces = new HashSet<String>(); classInterfaces.put(className, classesInterfaces); } for (String str : interfaces) { Set<String> classes = interfacesIndex.get(str); if (classes == null) { classes = new HashSet<String>(); interfacesIndex.put(str, classes); } classes.add(className); classesIndex.add(str); classesInterfaces.add(str); } } protected void populateInterfacesFromSuperClass() { Set<String> processedClasses = new HashSet<String>(); for (String className : superClasses.keySet()) { Set<String> interfacesFromSuperClass = getInterfacesFromSuperClass(className, processedClasses); if (interfacesFromSuperClass != null) { for (String interfaceName : interfacesFromSuperClass) { Set<String> classes = interfacesIndex.get(interfaceName); if (classes == null) { classes = new HashSet<String>(); interfacesIndex.put(interfaceName, classes); } classes.add(className); } } } } protected Set<String> getInterfacesFromSuperClass(String className, Set<String> processedClasses) { if (processedClasses.contains(className)) { return classInterfaces.get(className); } else { Set<String> result = new HashSet<String>(); String superClassName = superClasses.get(className); if (superClassName != null) { Set<String> superClassesInterfaces = classInterfaces.get(superClassName); if (superClassesInterfaces != null) { result.addAll(superClassesInterfaces); } Set<String> interfacesFromSuperClass = getInterfacesFromSuperClass(superClassName, processedClasses); if (interfacesFromSuperClass != null && interfacesFromSuperClass.size() > 0) { result.addAll(interfacesFromSuperClass); } } Set<String> interfaces = classInterfaces.get(className); if (result.size() > 0) { if (interfaces == null) { interfaces = new HashSet<String>(); classInterfaces.put(className, interfaces); } interfaces.addAll(result); } processedClasses.add(className); return result; } } 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); } populateInterfaces(cf); } /** * 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<String> classAnnotations = classIndex.get(className); for (Annotation ann : annotations) { Set<String> classes = annotationIndex.get(ann.getTypeName()); if (classes == null) { classes = new HashSet<String>(); 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<String> classes = annotationIndex.get(ann); Iterator<String> it = classes.iterator(); while (it.hasNext()) { writer.print(it.next()); if (it.hasNext()) { writer.print(", "); } } writer.println(); } } }