Attached you will find a patch against the trunk for
com.sun.jini.tool.ClassDep that contains the aggregated code changes for
the issues RIVER-7, RIVER-8 and RIVER-151.

Please review and let me know what you think.
--
Mark

Index: ../src/com/sun/jini/tool/ClassDep.java
===================================================================
--- ../src/com/sun/jini/tool/ClassDep.java      (revision 615657)
+++ ../src/com/sun/jini/tool/ClassDep.java      Mon Feb 11 20:15:15 CET 2008
@@ -147,11 +147,49 @@
  * separator character. Each class in the tree needs to be in a package that
  * is defined to be "inside" the graph (as described further below).
  * <p>
- * The <code>-prune</code> option can be used to exclude particular subtrees
- * from the set of roots.
+ * <dt><b><var>directory</var></b>
+ * <dd>This option specifies the root directory of a tree of compiled class
+ * files that are considered as root for the dependency checking. This option
+ * can be specified zero or more times. The actual behavior depends on whether
+ * the <code>-newdirbehavior</code> options is specified. The directory must
+ * contain at least one filename separator character and be one of the
+ * directories specified in <var><b>input_classpath</b></var>, or when the old
+ * behavior is effective it can be a subdirectory of one of the directories on
+ * the <var><b>input_classpath</b></var>. Each class in the tree needs to be in
+ * a package that is defined to be "inside" the graph (as described further
+ * below).
  * <p>
+ * When the <code>-newdirbehavior</code> options is set the 
<code>-inroot</code>
+ * and <code>-outroot</code> options can be used to include/exclude particular
+ * subtrees from the potential set of roots. When the old behavior is effective
+ * all classes are considered as root, but can be exluded through the
+ * <code>-prune</code> option.
+ * <p>
  * <dl>
- * <dt><b><code>-prune</code> <var>package-prefix</var></b>
+ * <dt><b><code>-inroot</code> <var>package-prefix</var></b> (new behavior 
only)
+ * <dd>Specifies a package namespace to include when selecting roots from the
+ * directory trees. Any found classes that is in this package or a subpackage 
of
+ * it are considered as root for the dependency checking, unless they are
+ * explicitly excluded using <code>-out</code>, <code>-skip</code> or
+ * <code>-outroot</code> options. If not specified all packages are considered
+ * to be in the <code>inroot</code> namespace. This option can be specified 
zero
+ * or more times. Note that the argument to <code>-inroot</code> is a package
+ * namespace (delimited by "."), not a directory.</dd>
+ * </dl>
+ * <p>
+ * <dl>
+ * <dt><b><code>-outroot</code> <var>package-prefix</var></b> (new behavior
+ * only)
+ * <dd>Specifies a package namespace to exclude when selecting roots from the
+ * directory trees. Within the directory trees as specified by the
+ * <code>rootdir</code> element, any classes that are in the given package or a
+ * subpackage of it are not treated as roots. This option can be specified zero
+ * or more times. Note that the argument to <code>-outroot</code> is a package
+ * namespace (delimited by "."), not a directory.</dd>
+ * </dl>
+ * <p>
+ * <dl>
+ * <dt><b><code>-prune</code> <var>package-prefix</var> (old behavior only)</b>
  * <dd>Specifies a package namespace to exclude when selecting roots from
  * directory trees. Within the directory trees, any classes that are in the
  * given package or a subpackage of it are not treated as roots. Note that
@@ -236,13 +274,26 @@
  * <dt><b><code>-outer</code></b>
  * <dd>By default, if a static nested class is included in the dependency
  * graph, all references from that static nested class to its immediate
- * lexically enclosing class are ignored, to avoid inadvertent inclusion of
- * the enclosing class. (The default is chosen this way because the compiled
- * class file of a static nested class always contains a reference to the
- * immediate lexically enclosing class.) This option causes all such
- * references to be considered rather than ignored.  Note that this option
- * is needed very infrequently.</dd>
+ * lexically enclosing class are ignored (except when the static nested class
+ * extends its outer class), to avoid inadvertent inclusion of the enclosing
+ * class. (The default is chosen this way because the compiled class file of a
+ * static nested class always contains a reference to the immediate lexically
+ * enclosing class.) This option causes all such references to be considered
+ * rather than ignored.  Note that this option is needed very 
infrequently.</dd>
  * </dl>
