1.0.3
[scannotation] / src / main / java / org / scannotation / AnnotationDB.java
index 2c062f2..230bd6e 100644 (file)
@@ -26,12 +26,13 @@ import java.util.Set;
 
 /**
  * The class allows you to scan an arbitrary set of "archives" for .class files.  These class files
- * are parsed to see what annotations they use.  Two indexes are created.
- *
+ * are parsed to see what annotations they use.  Two indexes are created.  The javax, java, sun, com.sun, and javassist
+ * packages will not be scanned by default.
+ * <p/>
  * One is a map of annotations and what classes
  * use those annotations.   This could be used, for example, by an EJB deployer to find all the EJBs contained
  * in the archive
- *
+ * <p/>
  * Another is a mpa of classes and what annotations those classes use.
  *
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -47,22 +48,120 @@ public class AnnotationDB implements Serializable
    protected transient boolean scanMethodAnnotations = true;
    protected transient boolean scanParameterAnnotations = true;
    protected transient boolean scanFieldAnnotations = true;
+   protected transient String[] ignoredPackages = {"javax", "java", "sun", "com.sun", "javassist"};
+   protected transient String[] scanPackages = null;
 
-   public class CrossReferenceException extends RuntimeException
+   public class CrossReferenceException extends Exception
    {
-      private Map<String, Set<String>> unresolved;
+      private Set<String> unresolved;
 
-      public CrossReferenceException(Map<String, Set<String>> unresolved)
+      public CrossReferenceException(Set<String> unresolved)
       {
          this.unresolved = unresolved;
       }
 
-      public Map<String, Set<String>> getUnresolved()
+      public Set<String> getUnresolved()
       {
          return unresolved;
       }
    }
 
+   public String[] getScanPackages()
+   {
+      return scanPackages;
+   }
+
+   /**
+    * Set explicit packages to scan.
+    * Set to null to enable ignore list.
+    *
+    * @param scanPackages packages to scan or null
+    */
+   public void setScanPackages(String[] scanPackages)
+   {
+      this.scanPackages = scanPackages;
+   }
+
+   public String[] getIgnoredPackages()
+   {
+      return ignoredPackages;
+   }
+
+   /**
+    * Override/overwrite any ignored packages
+    *
+    * @param ignoredPackages cannot be null
+    */
+   public void setIgnoredPackages(String[] ignoredPackages)
+   {
+      this.ignoredPackages = ignoredPackages;
+   }
+
+   public void addIgnoredPackages(String... ignored)
+   {
+      String[] tmp = new String[ignoredPackages.length + ignored.length];
+      int i = 0;
+      for (String ign : ignoredPackages) tmp[i++] = ign;
+      for (String ign : ignored) tmp[i++] = ign;
+      this.ignoredPackages = tmp;
+   }
+
+   /**
+    * This method will cross reference annotations in the annotation index with any meta-annotations that they have
+    * and create additional entries as needed.  For example:
+    *
+    * @HttpMethod("GET") public @interface GET {}
+    * <p/>
+    * The HttpMethod index will have additional classes added to it for any classes annotated with annotations that
+    * have the HttpMethod meta-annotation.
+    * <p/>
+    * WARNING: If the annotation class has not already been scaned, this method will load all annotation classes indexed
+    * as a resource so they must be in your classpath
+    */
+   public void crossReferenceMetaAnnotations() throws CrossReferenceException
+   {
+      Set<String> unresolved = new HashSet<String>();
+
+      Set<String> index = new HashSet<String>();
+      index.addAll(annotationIndex.keySet());
+
+      for (String annotation : index)
+      {
+         if (ignoreScan(annotation))
+         {
+            continue;
+         }
+         if (classIndex.containsKey(annotation))
+         {
+            for (String xref : classIndex.get(annotation))
+            {
+               annotationIndex.get(xref).addAll(annotationIndex.get(annotation));
+            }
+            continue;
+         }
+         InputStream bits = Thread.currentThread().getContextClassLoader().getResourceAsStream(annotation.replace('.', '/') + ".class");
+         if (bits == null)
+         {
+            unresolved.add(annotation);
+            continue;
+         }
+         try
+         {
+            scanClass(bits);
+         }
+         catch (IOException e)
+         {
+            unresolved.add(annotation);
+         }
+         for (String xref : classIndex.get(annotation))
+         {
+            annotationIndex.get(xref).addAll(annotationIndex.get(annotation));
+         }
+
+      }
+      if (unresolved.size() > 0) throw new CrossReferenceException(unresolved);
+   }
+
    /**
     * Sometimes you want to see if a particular class implements an interface with certain annotations
     * After you have loaded all your classpaths with the scanArchive() method, call this method to cross reference
@@ -70,51 +169,74 @@ public class AnnotationDB implements Serializable
     * classIndex indexes
     *
     * @param ignoredPackages var arg list of packages to ignore
-    * @throws CrossReferenceException a RuntimeException thrown if referenced interfaces haven't been scanned
+    * @throws CrossReferenceException aException thrown if referenced interfaces haven't been scanned
     */
