You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tomee.apache.org by rm...@apache.org on 2012/02/16 22:31:43 UTC

svn commit: r1245191 - in /openejb/trunk/sandbox/tools: ./ src/main/java/org/apache/openejb/tools/doc/ src/main/java/org/apache/openejb/tools/doc/property/ src/main/resources/doc/

Author: rmannibucau
Date: Thu Feb 16 21:31:42 2012
New Revision: 1245191

URL: http://svn.apache.org/viewvc?rev=1245191&view=rev
Log:
some work to be able to get doc info

Added:
    openejb/trunk/sandbox/tools/src/main/java/org/apache/openejb/tools/doc/
    openejb/trunk/sandbox/tools/src/main/java/org/apache/openejb/tools/doc/property/
    openejb/trunk/sandbox/tools/src/main/java/org/apache/openejb/tools/doc/property/AnnotationFinder.java
    openejb/trunk/sandbox/tools/src/main/java/org/apache/openejb/tools/doc/property/AnnotationInfo.java
    openejb/trunk/sandbox/tools/src/main/java/org/apache/openejb/tools/doc/property/AnnotationInstanceInfo.java
    openejb/trunk/sandbox/tools/src/main/java/org/apache/openejb/tools/doc/property/AnnotationParser.java
    openejb/trunk/sandbox/tools/src/main/java/org/apache/openejb/tools/doc/property/DocTemplate.java
    openejb/trunk/sandbox/tools/src/main/java/org/apache/openejb/tools/doc/property/DocumentationGenerator.java
    openejb/trunk/sandbox/tools/src/main/java/org/apache/openejb/tools/doc/property/DocumentationScanner.java
    openejb/trunk/sandbox/tools/src/main/resources/doc/
    openejb/trunk/sandbox/tools/src/main/resources/doc/doc.vm
Modified:
    openejb/trunk/sandbox/tools/pom.xml

Modified: openejb/trunk/sandbox/tools/pom.xml
URL: http://svn.apache.org/viewvc/openejb/trunk/sandbox/tools/pom.xml?rev=1245191&r1=1245190&r2=1245191&view=diff
==============================================================================
--- openejb/trunk/sandbox/tools/pom.xml (original)
+++ openejb/trunk/sandbox/tools/pom.xml Thu Feb 16 21:31:42 2012
@@ -148,8 +148,20 @@
       <version>1.6.1</version>
     </dependency>
 
-
+    <dependency>
+      <groupId>org.apache.xbean</groupId>
+      <artifactId>xbean-finder-shaded</artifactId>
+      <version>3.10-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.xbean</groupId>
+      <artifactId>xbean-reflect</artifactId>
+      <version>3.10-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.openejb</groupId>
+      <artifactId>openejb-doc-api</artifactId>
+      <version>4.0.0-beta-3-SNAPSHOT</version>
+    </dependency>
   </dependencies>
-
 </project>
-