+ * <p>
+ * <dt><b><code>-newdirbehavior</code></b>
+ * <dd>This option causes the utility to select classes, to serve as root for
+ * the dependency checking, from the directory argument based on the
+ * <code>-inroot</code> and <code>-outroot</code> options specified. When
+ * this option is set subdirectories of the specified directory are no longer
+ * considered as root for finding classes. When this option is not set, the
+ * default, is that the utility maintains the old behavior with respect to the
+ * semantics for the directories passed in and the <code>-prune</code> option
+ * must be used. You are advised to set this option as there are some edge 
cases
+ * for which the old behavior won't work as expected, especially when no
+ * <code>-in</code> options are set.</dd>
+ * </dl>
  *
  * <a name="output"></a>
  * <h3>Output Options and Arguments</h3>
@@ -481,6 +532,22 @@
      */
     private  final ArrayList prunes  = new ArrayList();
     /**
+     * Indicates whether the root directories specified must for finding 
classes
+     * to include for dependency checking in the 'old' way, or that the new
+     * behavior must be effective.
+     */
+    private boolean newRootDirBehavior;
+    /**
+     * Set of packages to include when classes are found through the specified
+     * root directories.
+     */
+    private  final ArrayList insideRoots  = new ArrayList();
+    /**
+     * Set of packages to exclude when classes are found through the specified
+     * root directories.
+     */
+    private  final ArrayList outsideRoots  = new ArrayList();
+    /**
      * Set of package prefixes to skip over in the processing 
      * of dependencies. 
      */
@@ -507,6 +574,12 @@
     private final ArrayList results = new ArrayList();
 
     /**
+     * Indicates whether a failure has been encountered during deep dependency
+     * checking.
+     */
+    private boolean failed;
+
+    /**
      * No argument constructor. The user must fill in the
      * appropriate fields prior to asking for the processing
      * of dependencies.
@@ -595,9 +668,11 @@
      * Recursively traverse a given path, finding all the classes that
      * make up the set to work with. We take into account skips,
      * prunes, and out sets defined.
+     * <p>
+     * This implementation is here to maintain the old behavior with reagard
+     * to how root directories were interpreted.
      *
      * @param path path to traverse down from
-     *
      */
     private void traverse(String path) {
        String apath = path;
@@ -686,10 +761,10 @@
                                         ((String)inside.get(j)).replace(
                                                     '.', File.separatorChar));
                    if (k >= 0) {
-                               /*
-                                * Insert the class and make sure to replace
-                                * File.separators into dots.
-                                */
+                       /*
+                        * Insert the class and make sure to replace
+                        * File.separators into dots.
+                        */
                        classes.add(file.substring(k + 1).replace(
                                                   File.separatorChar, '.'));
                    }