-   public void crossReferenceImplementedInterfaces(String... ignoredPackages) throws CrossReferenceException
+   public void crossReferenceImplementedInterfaces() throws CrossReferenceException
    {
-      Map<String, Set<String>> unresolved = new HashMap<String, Set<String>>();
+      Set<String> unresolved = new HashSet<String>();
       for (String clazz : implementsIndex.keySet())
       {
          Set<String> intfs = implementsIndex.get(clazz);
          for (String intf : intfs)
          {
-            if (intf.startsWith("java.") || intf.startsWith("javax.")) continue;
-            boolean ignoreInterface = false;
-            for (String ignored : ignoredPackages)
-            {
-               if (intf.startsWith(ignored + "."))
-               {
-                  ignoreInterface = true;
-                  break;
-               }
-            }
-            if (ignoreInterface) continue;
+            if (ignoreScan(intf)) continue;
 
-            Set<String> unresolvedInterfaces = new HashSet<String>();
             Set<String> xrefAnnotations = classIndex.get(intf);
             if (xrefAnnotations == null)
             {
-               unresolvedInterfaces.add(intf);
-               unresolved.put(clazz, unresolvedInterfaces);
+               unresolved.add(intf);
             }
-            Set<String> classAnnotations = classIndex.get(clazz);
-            classAnnotations.addAll(xrefAnnotations);
-            for (String annotation : xrefAnnotations)
+            else
             {
-               Set<String> classes = annotationIndex.get(annotation);
-               classes.add(clazz);
+               Set<String> classAnnotations = classIndex.get(clazz);
+               if (classAnnotations == null)
+               {
+                  classIndex.put(clazz, xrefAnnotations);
+               }
+               else classAnnotations.addAll(xrefAnnotations);
+               for (String annotation : xrefAnnotations)
+               {
+                  Set<String> classes = annotationIndex.get(annotation);
+                  classes.add(clazz);
+               }
             }
          }
       }
+      if (unresolved.size() > 0) throw new CrossReferenceException(unresolved);
 
    }
 
+   private boolean ignoreScan(String intf)
+   {
+         if (scanPackages != null)
+         {
+             for (String scan : scanPackages)
+             {
+                // do not ignore if on packages to scan list
+                if (intf.startsWith(scan + "."))
+                {
+                   return false;
+                }
+             }
+          return true; // didn't match whitelist, ignore
+         }
+      for (String ignored : ignoredPackages)
+      {
+         if (intf.startsWith(ignored + "."))
+         {
+            return true;
+         }
+         else
+         {
+            //System.out.println("NOT IGNORING: " + intf);
+         }
+      }
+      return false;
+   }
+
    /**
     * returns a map keyed by the fully qualified string name of a annotation class.  The Set returne is
     * a list of classes that use that annotation somehow.
-    *
     */
    public Map<String, Set<String>> getAnnotationIndex()
    {
@@ -124,7 +246,6 @@ public class AnnotationDB implements Serializable
    /**
     * returns a map keyed by the list of classes scanned.  The value set returned is a list of annotations
     * used by that class.
-    *
     */
    public Map<String, Set<String>> getClassIndex()
    {
@@ -187,7 +308,15 @@ public class AnnotationDB implements Serializable
          {
             public boolean accepts(String filename)
             {
-               return filename.endsWith(".class");
+               if (filename.endsWith(".class"))
+               {
+                  if (filename.startsWith("/") || filename.startsWith("\\"))
+                      filename = filename.substring(1);
+                  if (!ignoreScan(filename.replace('/', '.')))
+                      return true;
+                  //System.out.println("IGNORED: " + filename);
+               }
+               return false;
             }
          };
 
@@ -213,8 +342,8 @@ public class AnnotationDB implements Serializable
       {
          cf = new ClassFile(dstream);
          classIndex.put(cf.getName(), new HashSet<String>());
-         if (scanClassAnnotations) ;
-         scanClass(cf);
+         if (scanClassAnnotations)
+            scanClass(cf);
          if (scanMethodAnnotations || scanParameterAnnotations) scanMethods(cf);
          if (scanFieldAnnotations) scanFields(cf);
 
@@ -299,9 +428,9 @@ public class AnnotationDB implements Serializable
 
          if (visible != null) populate(visible.getAnnotations(), cf.getName());
          if (invisible != null) populate(invisible.getAnnotations(), cf.getName());
-
       }
 
+
    }
 
    protected void populate(Annotation[] annotations, String className)