/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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.apache.xbean.finder; import java.io.IOException; import java.io.InputStream; import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.xbean.asm9.original.commons.EmptyVisitor; import org.apache.xbean.finder.util.SingleLinkedList; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.ClassReader; import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.MethodVisitor; /** * @version $Rev$ $Date$ */ public abstract class AbstractFinder implements IAnnotationFinder { private final Map> annotated = new HashMap>(); protected final Map classInfos = new HashMap(); protected final Map originalInfos = new HashMap(); private final List classesNotLoaded = new ArrayList(); private final int ASM_FLAGS = ClassReader.SKIP_CODE + ClassReader.SKIP_DEBUG + ClassReader.SKIP_FRAMES; protected abstract URL getResource(String className); protected abstract Class loadClass(String fixedName) throws ClassNotFoundException; public List getAnnotatedClassNames() { return new ArrayList(originalInfos.keySet()); } /** * The link() method must be called to successfully use the findSubclasses and findImplementations methods * @return * @throws IOException */ public AbstractFinder link() throws IOException { // already linked? if (originalInfos.size() > 0) return this; // keep track of what was originally from the archives originalInfos.putAll(classInfos); for (ClassInfo classInfo : classInfos.values().toArray(new ClassInfo[classInfos.size()])) { linkParent(classInfo); } for (ClassInfo classInfo : classInfos.values().toArray(new ClassInfo[classInfos.size()])) { linkInterfaces(classInfo); } return this; } private void linkParent(ClassInfo classInfo) throws IOException { if (classInfo.superType == null) return; if (classInfo.superType.equals("java.lang.Object")) return; ClassInfo parentInfo = classInfo.superclassInfo; if (parentInfo == null) { parentInfo = classInfos.get(classInfo.superType); if (parentInfo == null) { if (classInfo.clazz != null) { readClassDef(((Class) classInfo.clazz).getSuperclass()); } else { readClassDef(classInfo.superType); } parentInfo = classInfos.get(classInfo.superType); if (parentInfo == null) return; linkParent(parentInfo); } classInfo.superclassInfo = parentInfo; } if (!parentInfo.subclassInfos.contains(classInfo)) { parentInfo.subclassInfos.add(classInfo); } } private void linkInterfaces(ClassInfo classInfo) throws IOException { final List infos = new ArrayList(); if (classInfo.clazz != null){ final Class[] interfaces = classInfo.clazz.getInterfaces(); for (Class clazz : interfaces) { ClassInfo interfaceInfo = classInfos.get(clazz.getName()); if (interfaceInfo == null){ readClassDef(clazz); } interfaceInfo = classInfos.get(clazz.getName()); if (interfaceInfo != null) { infos.add(interfaceInfo); } } } else { for (String className : classInfo.interfaces) { ClassInfo interfaceInfo = classInfos.get(className); if (interfaceInfo == null){ readClassDef(className); } interfaceInfo = classInfos.get(className); if (interfaceInfo != null) { infos.add(interfaceInfo); } } } for (ClassInfo info : infos) { linkInterfaces(info); } } public boolean isAnnotationPresent(Class annotation) { List infos = annotated.get(annotation.getName()); return infos != null && !infos.isEmpty(); } /** * Returns a list of classes that could not be loaded in last invoked findAnnotated* method. *

* The list will only contain entries of classes whose byte code matched the requirements * of last invoked find* method, but were unable to be loaded and included in the results. *

* The list returned is unmodifiable. Once obtained, the returned list will be a live view of the * results from the last findAnnotated* method call. *

* This method is not thread safe. * @return an unmodifiable live view of classes that could not be loaded in previous findAnnotated* call. */ public List getClassesNotLoaded() { return Collections.unmodifiableList(classesNotLoaded); } public List findAnnotatedPackages(Class annotation) { classesNotLoaded.clear(); List packages = new ArrayList(); List infos = getAnnotationInfos(annotation.getName()); for (Info info : infos) { if (info instanceof PackageInfo) { PackageInfo packageInfo = (PackageInfo) info; try { Package pkg = packageInfo.get(); // double check via proper reflection if (pkg.isAnnotationPresent(annotation)) { packages.add(pkg); } } catch (ClassNotFoundException e) { classesNotLoaded.add(packageInfo.getName()); } } } return packages; } public List> findAnnotatedClasses(Class annotation) { classesNotLoaded.clear(); List> classes = new ArrayList>(); List infos = getAnnotationInfos(annotation.getName()); for (Info info : infos) { if (info instanceof ClassInfo) { ClassInfo classInfo = (ClassInfo) info; try { Class clazz = classInfo.get(); // double check via proper reflection if (clazz.isAnnotationPresent(annotation)) { classes.add(clazz); } } catch (ClassNotFoundException e) { classesNotLoaded.add(classInfo.getName()); } } } return classes; } public List>> findMetaAnnotatedClasses(Class annotation) { List> classes = findAnnotatedClasses(annotation); List>> list = new ArrayList>>(); for (final Class clazz : classes) { list.add(new MetaAnnotatedClass(clazz)); } return list; } /** * Naive implementation - works extremelly slow O(n^3) * * @param annotation * @return list of directly or indirectly (inherited) annotated classes */ public List> findInheritedAnnotatedClasses(Class annotation) { classesNotLoaded.clear(); List> classes = new ArrayList>(); List infos = getAnnotationInfos(annotation.getName()); for (Info info : infos) { try { if(info instanceof ClassInfo){ classes.add(((ClassInfo) info).get()); } } catch (ClassNotFoundException cnfe) { // TODO: ignored, but a log message would be appropriate } } boolean annClassFound; List tempClassInfos = new ArrayList(classInfos.values()); do { annClassFound = false; for (int pos = 0; pos < tempClassInfos.size(); pos++) { ClassInfo classInfo = tempClassInfos.get(pos); try { // check whether any superclass is annotated String superType = classInfo.getSuperType(); for (Class clazz : classes) { if (superType.equals(clazz.getName())) { classes.add(classInfo.get()); tempClassInfos.remove(pos); annClassFound = true; break; } } // check whether any interface is annotated List interfces = classInfo.getInterfaces(); for (String interfce: interfces) { for (Class clazz : classes) { if (interfce.replaceFirst("<.*>","").equals(clazz.getName())) { classes.add(classInfo.get()); tempClassInfos.remove(pos); annClassFound = true; break; } } } } catch (ClassNotFoundException e) { classesNotLoaded.add(classInfo.getName()); } catch (NoClassDefFoundError e) { classesNotLoaded.add(classInfo.getName()); } } } while (annClassFound); return classes; } public List findAnnotatedMethods(Class annotation) { classesNotLoaded.clear(); List seen = new ArrayList(); List methods = new ArrayList(); List infos = getAnnotationInfos(annotation.getName()); for (Info info : infos) { if (info instanceof MethodInfo && !info.getName().equals("")) { MethodInfo methodInfo = (MethodInfo) info; ClassInfo classInfo = methodInfo.getDeclaringClass(); if (seen.contains(classInfo)) continue; seen.add(classInfo); try { Class clazz = classInfo.get(); for (Method method : clazz.getDeclaredMethods()) { if (method.isAnnotationPresent(annotation)) { methods.add(method); } } } catch (ClassNotFoundException e) { classesNotLoaded.add(classInfo.getName()); } } } return methods; } public List> findMetaAnnotatedMethods(Class annotation) { List methods = findAnnotatedMethods(annotation); List> list = new ArrayList>(); for (final Method method : methods) { list.add(new MetaAnnotatedMethod(method)); } return list; } public List findAnnotatedConstructors(Class annotation) { classesNotLoaded.clear(); List seen = new ArrayList(); List constructors = new ArrayList(); List infos = getAnnotationInfos(annotation.getName()); for (Info info : infos) { if (info instanceof MethodInfo && info.getName().equals("")) { MethodInfo methodInfo = (MethodInfo) info; ClassInfo classInfo = methodInfo.getDeclaringClass(); if (seen.contains(classInfo)) continue; seen.add(classInfo); try { Class clazz = classInfo.get(); for (Constructor constructor : clazz.getConstructors()) { if (constructor.isAnnotationPresent(annotation)) { constructors.add(constructor); } } } catch (ClassNotFoundException e) { classesNotLoaded.add(classInfo.getName()); } } } return constructors; } public List findAnnotatedFields(Class annotation) { classesNotLoaded.clear(); List seen = new ArrayList(); List fields = new ArrayList(); List infos = getAnnotationInfos(annotation.getName()); for (Info info : infos) { if (info instanceof FieldInfo) { FieldInfo fieldInfo = (FieldInfo) info; ClassInfo classInfo = fieldInfo.getDeclaringClass(); if (seen.contains(classInfo)) continue; seen.add(classInfo); try { Class clazz = classInfo.get(); for (Field field : clazz.getDeclaredFields()) { if (field.isAnnotationPresent(annotation)) { fields.add(field); } } } catch (ClassNotFoundException e) { classesNotLoaded.add(classInfo.getName()); } } } return fields; } public List> findMetaAnnotatedFields(Class annotation) { List fields = findAnnotatedFields(annotation); List> list = new ArrayList>(); for (final Field field : fields) { list.add(new MetaAnnotatedField(field)); } return list; } public List> findClassesInPackage(String packageName, boolean recursive) { classesNotLoaded.clear(); List> classes = new ArrayList>(); for (ClassInfo classInfo : classInfos.values()) { try { if (recursive && classInfo.getPackageName().startsWith(packageName)){ classes.add(classInfo.get()); } else if (classInfo.getPackageName().equals(packageName)){ classes.add(classInfo.get()); } } catch (ClassNotFoundException e) { classesNotLoaded.add(classInfo.getName()); } } return classes; } public List> findSubclasses(Class clazz) { if (clazz == null) throw new NullPointerException("class cannot be null"); classesNotLoaded.clear(); final ClassInfo classInfo = classInfos.get(clazz.getName()); List> found = new ArrayList>(); if (classInfo == null) return found; findSubclasses(classInfo, found, clazz); return found; } private void findSubclasses(ClassInfo classInfo, List> found, Class clazz) { for (ClassInfo subclassInfo : classInfo.subclassInfos) { try { found.add(subclassInfo.get().asSubclass(clazz)); } catch (ClassNotFoundException e) { classesNotLoaded.add(subclassInfo.getName()); } findSubclasses(subclassInfo, found, clazz); } } private List> _findSubclasses(Class clazz) { if (clazz == null) throw new NullPointerException("class cannot be null"); List> classes = new ArrayList>(); for (ClassInfo classInfo : classInfos.values()) { try { if (clazz.getName().equals(classInfo.superType)) { if (clazz.isAssignableFrom(classInfo.get())) { classes.add(classInfo.get().asSubclass(clazz)); classes.addAll(_findSubclasses(classInfo.get().asSubclass(clazz))); } } } catch (ClassNotFoundException e) { classesNotLoaded.add(classInfo.getName()); } } return classes; } public List> findImplementations(Class clazz) { if (clazz == null) throw new NullPointerException("class cannot be null"); if (!clazz.isInterface()) new IllegalArgumentException("class must be an interface"); classesNotLoaded.clear(); final String interfaceName = clazz.getName(); // Collect all interfaces extending the main interface (recursively) // Collect all implementations of interfaces // i.e. all *directly* implementing classes List infos = collectImplementations(interfaceName); // Collect all subclasses of implementations List> classes = new ArrayList>(); for (ClassInfo info : infos) { try { final Class impl = (Class) info.get(); if (clazz.isAssignableFrom(impl)) { classes.add(impl); // Optimization: Don't need to call this method if parent class was already searched classes.addAll(_findSubclasses(impl)); } } catch (ClassNotFoundException e) { classesNotLoaded.add(info.getName()); } } return classes; } private List collectImplementations(String interfaceName) { final List infos = new ArrayList(); for (ClassInfo classInfo : classInfos.values()) { if (classInfo.interfaces.contains(interfaceName)) { infos.add(classInfo); try { final Class clazz = classInfo.get(); if (clazz.isInterface() && !clazz.isAnnotation()) { infos.addAll(collectImplementations(classInfo.name)); } } catch (ClassNotFoundException ignore) { // we'll deal with this later } } } return infos; } protected List getAnnotationInfos(String name) { List infos = annotated.get(name); if (infos == null) { infos = new SingleLinkedList(); annotated.put(name, infos); } return infos; } protected void readClassDef(String className) { int pos = className.indexOf("<"); if (pos > -1) { className = className.substring(0, pos); } pos = className.indexOf(">"); if (pos > -1) { className = className.substring(0, pos); } if (!className.endsWith(".class")) { className = className.replace('.', '/') + ".class"; } try { // TODO: check out META-INF/versions//className URL resource = getResource(className); if (resource != null) { InputStream in = resource.openStream(); try { readClassDef(in); } finally { in.close(); } } else { classesNotLoaded.add(className + " (no resource found for class)"); } } catch (IOException e) { classesNotLoaded.add(className + e.getMessage()); } } protected void readClassDef(InputStream in) throws IOException { readClassDef(in, null); } protected void readClassDef(InputStream in, String path) throws IOException { ClassReader classReader = new ClassReader(in); classReader.accept(new InfoBuildingVisitor(path), ASM_FLAGS); } protected void readClassDef(Class clazz) { List infos = new ArrayList(); Package aPackage = clazz.getPackage(); if (aPackage != null){ final PackageInfo info = new PackageInfo(aPackage); for (AnnotationInfo annotation : info.getAnnotations()) { List annotationInfos = getAnnotationInfos(annotation.getName()); if (!annotationInfos.contains(info)) { annotationInfos.add(info); } } } ClassInfo classInfo = new ClassInfo(clazz); infos.add(classInfo); classInfos.put(clazz.getName(), classInfo); for (Method method : clazz.getDeclaredMethods()) { infos.add(new MethodInfo(classInfo, method)); } for (Constructor constructor : clazz.getConstructors()) { infos.add(new MethodInfo(classInfo, constructor)); } for (Field field : clazz.getDeclaredFields()) { infos.add(new FieldInfo(classInfo, field)); } for (Info info : infos) { for (AnnotationInfo annotation : info.getAnnotations()) { List annotationInfos = getAnnotationInfos(annotation.getName()); annotationInfos.add(info); } } } public class Annotatable { private final List annotations = new ArrayList(); public Annotatable(AnnotatedElement element) { for (Annotation annotation : getAnnotations(element)) { annotations.add(new AnnotationInfo(annotation.annotationType().getName())); } } public Annotatable() { } public List getAnnotations() { return annotations; } /** * Utility method to get around some errors caused by * interactions between the Equinox class loaders and * the OpenJPA transformation process. There is a window * where the OpenJPA transformation process can cause * an annotation being processed to get defined in a * classloader during the actual defineClass call for * that very class (e.g., recursively). This results in * a LinkageError exception. If we see one of these, * retry the request. Since the annotation will be * defined on the second pass, this should succeed. If * we get a second exception, then it's likely some * other problem. * * @param element The AnnotatedElement we need information for. * * @return An array of the Annotations defined on the element. */ private Annotation[] getAnnotations(AnnotatedElement element) { try { return element.getAnnotations(); } catch (LinkageError e) { return element.getAnnotations(); } } } public static interface Info { String getName(); List getAnnotations(); } public class PackageInfo extends Annotatable implements Info { private final String name; private final ClassInfo info; private final Package pkg; public PackageInfo(Package pkg){ super(pkg); this.pkg = pkg; this.name = pkg.getName(); this.info = null; } public PackageInfo(String name) { info = new ClassInfo(name, null); this.name = name; this.pkg = null; } public String getName() { return name; } public Package get() throws ClassNotFoundException { return (pkg != null)?pkg:info.get().getPackage(); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; PackageInfo that = (PackageInfo) o; if (name != null ? !name.equals(that.name) : that.name != null) return false; return true; } @Override public int hashCode() { return name != null ? name.hashCode() : 0; } } public class ClassInfo extends Annotatable implements Info { private String name; private final List methods = new SingleLinkedList(); private final List constructors = new SingleLinkedList(); private String superType; private ClassInfo superclassInfo; private final List subclassInfos = new SingleLinkedList(); private final List interfaces = new SingleLinkedList(); private final List fields = new SingleLinkedList(); //e.g. bundle class path prefix. private String path; private Class clazz; public ClassInfo(Class clazz) { super(clazz); this.clazz = clazz; this.name = clazz.getName(); Class superclass = clazz.getSuperclass(); this.superType = superclass != null ? superclass.getName(): null; for (Class intrface : clazz.getInterfaces()) { this.interfaces.add(intrface.getName()); } } public ClassInfo(String name, String superType) { this.name = name; this.superType = superType; } public String getPackageName(){ return name.indexOf(".") > 0 ? name.substring(0, name.lastIndexOf(".")) : "" ; } public List getConstructors() { return constructors; } public List getInterfaces() { return interfaces; } public List getFields() { return fields; } public List getMethods() { return methods; } public String getName() { return name; } public String getSuperType() { return superType; } public Class get() throws ClassNotFoundException { if (clazz != null) return clazz; try { String fixedName = name.replaceFirst("<.*>", ""); this.clazz = loadClass(fixedName); return clazz; } catch (ClassNotFoundException notFound) { classesNotLoaded.add(name); throw notFound; } } public String toString() { return name; } public String getPath() { return path; } } public class MethodInfo extends Annotatable implements Info { private final ClassInfo declaringClass; private final String returnType; private final String name; private final List> parameterAnnotations = new ArrayList>(); public MethodInfo(ClassInfo info, Constructor constructor){ super(constructor); this.declaringClass = info; this.name = ""; this.returnType = Void.TYPE.getName(); } public MethodInfo(ClassInfo info, Method method){ super(method); this.declaringClass = info; this.name = method.getName(); this.returnType = method.getReturnType().getName(); } public MethodInfo(ClassInfo declarignClass, String name, String returnType) { this.declaringClass = declarignClass; this.name = name; this.returnType = returnType; } public List> getParameterAnnotations() { return parameterAnnotations; } public List getParameterAnnotations(int index) { if (index >= parameterAnnotations.size()) { for (int i = parameterAnnotations.size(); i <= index; i++) { List annotationInfos = new ArrayList(); parameterAnnotations.add(i, annotationInfos); } } return parameterAnnotations.get(index); } public String getName() { return name; } public ClassInfo getDeclaringClass() { return declaringClass; } public String getReturnType() { return returnType; } public String toString() { return declaringClass + "@" + name; } } public class FieldInfo extends Annotatable implements Info { private final String name; private final String type; private final ClassInfo declaringClass; public FieldInfo(ClassInfo info, Field field){ super(field); this.declaringClass = info; this.name = field.getName(); this.type = field.getType().getName(); } public FieldInfo(ClassInfo declaringClass, String name, String type) { this.declaringClass = declaringClass; this.name = name; this.type = type; } public String getName() { return name; } public ClassInfo getDeclaringClass() { return declaringClass; } public String getType() { return type; } public String toString() { return declaringClass + "#" + name; } } public class AnnotationInfo extends Annotatable implements Info { private final String name; public AnnotationInfo(Annotation annotation){ this(annotation.getClass().getName()); } public AnnotationInfo(Class annotation) { this.name = annotation.getName().intern(); } public AnnotationInfo(String name) { name = name.replaceAll("^L|;$", ""); name = name.replace('/', '.'); this.name = name.intern(); } public String getName() { return name; } public String toString() { return name; } } public class InfoBuildingVisitor extends EmptyVisitor { private Info info; private String path; public InfoBuildingVisitor(String path) { this.path = path; } public InfoBuildingVisitor(Info info) { this.info = info; } @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { if (name.endsWith("package-info")) { info = new PackageInfo(javaName(name)); } else { ClassInfo classInfo = new ClassInfo(javaName(name), javaName(superName)); classInfo.path = path; // if (signature == null) { for (String interfce : interfaces) { classInfo.getInterfaces().add(javaName(interfce)); } // } else { // // the class uses generics // new SignatureReader(signature).accept(new GenericAwareInfoBuildingVisitor(GenericAwareInfoBuildingVisitor.TYPE.CLASS, classInfo)); // } info = classInfo; classInfos.put(classInfo.getName(), classInfo); } } private String javaName(String name) { return (name == null)? null:name.replace('/', '.'); } @Override public AnnotationVisitor visitAnnotation(String desc, boolean visible) { AnnotationInfo annotationInfo = new AnnotationInfo(desc); info.getAnnotations().add(annotationInfo); getAnnotationInfos(annotationInfo.getName()).add(info); return new InfoBuildingVisitor(annotationInfo).annotationVisitor(); } @Override public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { ClassInfo classInfo = ((ClassInfo) info); FieldInfo fieldInfo = new FieldInfo(classInfo, name, desc); classInfo.getFields().add(fieldInfo); return new InfoBuildingVisitor(fieldInfo).fieldVisitor(); } @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { ClassInfo classInfo = ((ClassInfo) info); MethodInfo methodInfo = new MethodInfo(classInfo, name, desc); classInfo.getMethods().add(methodInfo); return new InfoBuildingVisitor(methodInfo).methodVisitor(); } @Override public AnnotationVisitor visitMethodParameterAnnotation(int param, String desc, boolean visible) { MethodInfo methodInfo = ((MethodInfo) info); List annotationInfos = methodInfo.getParameterAnnotations(param); AnnotationInfo annotationInfo = new AnnotationInfo(desc); annotationInfos.add(annotationInfo); return new InfoBuildingVisitor(annotationInfo).annotationVisitor(); } } }