@@ -699,6 +774,88 @@
     }
 
     /**
+     * Recursively traverse a given path, finding all the classes that make up
+     * the set of classes to work with. We take into account inroot and outroot
+     * sets, skips, and the in and out sets defined.
+     *
+     * @param path path to traverse down from
+     * @param rootPath path to the directory that serves as the root for the
+     *        class files found, any path component below the root is part of
+     *        the full qualified name of the class
+     */
+    private void traverse(String path, String rootPath) {
+       // get the current list of files at the current directory we are in. If
+       // there are no files then leave this current recursive thread.
+       String[] files = new File(path).list();
+       if (files == null)
+           return;
+    outer:
+       //take the found list of files and iterate over them
+       for (int i = 0; i < files.length; i++) {
+           String file = files[i];
+           // see if we have a ".class" file. If not then we call ourselves
+           // again, assuming it is a directory, if not the call will return.
+           if (!file.endsWith(".class")) {
+               traverse(path + File.separatorChar + file, rootPath);
+           } else {
+               // determine the package name in File.Separators notation
+               String packageName = path.substring(rootPath.length(),
+                                                   path.length());
+
+               // when we have in roots defined verify whether we are inside
+               if (!insideRoots.isEmpty()) {
+                   boolean matched = false;
+                   for (int j = 0; j < insideRoots.size(); j++) {
+                       if (packageName.startsWith(
+                               (String) insideRoots.get(j))) {
+                           matched = true;
+                           break;
+                       }
+                   }
+                   if (!matched) {
+                       continue;
+                   }
+               }
+
+               // when we have out roots and we are at this level outside we
+               // can break the recursion
+               if (!outsideRoots.isEmpty()) {
+                   for (int j = 0; j < outsideRoots.size(); j++) {
+                       if (packageName.startsWith(
+                               (String) outsideRoots.get(j))) {
+                           return;
+                       }
+                   }
+               }
+
+               // determine the fully qualified class name, but with dots
+               // converted to File.Separators and starting with a
+               // File.Separators as well
+               String className = packageName + File.separatorChar
+                                  + file.substring(0, file.length() - 6);
+               // see if there are any class files that need to be skipped, the
+               // skip classes are in the above notation as well
+               for (int j = 0; j < skips.size(); j++) {
+                   String skip = (String) skips.get(j);
+                   if (!className.startsWith(skip)) {
+                       continue;
+                   }
+                   // if we matched the entire class or if we have a class with
+                   // an inner class, skip it and go on to the next outer loop
+                   if (className.length() == skip.length()
+                           || className.charAt(skip.length()) == '$')
+                       continue outer;
+               }
+
+               // we found a class that satisfy all the criteria, convert it
+               // to the proper notation
+               classes.add(className.substring(1).replace(File.separatorChar,
+                                                          '.'));
+           }
+       }
+    }
+
+    /**
      * Depending on the part of the class file
      * that we are on the class types that we are
      * looking for can come in several flavors.
@@ -855,12 +1012,15 @@
            cdef = (BinaryClass)env.getClassDefinition(id);
            cdef.loadNested(env);
        } catch (ClassNotFound e) {
+           failed = true;
            print("classdep.notfound", id);
            return;
        } catch (IllegalArgumentException e) {
+           failed = true;
            print("classdep.illegal", id, e.getMessage());
            return;
        } catch (Exception e) {
+           failed = true;
            print("classdep.failed", id);
            e.printStackTrace();
            return;
@@ -878,14 +1038,18 @@
        for (Enumeration deps = cdef.getDependencies();
             deps.hasMoreElements(); )
        {
-           Identifier dep = ((ClassDeclaration)deps.nextElement()).getName();
+           ClassDeclaration cd = (ClassDeclaration) deps.nextElement();
            /*
             * If we dont' want the outer parent class of an inner class
-            * make this comparison.
+            * make this comparison, except when it is clear the outer class is
+            * the super class of the static nested class.
             */
-           if (outer != dep)
-               process(id, dep, false);
+           if (outer != cd.getName()
+                   || (outer != null && cdef.getSuperClass() == cd)) {
+
+               process(id, cd.getName(), false);
-       }
+           }
+       }
 
 
        /*
@@ -920,9 +1084,16 @@
     /**
      * Method that takes the user provided switches that
      * logically define the domain in which to look for
-     * dependencies. 
+     * dependencies.
+     * <p>
+     * Whether a failure has occurred during computing the dependent classes
+     * can be found out with a call to [EMAIL PROTECTED] #hasFailed()}.
+     *
+     * @return array containing the dependent classes found in the format as
+     * specified by the options passed in
      */
     public String[] compute() {
+       failed = false;
        /* sort ins and outs, longest first */
        Comparator c = new Compare();
        Collections.sort(inside, c);
@@ -942,8 +1113,13 @@
            /*
             * Get the classes that we want do to dependency checking on.
             */
+           if (newRootDirBehavior) {
+               traverse((String)roots.get(i), (String)roots.get(i));
+           }
+           else {
-           traverse((String)roots.get(i));
-       }
+               traverse((String)roots.get(i));
+           }
+       }
        for (int i = 0; i < classes.size(); i++) {
            process(null, Identifier.lookup((String)classes.get(i)), true);
        }