Added: openejb/trunk/sandbox/tools/src/main/java/org/apache/openejb/tools/doc/property/AnnotationFinder.java
URL: http://svn.apache.org/viewvc/openejb/trunk/sandbox/tools/src/main/java/org/apache/openejb/tools/doc/property/AnnotationFinder.java?rev=1245191&view=auto
==============================================================================
--- openejb/trunk/sandbox/tools/src/main/java/org/apache/openejb/tools/doc/property/AnnotationFinder.java (added)
+++ openejb/trunk/sandbox/tools/src/main/java/org/apache/openejb/tools/doc/property/AnnotationFinder.java Thu Feb 16 21:31:42 2012
@@ -0,0 +1,1531 @@
+package org.apache.openejb.tools.doc.property;
+
+import org.apache.xbean.asm.AnnotationVisitor;
+import org.apache.xbean.asm.Attribute;
+import org.apache.xbean.asm.ClassReader;
+import org.apache.xbean.asm.FieldVisitor;
+import org.apache.xbean.asm.MethodVisitor;
+import org.apache.xbean.asm.Type;
+import org.apache.xbean.asm.commons.EmptyVisitor;
+import org.apache.xbean.asm.signature.SignatureVisitor;
+import org.apache.xbean.finder.Annotated;
+import org.apache.xbean.finder.IAnnotationFinder;
+import org.apache.xbean.finder.MetaAnnotatedClass;
+import org.apache.xbean.finder.MetaAnnotatedField;
+import org.apache.xbean.finder.MetaAnnotatedMethod;
+import org.apache.xbean.finder.archive.Archive;
+import org.apache.xbean.finder.util.Classes;
+import org.apache.xbean.finder.util.SingleLinkedList;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+// simply get not runtime information
+public class AnnotationFinder implements IAnnotationFinder {
+
+    private final Set<Class<? extends Annotation>> metaroots = new HashSet<Class<? extends Annotation>>();
+
+    private final Map<String, List<Info>> annotated = new HashMap<String, List<Info>>();
+
+    protected final Map<String, ClassInfo> classInfos = new HashMap<String, ClassInfo>();
+    protected final Map<String, ClassInfo> originalInfos = new HashMap<String, ClassInfo>();
+    private final List<String> classesNotLoaded = new ArrayList<String>();
+    private final int ASM_FLAGS = ClassReader.SKIP_CODE + ClassReader.SKIP_DEBUG + ClassReader.SKIP_FRAMES;
+    private final Archive archive;
+    private boolean checkRuntimeAnnotation;
+
+    public AnnotationFinder(Archive archive, boolean checkRuntimeAnnotation) {
+        this.archive = archive;
+        this.checkRuntimeAnnotation = checkRuntimeAnnotation;
+
+        for (String className : this.archive) {
+            try {
+                readClassDef(archive.getBytecode(className));
+            } catch (NoClassDefFoundError e) {
+                throw new NoClassDefFoundError("Could not fully load class: " + className + "\n due to:" + e.getMessage());
+            } catch (ClassNotFoundException e) {
+                e.printStackTrace();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+    public AnnotationFinder(Archive archive) {
+        this(archive, true);
+    }
+
+    public List<String> getAnnotatedClassNames() {
+        return new ArrayList<String>(originalInfos.keySet());
+    }
+
+    public Archive getArchive() {
+        return archive;
+    }
+
+    /**
+     * The link() method must be called to successfully use the findSubclasses and findImplementations methods
+     *
+     * @return
+     * @throws java.io.IOException
+     */
+    public AnnotationFinder link() {
+        // 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);
+
+        }
+
+        // diff new and old lists
+        resolveAnnotations(new ArrayList<String>());
+
+        linkMetaAnnotations();
+
+        return this;
+    }
+
+    /**
+     * Used to support meta annotations
+     * <p/>
+     * Once the list of classes has been read from the Archive, we
+     * iterate over all the annotations that are used by those classes
+     * and recursively resolve any annotations those annotations use.
+     *
+     * @param scanned
+     * @throws ClassNotFoundException
+     * @throws IOException
+     */
+    private void resolveAnnotations(List<String> scanned) {
+        // Get a list of the annotations that exist before we start
+        final List<String> annotations = new ArrayList<String>(annotated.keySet());
+
+        for (String annotation : annotations) {
+            if (scanned.contains(annotation)) continue;
+            readClassDef(annotation);
+        }
+
+        // If the "annotated" list has grown, then we must scan those
+        if (annotated.keySet().size() != annotations.size()) {
+            resolveAnnotations(annotations);
+        }
+
+
+//        for (ClassInfo classInfo : classInfos.values()) {
+//            for (AnnotationInfo annotationInfo : classInfo.getAnnotations()) {
+//                for (AnnotationInfo info : annotationInfo.getAnnotations()) {
+//                    final String annotation = info.getName();
+//
+//                    if (hasName(annotation, "Metaroot") && !scanned.contains(annotation)) {
+//                        readClassDef(annotation);
+//                    }
+//                }
+//            }
+//        }
+    }
+
+    private void linkMetaAnnotations() {
+        for (ClassInfo classInfo : classInfos.values()) {
+            if (isMetaRoot(classInfo)) {
+                try {
+                    metaroots.add((Class<? extends Annotation>) classInfo.get());
+                } catch (ClassNotFoundException e) {
+                    classesNotLoaded.add(classInfo.getName());
+                }
+            }
+        }
+
+        for (Class<? extends Annotation> metaroot : metaroots) {
+            List<Info> infoList = annotated.get(metaroot.getName());
+            for (Info info : infoList) {
+                readClassDef(info.getName() + "$$");
+            }
+        }
+    }
+
+    private boolean isMetaRoot(ClassInfo classInfo) {
+        if (!classInfo.isAnnotation()) return false;
+
+        if (classInfo.getName().equals("javax.annotation.Metatype")) return true;
+        if (isSelfAnnotated(classInfo, "Metatype")) return true;
+        if (isSelfAnnotated(classInfo, "Metaroot")) return false;
+
+        for (AnnotationInfo annotationInfo : classInfo.getAnnotations()) {
+            final ClassInfo annotation = classInfos.get(annotationInfo.getName());
+            if (annotation == null) return false;
+            if (annotation.getName().equals("javax.annotation.Metaroot")) return true;
+            if (isSelfAnnotated(annotation, "Metaroot")) return true;
+        }
+
+        return false;
+    }
+
+    private boolean isSelfAnnotated(ClassInfo classInfo, String metatype) {
+        if (!classInfo.isAnnotation()) return false;
+
+        final String name = classInfo.getName();
+        if (!hasName(name, metatype)) return false;
+
+        for (AnnotationInfo info : classInfo.getAnnotations()) {
+            if (info.getName().equals(name)) return true;
+        }
+
+        return true;
+    }
+
+    private boolean hasName(String className, String simpleName) {
+        return className.equals(simpleName) || className.endsWith("." + simpleName) || className.endsWith("$" + simpleName);
+    }
+
+    private void linkParent(ClassInfo classInfo) {
+        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) {
+        final List<ClassInfo> infos = new ArrayList<ClassInfo>();
+
+        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<? extends Annotation> annotation) {
+        List<Info> 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.
+     * <p/>
+     * 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.
+     * <p/>
+     * The list returned is unmodifiable.  Once obtained, the returned list will be a live view of the
+     * results from the last findAnnotated* method call.
+     * <p/>
+     * This method is not thread safe.
+     *
+     * @return an unmodifiable live view of classes that could not be loaded in previous findAnnotated* call.
+     */
+    public List<String> getClassesNotLoaded() {
+        return Collections.unmodifiableList(classesNotLoaded);
+    }
+
+    public List<Package> findAnnotatedPackages(Class<? extends Annotation> annotation) {
+        classesNotLoaded.clear();
+        List<Package> packages = new ArrayList<Package>();
+        List<Info> 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 (!checkRuntimeAnnotation || pkg.isAnnotationPresent(annotation)) {
+                        packages.add(pkg);
+                    }
+                } catch (ClassNotFoundException e) {
+                    classesNotLoaded.add(packageInfo.getName());
+                }
+            }
+        }
+        return packages;
+    }
+
+    public List<Class<?>> findAnnotatedClasses(Class<? extends Annotation> annotation) {
+        classesNotLoaded.clear();
+        List<Class<?>> classes = new ArrayList<Class<?>>();
+        List<Info> 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 (!checkRuntimeAnnotation || clazz.isAnnotationPresent(annotation)) {
+                        classes.add(clazz);
+                    }
+                } catch (ClassNotFoundException e) {
+                    classesNotLoaded.add(classInfo.getName());
+                }
+            }
+        }
+        return classes;
+    }
+
+    public List<Annotated<Class<?>>> findMetaAnnotatedClasses(Class<? extends Annotation> annotation) {
+        classesNotLoaded.clear();
+        Set<Class<?>> classes = findMetaAnnotatedClasses(annotation, new HashSet<Class<?>>());
+
+        List<Annotated<Class<?>>> list = new ArrayList<Annotated<Class<?>>>();
+
+        for (Class<?> clazz : classes) {
+            if (Annotation.class.isAssignableFrom(clazz) && isMetaAnnotation((Class<? extends Annotation>) clazz)) continue;
+            list.add(new MetaAnnotatedClass(clazz));
+        }
+
+        return list;
+    }
+
+    private static boolean isMetaAnnotation(Class<? extends Annotation> clazz) {
+        for (Annotation annotation : clazz.getDeclaredAnnotations()) {
+            if (isMetatypeAnnotation(annotation.annotationType())) return true;
+        }
+
+        return false;
+    }
+
+    private static boolean isMetatypeAnnotation(Class<? extends Annotation> type) {
+        if (isSelfAnnotated(type, "Metatype")) return true;
+
+        for (Annotation annotation : type.getAnnotations()) {
+            if (isSelfAnnotated(annotation.annotationType(), "Metaroot")) return true;
+        }
+
+        return false;
+    }
+
+    private static boolean isSelfAnnotated(Class<? extends Annotation> type, String name) {
+        return type.isAnnotationPresent(type) && type.getSimpleName().equals(name) && validTarget(type);
+    }
+
+    private static boolean validTarget(Class<? extends Annotation> type) {
+        final Target target = type.getAnnotation(Target.class);
+
+        if (target == null) return false;
+
+        final ElementType[] targets = target.value();
+
+        return targets.length == 1 && targets[0] == ElementType.ANNOTATION_TYPE;
+    }
+
+
+    private Set<Class<?>> findMetaAnnotatedClasses(Class<? extends Annotation> annotation, Set<Class<?>> classes) {
+        List<Info> infos = getAnnotationInfos(annotation.getName());
+        for (Info info : infos) {
+            if (info instanceof ClassInfo) {
+                ClassInfo classInfo = (ClassInfo) info;
+                try {
+                    Class clazz = classInfo.get();
+
+                    if (classes.contains(clazz)) continue;
+
+                    // double check via proper reflection
+                    if (!checkRuntimeAnnotation || clazz.isAnnotationPresent(annotation)) {
+                        classes.add(clazz);
+                    }
+
+                    String meta = info.getMetaAnnotationName();
+                    if (meta != null) {
+                        classes.addAll(findMetaAnnotatedClasses((Class<? extends Annotation>) clazz, classes));
+                    }
+                } catch (ClassNotFoundException e) {
+                    classesNotLoaded.add(classInfo.getName());
+                }
+            }
+        }
+        return classes;
+    }
+
+    /**
+     * Naive implementation - works extremelly slow O(n^3)
+     *
+     * @param annotation
+     * @return list of directly or indirectly (inherited) annotated classes
+     */
+    public List<Class<?>> findInheritedAnnotatedClasses(Class<? extends Annotation> annotation) {
+        classesNotLoaded.clear();
+        List<Class<?>> classes = new ArrayList<Class<?>>();
+        List<Info> 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<ClassInfo> tempClassInfos = new ArrayList<ClassInfo>(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<String> 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<Method> findAnnotatedMethods(Class<? extends Annotation> annotation) {
+        classesNotLoaded.clear();
+        List<ClassInfo> seen = new ArrayList<ClassInfo>();
+        List<Method> methods = new ArrayList<Method>();
+        List<Info> infos = getAnnotationInfos(annotation.getName());
+        for (Info info : infos) {
+            if (info instanceof MethodInfo && !info.getName().equals("<init>")) {
+                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 (!checkRuntimeAnnotation || method.isAnnotationPresent(annotation)) {
+                            methods.add(method);
+                        }
+                    }
+                } catch (ClassNotFoundException e) {
+                    classesNotLoaded.add(classInfo.getName());
+                }
+            }
+        }
+        return methods;
+    }
+
+    public List<Annotated<Method>> findMetaAnnotatedMethods(Class<? extends Annotation> annotation) {
+        classesNotLoaded.clear();
+
+        Set<Method> methods = findMetaAnnotatedMethods(annotation, new HashSet<Method>(), new HashSet<String>());
+
+        List<Annotated<Method>> targets = new ArrayList<Annotated<Method>>();
+
+        for (Method method : methods) {
+            targets.add(new MetaAnnotatedMethod(method));
+        }
+
+        return targets;
+    }
+
+    private Set<Method> findMetaAnnotatedMethods(Class<? extends Annotation> annotation, Set<Method> methods, Set<String> seen) {
+        List<Info> infos = getAnnotationInfos(annotation.getName());
+
+        for (Info info : infos) {
+
+            String meta = info.getMetaAnnotationName();
+            if (meta != null) {
+                if (meta.equals(annotation.getName())) continue;
+                if (!seen.add(meta)) continue;
+
+
+                ClassInfo metaInfo = classInfos.get(meta);
+
+                Class<?> clazz;
+                try {
+                    clazz = metaInfo.get();
+                } catch (ClassNotFoundException e) {
+                    classesNotLoaded.add(metaInfo.getName());
+                    continue;
+                }
+
+                findMetaAnnotatedMethods((Class<? extends Annotation>) clazz, methods, seen);
+
+            } else if (info instanceof MethodInfo && !((MethodInfo) info).isConstructor()) {
+
+                MethodInfo methodInfo = (MethodInfo) info;
+
+                ClassInfo classInfo = methodInfo.getDeclaringClass();
+
+                try {
+                    Class clazz = classInfo.get();
+                    for (Method method : clazz.getDeclaredMethods()) {
+                        if (!checkRuntimeAnnotation || method.isAnnotationPresent(annotation)) {
+                            methods.add(method);
+                        }
+                    }
+                } catch (ClassNotFoundException e) {
+                    classesNotLoaded.add(classInfo.getName());
+                }
+            }
+        }
+
+        return methods;
+    }
+
+    public List<Annotated<Field>> findMetaAnnotatedFields(Class<? extends Annotation> annotation) {
+        classesNotLoaded.clear();
+
+        Set<Field> fields = findMetaAnnotatedFields(annotation, new HashSet<Field>(), new HashSet<String>());
+
+        List<Annotated<Field>> targets = new ArrayList<Annotated<Field>>();
+
+        for (Field field : fields) {
+            targets.add(new MetaAnnotatedField(field));
+        }
+
+        return targets;
+    }
+
+    private Set<Field> findMetaAnnotatedFields(Class<? extends Annotation> annotation, Set<Field> fields, Set<String> seen) {
+        List<Info> infos = getAnnotationInfos(annotation.getName());
+
+        for (Info info : infos) {
+
+            String meta = info.getMetaAnnotationName();
+            if (meta != null) {
+                if (meta.equals(annotation.getName())) continue;
+                if (!seen.add(meta)) continue;
+
+
+                ClassInfo metaInfo = classInfos.get(meta);
+
+                Class<?> clazz;
+                try {
+                    clazz = metaInfo.get();
+                } catch (ClassNotFoundException e) {
+                    classesNotLoaded.add(metaInfo.getName());
+                    continue;
+                }
+
+                findMetaAnnotatedFields((Class<? extends Annotation>) clazz, fields, seen);
+
+            } else if (info instanceof FieldInfo) {
+
+                FieldInfo fieldInfo = (FieldInfo) info;
+
+                ClassInfo classInfo = fieldInfo.getDeclaringClass();
+
+                try {
+                    Class clazz = classInfo.get();
+                    for (Field field : clazz.getDeclaredFields()) {
+                        if (!checkRuntimeAnnotation || field.isAnnotationPresent(annotation)) {
+                            fields.add(field);
+                        }
+                    }
+                } catch (ClassNotFoundException e) {
+                    classesNotLoaded.add(classInfo.getName());
+                }
+            }
+        }
+
+        return fields;
+    }
+
+    public List<Constructor> findAnnotatedConstructors(Class<? extends Annotation> annotation) {
+        classesNotLoaded.clear();
+        List<ClassInfo> seen = new ArrayList<ClassInfo>();
+        List<Constructor> constructors = new ArrayList<Constructor>();
+        List<Info> infos = getAnnotationInfos(annotation.getName());
+        for (Info info : infos) {
+            if (info instanceof MethodInfo && info.getName().equals("<init>")) {
+                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 (!checkRuntimeAnnotation || constructor.isAnnotationPresent(annotation)) {
+                            constructors.add(constructor);
+                        }
+                    }
+                } catch (ClassNotFoundException e) {
+                    classesNotLoaded.add(classInfo.getName());
+                }
+            }
+        }
+        return constructors;
+    }
+
+    public List<Field> findAnnotatedFields(Class<? extends Annotation> annotation) {
+        classesNotLoaded.clear();
+        List<ClassInfo> seen = new ArrayList<ClassInfo>();
+        List<Field> fields = new ArrayList<Field>();
+        List<Info> 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 (!checkRuntimeAnnotation || field.isAnnotationPresent(annotation)) {
+                            fields.add(field);
+                        }
+                    }
+                } catch (ClassNotFoundException e) {
+                    classesNotLoaded.add(classInfo.getName());
+                }
+            }
+        }
+        return fields;
+    }
+
+    public List<Class<?>> findClassesInPackage(String packageName, boolean recursive) {
+        classesNotLoaded.clear();
+        List<Class<?>> classes = new ArrayList<Class<?>>();
+        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 <T> List<Class<? extends T>> findSubclasses(Class<T> clazz) {
+        if (clazz == null) throw new NullPointerException("class cannot be null");
+
+        classesNotLoaded.clear();
+
+        final ClassInfo classInfo = classInfos.get(clazz.getName());
+
+        List<Class<? extends T>> found = new ArrayList<Class<? extends T>>();
+
+        if (classInfo == null) return found;
+
+        findSubclasses(classInfo, found, clazz);
+
+        return found;
+    }
+
+    private <T> void findSubclasses(ClassInfo classInfo, List<Class<? extends T>> found, Class<T> 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 <T> List<Class<? extends T>> _findSubclasses(Class<T> clazz) {
+        if (clazz == null) throw new NullPointerException("class cannot be null");
+
+        List<Class<? extends T>> classes = new ArrayList<Class<? extends T>>();
+
+
+        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 <T> List<Class<? extends T>> findImplementations(Class<T> 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<ClassInfo> infos = collectImplementations(interfaceName);
+
+        // Collect all subclasses of implementations
+        List<Class<? extends T>> classes = new ArrayList<Class<? extends T>>();
+        for (ClassInfo info : infos) {
+            try {
+                final Class<? extends T> impl = (Class<? extends T>) 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<ClassInfo> collectImplementations(String interfaceName) {
+        final List<ClassInfo> infos = new ArrayList<ClassInfo>();
+
+        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<Info> getAnnotationInfos(String name) {
+        List<Info> infos = annotated.get(name);
+        if (infos == null) {
+            infos = new SingleLinkedList<Info>();
+            annotated.put(name, infos);
+        }
+        return infos;
+    }
+
+    protected void readClassDef(String className) {
+        if (classInfos.containsKey(className)) return;
+        try {
+            readClassDef(archive.getBytecode(className));
+        } catch (Exception e) {
+            if (className.endsWith("$$")) return;
+            classesNotLoaded.add(className);
+        }
+    }
+
+    protected void readClassDef(InputStream in) throws IOException {
+        ClassReader classReader = new ClassReader(in);
+        classReader.accept(new InfoBuildingVisitor(), ASM_FLAGS);
+    }
+
+    protected void readClassDef(Class clazz) {
+        List<Info> infos = new ArrayList<Info>();
+
+        Package aPackage = clazz.getPackage();
+        if (aPackage != null) {
+            final PackageInfo info = new PackageInfo(aPackage);
+            for (AnnotationInfo annotation : info.getAnnotations()) {
+                List<Info> 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<Info> annotationInfos = getAnnotationInfos(annotation.getName());
+                annotationInfos.add(info);
+            }
+        }
+    }
+
+    public class Annotatable {
+        private final List<AnnotationInfo> annotations = new ArrayList<AnnotationInfo>();
+
+        public Annotatable(AnnotatedElement element) {
+            for (Annotation annotation : getAnnotations(element)) {
+                annotations.add(new AnnotationInfo(annotation.annotationType().getName()));
+            }
+        }
+
+        public Annotatable() {
+        }
+
+        public Annotation[] getDeclaredAnnotations() {
+            return new Annotation[0];
+        }
+
+        public List<AnnotationInfo> getAnnotations() {
+            return annotations;
+        }
+
+        public String getMetaAnnotationName() {
+            return null;
+        }
+
+        /**
+         * 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 getMetaAnnotationName();
+
+        String getName();
+
+        List<AnnotationInfo> getAnnotations();
+
+        Annotation[] getDeclaredAnnotations();
+    }
+
+    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<MethodInfo> methods = new SingleLinkedList<MethodInfo>();
+        private final List<MethodInfo> constructors = new SingleLinkedList<MethodInfo>();
+        private String superType;
+        private ClassInfo superclassInfo;
+        private final List<ClassInfo> subclassInfos = new SingleLinkedList<ClassInfo>();
+        private final List<String> interfaces = new SingleLinkedList<String>();
+        private final List<FieldInfo> fields = new SingleLinkedList<FieldInfo>();
+        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;
+        }
+
+        @Override
+        public String getMetaAnnotationName() {
+            for (AnnotationInfo info : getAnnotations()) {
+                for (Class<? extends Annotation> metaroot : metaroots) {
+                    if (info.getName().equals(metaroot.getName())) return name;
+                }
+            }
+
+            if (name.endsWith("$$")) {
+                ClassInfo info = classInfos.get(name.substring(0, name.length() - 2));
+                if (info != null) {
+                    return info.getMetaAnnotationName();
+                }
+            }
+
+            return null;
+        }
+
+        public String getPackageName() {
+            return name.indexOf(".") > 0 ? name.substring(0, name.lastIndexOf(".")) : "";
+        }
+
+        public List<MethodInfo> getConstructors() {
+            return constructors;
+        }
+
+        public List<String> getInterfaces() {
+            return interfaces;
+        }
+
+        public List<FieldInfo> getFields() {
+            return fields;
+        }
+
+        public List<MethodInfo> getMethods() {
+            return methods;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public String getSuperType() {
+            return superType;
+        }
+
+        public boolean isAnnotation() {
+            return "java.lang.Object".equals(superType) && interfaces.size() == 1 && "java.lang.annotation.Annotation".equals(interfaces.get(0));
+        }
+
+        public Class<?> get() throws ClassNotFoundException {
+            if (clazz != null) return clazz;
+            try {
+                String fixedName = name.replaceFirst("<.*>", "");
+                this.clazz = archive.loadClass(fixedName);
+                return clazz;
+            } catch (ClassNotFoundException notFound) {
+                classesNotLoaded.add(name);
+                throw notFound;
+            }
+        }
+
+        public String toString() {
+            return name;
+        }
+    }
+
+    public class MethodInfo extends Annotatable implements Info {
+        private final ClassInfo declaringClass;
+        private final String descriptor;
+        private final String name;
+        private final List<List<AnnotationInfo>> parameterAnnotations = new ArrayList<List<AnnotationInfo>>();
+        private Member method;
+
+        public MethodInfo(ClassInfo info, Constructor constructor) {
+            super(constructor);
+            this.declaringClass = info;
+            this.name = "<init>";
+            this.descriptor = null;
+        }
+
+        public MethodInfo(ClassInfo info, Method method) {
+            super(method);
+            this.declaringClass = info;
+            this.name = method.getName();
+            this.descriptor = method.getReturnType().getName();
+            this.method = method;
+        }
+
+        public MethodInfo(ClassInfo declarignClass, String name, String descriptor) {
+            this.declaringClass = declarignClass;
+            this.name = name;
+            this.descriptor = descriptor;
+        }
+
+        @Override
+        public String getMetaAnnotationName() {
+            return declaringClass.getMetaAnnotationName();
+        }
+
+        @Override
+        public Annotation[] getDeclaredAnnotations() {
+            super.getDeclaredAnnotations();
+            try {
+                return ((AnnotatedElement) get()).getDeclaredAnnotations();
+            } catch (ClassNotFoundException e) {
+                return super.getDeclaredAnnotations();
+            }
+        }
+
+        public boolean isConstructor() {
+            return getName().equals("<init>");
+        }
+
+        public List<List<AnnotationInfo>> getParameterAnnotations() {
+            return parameterAnnotations;
+        }
+
+        public List<AnnotationInfo> getParameterAnnotations(int index) {
+            if (index >= parameterAnnotations.size()) {
+                for (int i = parameterAnnotations.size(); i <= index; i++) {
+                    List<AnnotationInfo> annotationInfos = new ArrayList<AnnotationInfo>();
+                    parameterAnnotations.add(i, annotationInfos);
+                }
+            }
+            return parameterAnnotations.get(index);
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public ClassInfo getDeclaringClass() {
+            return declaringClass;
+        }
+
+        public String toString() {
+            return declaringClass + "@" + name;
+        }
+
+        public Member get() throws ClassNotFoundException {
+            if (method == null) {
+                method = toMethod();
+            }
+
+            return method;
+        }
+
+        private Method toMethod() throws ClassNotFoundException {
+            org.apache.xbean.asm.commons.Method method = new org.apache.xbean.asm.commons.Method(name, descriptor);
+
+            Class<?> clazz = this.declaringClass.get();
+            List<Class> parameterTypes = new ArrayList<Class>();
+
+            for (org.apache.xbean.asm.Type type : method.getArgumentTypes()) {
+                String paramType = type.getClassName();
+                try {
+                    parameterTypes.add(Classes.forName(paramType, clazz.getClassLoader()));
+                } catch (ClassNotFoundException cnfe) {
+                    throw new IllegalStateException("Parameter class could not be loaded for type " + paramType, cnfe);
+                }
+            }
+
+            Class[] parameters = parameterTypes.toArray(new Class[parameterTypes.size()]);
+
+            IllegalStateException noSuchMethod = null;
+            while (clazz != null) {
+                try {
+                    return clazz.getDeclaredMethod(name, parameters);
+                } catch (NoSuchMethodException e) {
+                    if (noSuchMethod == null) {
+                        noSuchMethod = new IllegalStateException("Callback method does not exist: " + clazz.getName() + "." + name, e);
+                    }
+                    clazz = clazz.getSuperclass();
+                }
+            }
+
+            throw noSuchMethod;
+        }
+
+    }
+
+    public class FieldInfo extends Annotatable implements Info {
+        private final String name;
+        private final String type;
+        private final ClassInfo declaringClass;
+        private Field field;
+
+        public FieldInfo(ClassInfo info, Field field) {
+            super(field);
+            this.declaringClass = info;
+            this.name = field.getName();
+            this.type = field.getType().getName();
+            this.field = field;
+        }
+
+        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;
+        }
+
+        @Override
+        public String getMetaAnnotationName() {
+            return declaringClass.getMetaAnnotationName();
+        }
+
+        @Override
+        public Annotation[] getDeclaredAnnotations() {
+            super.getDeclaredAnnotations();
+            try {
+                return ((AnnotatedElement) get()).getDeclaredAnnotations();
+            } catch (ClassNotFoundException e) {
+                return super.getDeclaredAnnotations();
+            }
+        }
+
+        public Member get() throws ClassNotFoundException {
+            if (field == null) {
+                field = toField();
+            }
+
+            return field;
+        }
+
+        private Field toField() throws ClassNotFoundException {
+
+            Class<?> clazz = this.declaringClass.get();
+
+            try {
+                return clazz.getDeclaredField(name);
+            } catch (NoSuchFieldException e) {
+                throw new IllegalStateException(name, e);
+            }
+
+        }
+    }
+
+    public class AnnotationInfo extends Annotatable implements Info {
+        private final String name;
+
+        public AnnotationInfo(Annotation annotation) {
+            this(annotation.getClass().getName());
+        }
+
+        public AnnotationInfo(Class<? extends Annotation> annotation) {
+            this.name = annotation.getName().intern();
+        }
+
+        public AnnotationInfo(String name) {
+            name = Type.getType(name).getClassName();
+            this.name = name.intern();
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public String toString() {
+            return name;
+        }
+    }
+
+    public class InfoBuildingVisitor extends EmptyVisitor {
+        private Info info;
+
+        public InfoBuildingVisitor() {
+        }
+
+        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));
+
+//                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 void visitInnerClass(String name, String outerName, String innerName, int access) {
+            super.visitInnerClass(name, outerName, innerName, access);
+        }
+
+        @Override
+        public void visitAttribute(Attribute attribute) {
+            super.visitAttribute(attribute);
+        }
+
+        @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);
+        }
+
+        @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);
+        }
+
+        @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);
+        }
+
+
+        @Override
+        public AnnotationVisitor visitParameterAnnotation(int param, String desc, boolean visible) {
+            MethodInfo methodInfo = ((MethodInfo) info);
+            List<AnnotationInfo> annotationInfos = methodInfo.getParameterAnnotations(param);
+            AnnotationInfo annotationInfo = new AnnotationInfo(desc);
+            annotationInfos.add(annotationInfo);
+            return new InfoBuildingVisitor(annotationInfo);
+        }
+    }
+
+    public static class GenericAwareInfoBuildingVisitor implements SignatureVisitor {
+
+        public enum TYPE {
+            CLASS
+        }
+
+        public enum STATE {
+            BEGIN, END, SUPERCLASS, INTERFACE, FORMAL_TYPE_PARAM
+        }
+
+        private Info info;
+        private GenericAwareInfoBuildingVisitor.TYPE type;
+        private GenericAwareInfoBuildingVisitor.STATE state;
+
+        private static boolean debug = false;
+
+        public GenericAwareInfoBuildingVisitor() {
+        }
+
+        public GenericAwareInfoBuildingVisitor(GenericAwareInfoBuildingVisitor.TYPE type, Info info) {
+            this.type = type;
+            this.info = info;
+            this.state = GenericAwareInfoBuildingVisitor.STATE.BEGIN;
+        }
+
+        public void visitFormalTypeParameter(String s) {
+            if (debug) System.out.println(" s=" + s);
+            switch (state) {
+                case BEGIN:
+                    ((ClassInfo) info).name += "<" + s;
+            }
+            state = GenericAwareInfoBuildingVisitor.STATE.FORMAL_TYPE_PARAM;
+        }
+
+        public SignatureVisitor visitClassBound() {
+            if (debug) System.out.println(" visitClassBound()");
+            return this;
+        }
+
+        public SignatureVisitor visitInterfaceBound() {
+            if (debug) System.out.println(" visitInterfaceBound()");
+            return this;
+        }
+
+        public SignatureVisitor visitSuperclass() {
+            if (debug) System.out.println(" visitSuperclass()");
+            state = GenericAwareInfoBuildingVisitor.STATE.SUPERCLASS;
+            return this;
+        }
+
+        public SignatureVisitor visitInterface() {
+            if (debug) System.out.println(" visitInterface()");
+            ((ClassInfo) info).getInterfaces().add("");
+            state = GenericAwareInfoBuildingVisitor.STATE.INTERFACE;
+            return this;
+        }
+
+        public SignatureVisitor visitParameterType() {
+            if (debug) System.out.println(" visitParameterType()");
+            return this;
+        }
+
+        public SignatureVisitor visitReturnType() {
+            if (debug) System.out.println(" visitReturnType()");
+            return this;
+        }
+
+        public SignatureVisitor visitExceptionType() {
+            if (debug) System.out.println(" visitExceptionType()");
+            return this;
+        }
+
+        public void visitBaseType(char c) {
+            if (debug) System.out.println(" visitBaseType(" + c + ")");
+        }
+
+        public void visitTypeVariable(String s) {
+            if (debug) System.out.println(" visitTypeVariable(" + s + ")");
+        }
+
+        public SignatureVisitor visitArrayType() {
+            if (debug) System.out.println(" visitArrayType()");
+            return this;
+        }
+
+        public void visitClassType(String s) {
+            if (debug) System.out.println(" visitClassType(" + s + ")");
+            switch (state) {
+                case INTERFACE:
+                    List<String> interfces = ((ClassInfo) info).getInterfaces();
+                    int idx = interfces.size() - 1;
+                    String interfce = interfces.get(idx);
+                    if (interfce.length() == 0) {
+                        interfce = javaName(s);
+                    } else {
+                        interfce += javaName(s);
+                    }
+                    interfces.set(idx, interfce);
+                    break;
+                case SUPERCLASS:
+                    if (!s.equals("java/lang/Object")) {
+                        ((ClassInfo) info).superType = javaName(s);
+                    }
+            }
+        }
+
+        public void visitInnerClassType(String s) {
+            if (debug) System.out.println(" visitInnerClassType(" + s + ")");
+        }
+
+        public void visitTypeArgument() {
+            if (debug) System.out.println(" visitTypeArgument()");
+            switch (state) {
+                case INTERFACE:
+                    List<String> interfces = ((ClassInfo) info).getInterfaces();
+                    int idx = interfces.size() - 1;
+                    String interfce = interfces.get(idx);
+                    interfce += "<";
+                    interfces.set(idx, interfce);
+            }
+        }
+
+        public SignatureVisitor visitTypeArgument(char c) {
+            if (debug) System.out.println(" visitTypeArgument(" + c + ")");
+            switch (state) {
+                case INTERFACE:
+                    List<String> interfces = ((ClassInfo) info).getInterfaces();
+                    int idx = interfces.size() - 1;
+                    String interfce = interfces.get(idx);
+                    interfce += "<";
+                    interfces.set(idx, interfce);
+            }
+            return this;
+        }
+
+        public void visitEnd() {
+            if (debug) System.out.println(" visitEnd()");
+            switch (state) {
+                case INTERFACE:
+                    List<String> interfces = ((ClassInfo) info).getInterfaces();
+                    int idx = interfces.size() - 1;
+                    String interfce = interfces.get(idx);
+                    interfce += ">";
+                    interfces.set(idx, interfce);
+                    break;
+                case FORMAL_TYPE_PARAM:
+                    String name = ((ClassInfo) info).name;
+                    if (name.contains("<")) {
+                        ((ClassInfo) info).name += ">";
+                    }
+            }
+            state = GenericAwareInfoBuildingVisitor.STATE.END;
+        }
+
+        private String javaName(String name) {
+            return (name == null) ? null : name.replace('/', '.');
+        }
+
+    }
+
+}

Added: openejb/trunk/sandbox/tools/src/main/java/org/apache/openejb/tools/doc/property/AnnotationInfo.java
URL: http://svn.apache.org/viewvc/openejb/trunk/sandbox/tools/src/main/java/org/apache/openejb/tools/doc/property/AnnotationInfo.java?rev=1245191&view=auto
==============================================================================
--- openejb/trunk/sandbox/tools/src/main/java/org/apache/openejb/tools/doc/property/AnnotationInfo.java (added)
+++ openejb/trunk/sandbox/tools/src/main/java/org/apache/openejb/tools/doc/property/AnnotationInfo.java Thu Feb 16 21:31:42 2012
@@ -0,0 +1,27 @@
+package org.apache.openejb.tools.doc.property;
+
+public class AnnotationInfo {
+    private String title;
+    private String description;
+
+    public AnnotationInfo(String title, String description) {
+        this.title = title;
+        this.description = description;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    @Override
+    public String toString() {
+        return "AnnotationInfo{" +
+                "title='" + title + '\'' +
+                ", description='" + description + '\'' +
+            '}';
+    }
+}

Added: openejb/trunk/sandbox/tools/src/main/java/org/apache/openejb/tools/doc/property/AnnotationInstanceInfo.java
URL: http://svn.apache.org/viewvc/openejb/trunk/sandbox/tools/src/main/java/org/apache/openejb/tools/doc/property/AnnotationInstanceInfo.java?rev=1245191&view=auto
==============================================================================
--- openejb/trunk/sandbox/tools/src/main/java/org/apache/openejb/tools/doc/property/AnnotationInstanceInfo.java (added)
+++ openejb/trunk/sandbox/tools/src/main/java/org/apache/openejb/tools/doc/property/AnnotationInstanceInfo.java Thu Feb 16 21:31:42 2012
@@ -0,0 +1,29 @@
+package org.apache.openejb.tools.doc.property;
+
+import java.util.Map;
+
+public class AnnotationInstanceInfo {
+    private final Map<String, String> info;
+    private final String where;
+
+    public AnnotationInstanceInfo(Map<String, String> info, String where) {
+        this.info = info;
+        this.where = where;
+    }
+
+    public String getWhere() {
+        return where;
+    }
+
+    public Map<String, String> getInfo() {
+        return info;
+    }
+
+    @Override
+    public String toString() {
+        return "AnnotationInstanceInfo{" +
+                "info='" + info + '\'' +
+                ", where='" + where + '\'' +
+            '}';
+    }
+}

Added: openejb/trunk/sandbox/tools/src/main/java/org/apache/openejb/tools/doc/property/AnnotationParser.java
URL: http://svn.apache.org/viewvc/openejb/trunk/sandbox/tools/src/main/java/org/apache/openejb/tools/doc/property/AnnotationParser.java?rev=1245191&view=auto
==============================================================================
--- openejb/trunk/sandbox/tools/src/main/java/org/apache/openejb/tools/doc/property/AnnotationParser.java (added)
+++ openejb/trunk/sandbox/tools/src/main/java/org/apache/openejb/tools/doc/property/AnnotationParser.java Thu Feb 16 21:31:42 2012
@@ -0,0 +1,99 @@
+package org.apache.openejb.tools.doc.property;
+
+import org.apache.xbean.asm.ClassReader;
+import org.apache.xbean.asm.FieldVisitor;
+import org.apache.xbean.asm.MethodVisitor;
+import org.apache.xbean.asm.commons.EmptyVisitor;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+
+public class AnnotationParser {
+    public static Map<String, String> annotationInfos(final String name, final String field, final String clazz, final String mtd, final InputStream is) throws IOException {
+        final ClassReader reader = new ClassReader(new BufferedInputStream(is));
+        final AnnotationVisitor visitor = new AnnotationVisitor(name, field, clazz, mtd);
+        reader.accept(visitor, ClassReader.SKIP_CODE + ClassReader.SKIP_DEBUG + ClassReader.SKIP_FRAMES);
+        return visitor.values();
+    }
+
+    private static class AnnotationVisitor extends EmptyVisitor {
+        private boolean targetFound = false;
+        private boolean entityFound = false;
+
+        private final Map<String, String> values = new HashMap<String, String>();
+        private String target;
+
+        // one max != null
+        private String field;
+        private String clazz;
+        private String method;
+
+        private AnnotationVisitor(String target, String field, String clazz, String method) {
+            this.target = target;
+            this.field = field;
+            this.clazz = clazz;
+            this.method = method;
+
+            if (field == null && clazz == null && method == null) {
+                entityFound = true;
+            }
+        }
+
+        @Override
+        public void visit(int i, int i1, java.lang.String s, java.lang.String s1, java.lang.String s2, java.lang.String[] strings) {
+            if (clazz != null && s.equals(clazz.replace(".", "/"))) {
+                entityFound = true;
+            }
+        }
+
+        @Override
+        public FieldVisitor visitField(int i, String s, String s1, String s2, java.lang.Object o) {
+            if (s.equals(field)) {
+                entityFound = true;
+            }
+            return this;
+        }
+
+        @Override
+        public MethodVisitor visitMethod(int i, String s, String s1, String s2, String[] strings) {
+            if (s.equals(method)) { // todo: manage all parameters?
+                entityFound = true;
+            }
+            return this;
+        }
+
+        @Override
+        public org.apache.xbean.asm.AnnotationVisitor visitAnnotation(String s, boolean b) {
+            if (s.equals("L" + target.replace(".", "/") + ";")) {
+                targetFound = true;
+            }
+            return this;
+        }
+
+        @Override
+        public void visit(final String mtdName, final Object value) {
+            if (targetFound && entityFound) {
+                values.put(mtdName, value.toString());
+            }
+        }
+
+        @Override
+        public void visitEnd() {
+            if (entityFound && targetFound) {
+                entityFound = false;
+                targetFound = false;
+            }
+        }
+
+        public String get(final String key) {
+            return values.get(key);
+        }
+
+        public Map<String, String> values() {
+            return values;
+        }
+    }
+}

Added: openejb/trunk/sandbox/tools/src/main/java/org/apache/openejb/tools/doc/property/DocTemplate.java
URL: http://svn.apache.org/viewvc/openejb/trunk/sandbox/tools/src/main/java/org/apache/openejb/tools/doc/property/DocTemplate.java?rev=1245191&view=auto
==============================================================================
--- openejb/trunk/sandbox/tools/src/main/java/org/apache/openejb/tools/doc/property/DocTemplate.java (added)
+++ openejb/trunk/sandbox/tools/src/main/java/org/apache/openejb/tools/doc/property/DocTemplate.java Thu Feb 16 21:31:42 2012
@@ -0,0 +1,101 @@
+package org.apache.openejb.tools.doc.property;
+
+import org.apache.log4j.Logger;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.VelocityEngine;
+import org.apache.velocity.runtime.log.NullLogChute;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.StringWriter;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+public final class DocTemplate {
+    private static final Logger LOGGER = Logger.getLogger(DocTemplate.class);
+
+    private static final DocTemplate INSTANCE = new DocTemplate();
+    private static final String LOG_TAG = DocTemplate.class.getName();
+    private static final String BASE = "doc/";
+
+    private VelocityEngine engine;
+    private Map<String, URL> resources = new HashMap<String, URL>();
+
+    private DocTemplate() {
+        // no-op
+    }
+
+    public synchronized void init() {
+        if (engine != null) {
+            return;
+        }
+
+        engine = new VelocityEngine();
+
+        Properties properties = new Properties();
+        properties.setProperty("file.resource.loader.cache", "true");
+        properties.setProperty("resource.loader", "file, class");
+        properties.setProperty("class.resource.loader.description", "Velocity Classpath Resource Loader");
+        properties.setProperty("class.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
+        properties.setProperty("runtime.log.logsystem.class", NullLogChute.class.getName());
+        properties.setProperty("runtime.log.logsystem.commons.logging.name", LOG_TAG);
+        engine.init(properties);
+    }
+
+    private void evaluate(String template, final AnnotationInfo key, final List<AnnotationInstanceInfo> value, StringWriter writer) throws IOException {
+        if (engine == null) {
+            init();
+        }
+
+        if (!resources.containsKey(template)) {
+            URL resource = Thread.currentThread().getContextClassLoader().getResource(BASE + template);
+            resources.put(template, resource);
+        }
+
+        URL url = resources.get(template);
+        if (url == null) {
+            LOGGER.error("can't find template " + template);
+            return;
+        }
+
+        VelocityContext context = new VelocityContext(context(key, value));
+        engine.evaluate(context, writer, LOG_TAG, new InputStreamReader(new BufferedInputStream(url.openStream())));
+    }
+
+    private Map context(AnnotationInfo key, List<AnnotationInstanceInfo> value) {
+        final Map<String, Object> map = new HashMap<String, Object>();
+        map.put("key", key);
+        map.put("values", value);
+        map.put("columns", value.isEmpty() ? new ArrayList<String>() : value.iterator().next().getInfo().keySet());
+        return map;
+    }
+
+    public String apply(String template, AnnotationInfo key, List<AnnotationInstanceInfo> value) {
+        StringWriter writer = null;
+        try {
+            writer = new StringWriter();
+            evaluate(template, key, value, writer);
+        } catch (IOException ioe) {
+            LOGGER.error("can't apply template " + template, ioe);
+        } finally {
+            if (writer != null) {
+                try {
+                    writer.flush();
+                    writer.close();
+                } catch (IOException e) {
+                    LOGGER.error("can't flush writer", e);
+                }
+            }
+        }
+        return writer.toString();
+    }
+
+    public static DocTemplate get() {
+        return INSTANCE;
+    }
+}

Added: openejb/trunk/sandbox/tools/src/main/java/org/apache/openejb/tools/doc/property/DocumentationGenerator.java
URL: http://svn.apache.org/viewvc/openejb/trunk/sandbox/tools/src/main/java/org/apache/openejb/tools/doc/property/DocumentationGenerator.java?rev=1245191&view=auto
==============================================================================
--- openejb/trunk/sandbox/tools/src/main/java/org/apache/openejb/tools/doc/property/DocumentationGenerator.java (added)
+++ openejb/trunk/sandbox/tools/src/main/java/org/apache/openejb/tools/doc/property/DocumentationGenerator.java Thu Feb 16 21:31:42 2012
@@ -0,0 +1,70 @@
+package org.apache.openejb.tools.doc.property;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.filefilter.AndFileFilter;
+import org.apache.commons.io.filefilter.IOFileFilter;
+import org.apache.commons.io.filefilter.OrFileFilter;
+import org.apache.commons.io.filefilter.PrefixFileFilter;
+import org.apache.commons.io.filefilter.SuffixFileFilter;
+import org.apache.commons.io.filefilter.TrueFileFilter;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.apache.openejb.tools.doc.property.DocumentationScanner.findDocumentation;
+
+public final class DocumentationGenerator {
+    private DocumentationGenerator() {
+        // no-op
+    }
+
+    public static void main(String[] args) {
+        if (args.length != 1) {
+            System.err.println("Usage:");
+            System.err.println("======");
+            System.err.println("\tjava -cp tools.jar " + DocumentationGenerator.class.getName() + " <directory to scan>");
+            return;
+        }
+
+        final File dir = new File(args[0]);
+        if (!dir.isDirectory()) {
+            System.err.println("args0 should be a directory");
+            return;
+        }
+
+        // get info
+        final Collection<File > files = FileUtils.listFiles(dir, new OrFileFilter(filter("openejb-"), filter("tomee-")), TrueFileFilter.INSTANCE);
+        final Map<AnnotationInfo, List<AnnotationInstanceInfo>> info = new HashMap<AnnotationInfo, List<AnnotationInstanceInfo>>();
+        for (File file : files) {
+            add(info, findDocumentation(file));
+        }
+
+        for (Map.Entry<AnnotationInfo, List<AnnotationInstanceInfo>> entry : info.entrySet()) {
+            dump(entry.getKey(), entry.getValue());
+            System.out.println();
+        }
+    }
+
+    private static void dump(AnnotationInfo key, List<AnnotationInstanceInfo> value) {
+        System.out.println(DocTemplate.get()
+                .apply("doc.vm", key, value));
+    }
+
+    private static void add(Map<AnnotationInfo, List<AnnotationInstanceInfo>> info, Map<AnnotationInfo, List<AnnotationInstanceInfo>> documentation) {
+        for (Map.Entry<AnnotationInfo, List<AnnotationInstanceInfo>> entry : documentation.entrySet()) {
+            final AnnotationInfo key = entry.getKey();
+            if (info.containsKey(key)) {
+                info.get(key).addAll(entry.getValue());
+            } else {
+                info.put(key, entry.getValue());
+            }
+        }
+    }
+
+    private static IOFileFilter filter(String s) {
+        return new AndFileFilter(new PrefixFileFilter(s), new SuffixFileFilter(".jar"));
+    }
+}

Added: openejb/trunk/sandbox/tools/src/main/java/org/apache/openejb/tools/doc/property/DocumentationScanner.java
URL: http://svn.apache.org/viewvc/openejb/trunk/sandbox/tools/src/main/java/org/apache/openejb/tools/doc/property/DocumentationScanner.java?rev=1245191&view=auto
==============================================================================
--- openejb/trunk/sandbox/tools/src/main/java/org/apache/openejb/tools/doc/property/DocumentationScanner.java (added)
+++ openejb/trunk/sandbox/tools/src/main/java/org/apache/openejb/tools/doc/property/DocumentationScanner.java Thu Feb 16 21:31:42 2012
@@ -0,0 +1,138 @@
+package org.apache.openejb.tools.doc.property;
+
+import org.apache.openejb.documentation.Documentation;
+import org.apache.openejb.documentation.Property;
+import org.apache.xbean.finder.Annotated;
+import org.apache.xbean.finder.archive.JarArchive;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public final class DocumentationScanner {
+    private DocumentationScanner() {
+        // no-op
+    }
+
+    private static Class<?>[] findDocumentationAnnotations() {
+        // should be done on openejb-api if we want to do so
+        try {
+            final AnnotationFinder finder = new AnnotationFinder(new JarArchive(Thread.currentThread().getContextClassLoader(), openejbApiJar()), false);
+            final List<Annotated<Class<?>>> annotations = finder.findMetaAnnotatedClasses(Documentation.class);
+            final Class<?>[] classes = new Class<?>[annotations.size()];
+            int i = 0;
+            for (Annotated<Class<?>> clazz : annotations) {
+                classes[i] = clazz.get();
+            }
+            return classes;
+        } catch (Exception e) {
+            return new Class<?>[] { Property.class };
+        }
+    }
+
+    private static URL openejbApiJar() {
+        final ClassLoader tccl = Thread.currentThread().getContextClassLoader();
+        if (tccl instanceof URLClassLoader) {
+            URL[] urls = ((URLClassLoader) tccl).getURLs();
+            for (URL url : urls) {
+                final String urlStr = url.toExternalForm();
+                if (urlStr.contains("openejb-api")) {
+                    if (urlStr.startsWith("jar")) {
+                        return url;
+                    }
+                    try {
+                        return new URL("jar:" + urlStr + "!/");
+                    } catch (MalformedURLException e) {
+                        // ignored
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+    public static Map<AnnotationInfo, List<AnnotationInstanceInfo>> findDocumentation(final File jar) {
+        final Map<AnnotationInfo, List<AnnotationInstanceInfo>> infos = new HashMap<AnnotationInfo, List<AnnotationInstanceInfo>>();
+        final AnnotationFinder finder;
+        try {
+            finder = new AnnotationFinder(new JarArchive(new URLClassLoader(new URL[] { jar.toURI().toURL() }, Thread.currentThread().getContextClassLoader()), new URL("jar:" + jar.toURI().toURL().toExternalForm() + "!/")), false);
+        } catch (MalformedURLException e) {
+            throw new RuntimeException("can't look into " + jar.getPath());
+        }
+
+        for (Class<?> clazz : findDocumentationAnnotations()) {
+            final AnnotationInfo key = key(clazz);
+            infos.put(key, new ArrayList<AnnotationInstanceInfo>());
+            try {
+                final List<Annotated<Field>> fields = finder.findMetaAnnotatedFields((Class<? extends Annotation>) clazz);
+                for (Annotated<Field> foundField : fields) {
+                    infos.get(key).add(new AnnotationInstanceInfo(
+                            AnnotationParser.annotationInfos(clazz.getName(), foundField.get().getName(), null, null, is(foundField.get())),
+                            foundField.get().toGenericString()));
+                }
+
+                final List<Annotated<Class<?>>> classes = finder.findMetaAnnotatedClasses((Class<? extends Annotation>) clazz);
+                for (Annotated<Class<?>> foundClazz : classes) {
+                    infos.get(key).add(new AnnotationInstanceInfo(
+                            AnnotationParser.annotationInfos(clazz.getName(), null, foundClazz.get().getName(), null, is(foundClazz.get())),
+                            foundClazz.get().toString()));
+                }
+
+                final List<Annotated<Method>> methods = finder.findMetaAnnotatedMethods((Class<? extends Annotation>) clazz);
+                for (Annotated<Method> method : methods) {
+                    infos.get(key).add(new AnnotationInstanceInfo(
+                            AnnotationParser.annotationInfos(clazz.getName(), null, null, method.get().getName(), is(method.get())),
+                                    method.get().toGenericString()));
+                }
+            } catch (Exception e) {
+                throw new RuntimeException("can't parse " + clazz.getName());
+            }
+        }
+
+        return infos;
+    }
+
+    private static InputStream is(Method method) throws IOException {
+        return url(method.getDeclaringClass()).openStream();
+    }
+
+    private static InputStream is(Class<?> aClass) throws IOException {
+        return url(aClass).openStream();
+    }
+
+    private static InputStream is(Field field) throws IOException {
+        return url(field.getDeclaringClass()).openStream();
+    }
+
+    private static AnnotationInfo key(final Class<?> clazz) {
+
+        try {
+            final Map<String, String> values = AnnotationParser.annotationInfos(Documentation.class.getName(), null, null, null, url(clazz).openStream());
+            if (values.containsKey("title")) {
+                return new AnnotationInfo(values.get("title"), values.get("description"));
+            }
+        } catch (IOException e) {
+            // ignored
+        }
+        return new AnnotationInfo(clazz.getSimpleName(), null);
+    }
+
+    private static URL url(Class<?> clazz) throws MalformedURLException {
+        String urlStr = clazz.getProtectionDomain().getCodeSource().getLocation().toExternalForm();
+        if (!urlStr.startsWith("jar")) {
+            urlStr = "jar:" + urlStr + "!/";
+        }
+        urlStr += clazz.getName().replace(".", "/") + ".class";
+        return new URL(urlStr);
+    }
+}

Added: openejb/trunk/sandbox/tools/src/main/resources/doc/doc.vm
URL: http://svn.apache.org/viewvc/openejb/trunk/sandbox/tools/src/main/resources/doc/doc.vm?rev=1245191&view=auto
==============================================================================
--- openejb/trunk/sandbox/tools/src/main/resources/doc/doc.vm (added)
+++ openejb/trunk/sandbox/tools/src/main/resources/doc/doc.vm Thu Feb 16 21:31:42 2012
@@ -0,0 +1,20 @@
+<h1>$key.title</h1>
+<p>
+    $key.description
+</p>
+
+<table>
+    <tr>
+#foreach( $col in $columns )
+        <td>$col</td>
+#end
+    </tr>
+#foreach( $item in $values)
+    <tr>
+#foreach( $col in $columns )
+        <td>$item.info.get($col)</td>
+#end
+    </tr>
+#end
+## skipping where (origin of the property = field, method or class)
+</table>