@@ -1024,6 +1200,10 @@
      * Add an entry into the set of package prefixes
      * that will be skipped as part of the dependency
      * generation.
+     * <p>
+     * This method has no impact if the new behavior is effective for the
+     * interpretation of the root directories for finding class files to
+     * include for dependency checking.
      */
     public void addPrune(String packagePrefix) {
        String arg = packagePrefix;
@@ -1037,6 +1217,55 @@
     }
 
     /**
+     * Controls whether the behavior for finding class files in the specified
+     * directories, if any, must be based on the old behavior (the default) or
+     * the new behavior that solves some of the problems with the old behavior.
+     */
+    public void setRootDirBehavior(boolean newBehavior) {
+       newRootDirBehavior = newBehavior;
+    }
+
+    /**
+     * Adds an entry into the set of package prefixes for which classes found
+     * through the specified root directories will be considered for dependency
+     * generation.
+     * <p>
+     * This method has no impact if the old behavior is effective for the
+     * interpretation of the root directories for finding class files to
+     * include for dependency checking.
+     */
+    public void addInsideRoot(String packagePrefix) {
+       String arg = packagePrefix;
+       if (arg.endsWith("."))
+           arg = arg.substring(0, arg.length() - 1);
+       /*
+        * Convert dots into File.separator for later usage.
+        */
+       arg = File.separator + arg.replace('.', File.separatorChar);
+       insideRoots.add(arg);
+    }
+
+    /**
+     * Adds an entry into the set of package prefixes for which classes found
+     * through the specified root directories, and that are part of the inside
+     * root namespace, will be skipped as part of the dependency generation.
+     * <p>
+     * This method has no impact if the old behavior is effective for the
+     * interpretation of the root directories for finding class files to
+     * include for dependency checking.
+     */
+    public void addOutsideRoot(String packagePrefix) {
+       String arg = packagePrefix;
+       if (arg.endsWith("."))
+           arg = arg.substring(0, arg.length() - 1);
+       /*
+        * Convert dots into File.separator for later usage.
+        */
+       arg = File.separator + arg.replace('.', File.separatorChar);
+       outsideRoots.add(arg);
+    }
+
+    /**
      * Add an entry into the set of package prefixes
      * that we want to display.
      * This applies only to the final output, so this
@@ -1097,7 +1326,7 @@
        classes.add(className);
     }
 
-    /**
+   /**
      * If true classnames will be separated using
      * File.separator, else it will use dots.
      */
@@ -1115,6 +1344,17 @@
     }
 
     /**
+     * Indicates whether computing the dependent classes as result of the last
+     * call to [EMAIL PROTECTED] #compute()} resulted in one or more failures.
+     *
+     * @return <code>true</code> in case a failure has happened, such as a
+     * class not being found, <code>false</code> otherwise
+     */
+    public boolean hasFailed() {
+       return failed;
+    }
+
+    /**
      * Convenience method for initializing an instance with specific
      * command line arguments. See the description of this class
      * for a list and description of the acceptable arguments.
@@ -1122,7 +1362,10 @@
     public void setupOptions(String[] args) {
        for (int i = 0; i < args.length ; i++ ) {
            String arg = args[i];
-           if (arg.equals("-cp")) {
+           if (args.equals("-newdirbehavior")) {
+               newRootDirBehavior = true;
+           }
+           else if (arg.equals("-cp")) {
                i++;
                setClassPath(args[i]);
            } else if (arg.equals("-files")) {
@@ -1143,6 +1386,12 @@
            } else if (arg.equals("-prune")) {
                i++;
                addPrune(args[i]);
+           } else if (arg.equals("-inroot")) {
+               i++;
+               addInsideRoot(args[i]);
+           } else if (arg.equals("-outroot")) {
+               i++;
+               addOutsideRoot(args[i]);
            } else if (arg.equals("-show")) {
                i++;
                addShow(args[i]);

Reply via email to