src/share/classes/com/sun/tools/jdeps/JdepsTask.java

Print this page

        

@@ -22,16 +22,21 @@
  * or visit www.oracle.com if you need additional information or have any
  * questions.
  */
 package com.sun.tools.jdeps;
 
+import com.sun.tools.classfile.AccessFlags;
 import com.sun.tools.classfile.ClassFile;
 import com.sun.tools.classfile.ConstantPoolException;
 import com.sun.tools.classfile.Dependencies;
 import com.sun.tools.classfile.Dependencies.ClassFileError;
 import com.sun.tools.classfile.Dependency;
 import java.io.*;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.text.MessageFormat;
 import java.util.*;
 import java.util.regex.Pattern;
 
 /**

@@ -65,16 +70,15 @@
             return false;
         }
 
         boolean matches(String opt) {
             for (String a : aliases) {
-                if (a.equals(opt)) {
+                if (a.equals(opt))
                     return true;
-                } else if (opt.startsWith("--") && hasArg && opt.startsWith(a + "=")) {
+                if (hasArg && opt.startsWith(a + "="))
                     return true;
                 }
-            }
             return false;
         }
 
         boolean ignoreRest() {
             return false;

@@ -94,84 +98,108 @@
             return true;
         }
     }
 
     static Option[] recognizedOptions = {
-        new Option(false, "-h", "-?", "--help") {
+        new Option(false, "-h", "-?", "-help") {
             void process(JdepsTask task, String opt, String arg) {
                 task.options.help = true;
             }
         },
-        new Option(false, "-s", "--summary") {
-            void process(JdepsTask task, String opt, String arg) {
-                task.options.showSummary = true;
-                task.options.verbose = Analyzer.Type.SUMMARY;
+        new Option(true, "-dotoutput") {
+            void process(JdepsTask task, String opt, String arg) throws BadArgs {
+                Path p = Paths.get(arg);
+                if (Files.exists(p) && (!Files.isDirectory(p) || !Files.isWritable(p))) {
+                    throw new BadArgs("err.dot.output.path", arg);
+                }
+                task.options.dotOutputDir = arg;
             }
         },
-        new Option(false, "-v", "--verbose") {
+        new Option(false, "-s", "-summary") {
             void process(JdepsTask task, String opt, String arg) {
-                task.options.verbose = Analyzer.Type.VERBOSE;
+                task.options.showSummary = true;
+                task.options.verbose = Analyzer.Type.SUMMARY;
             }
         },
-        new Option(true, "-V", "--verbose-level") {
+        new Option(false, "-v", "-verbose",
+                          "-verbose:package",
+                          "-verbose:class")
+        {
             void process(JdepsTask task, String opt, String arg) throws BadArgs {
-                if ("package".equals(arg)) {
+                switch (opt) {
+                    case "-v":
+                    case "-verbose":
+                        task.options.verbose = Analyzer.Type.VERBOSE;
+                        break;
+                    case "-verbose:package":
                     task.options.verbose = Analyzer.Type.PACKAGE;
-                } else if ("class".equals(arg)) {
+                            break;
+                    case "-verbose:class":
                     task.options.verbose = Analyzer.Type.CLASS;
-                } else {
+                            break;
+                    default:
                     throw new BadArgs("err.invalid.arg.for.option", opt);
                 }
             }
         },
-        new Option(true, "-c", "--classpath") {
+        new Option(true, "-cp", "-classpath") {
             void process(JdepsTask task, String opt, String arg) {
                 task.options.classpath = arg;
             }
         },
-        new Option(true, "-p", "--package") {
+        new Option(true, "-p", "-package") {
             void process(JdepsTask task, String opt, String arg) {
                 task.options.packageNames.add(arg);
             }
         },
-        new Option(true, "-e", "--regex") {
+        new Option(true, "-e", "-regex") {
             void process(JdepsTask task, String opt, String arg) {
                 task.options.regex = arg;
             }
         },
-        new Option(false, "-P", "--profile") {
+        new Option(true, "-include") {
+            void process(JdepsTask task, String opt, String arg) throws BadArgs {
+                task.options.includePattern = Pattern.compile(arg);
+            }
+        },
+        new Option(false, "-P", "-profile") {
             void process(JdepsTask task, String opt, String arg) throws BadArgs {
                 task.options.showProfile = true;
-                if (Profiles.getProfileCount() == 0) {
+                if (Profile.getProfileCount() == 0) {
                     throw new BadArgs("err.option.unsupported", opt, getMessage("err.profiles.msg"));
                 }
             }
         },
-        new Option(false, "-R", "--recursive") {
+        new Option(false, "-apionly") {
             void process(JdepsTask task, String opt, String arg) {
-                task.options.depth = 0;
+                task.options.apiOnly = true;
             }
         },
-        new HiddenOption(true, "-d", "--depth") {
-            void process(JdepsTask task, String opt, String arg) throws BadArgs {
-                try {
-                    task.options.depth = Integer.parseInt(arg);
-                } catch (NumberFormatException e) {
-                    throw new BadArgs("err.invalid.arg.for.option", opt);
-                }
+        new Option(false, "-recursive") {
+            void process(JdepsTask task, String opt, String arg) {
+                task.options.depth = 0;
             }
         },
-        new Option(false, "--version") {
+        new Option(false, "-version") {
             void process(JdepsTask task, String opt, String arg) {
                 task.options.version = true;
             }
         },
-        new HiddenOption(false, "--fullversion") {
+        new HiddenOption(false, "-fullversion") {
             void process(JdepsTask task, String opt, String arg) {
                 task.options.fullVersion = true;
             }
         },
+        new HiddenOption(true, "-depth") {
+            void process(JdepsTask task, String opt, String arg) throws BadArgs {
+                try {
+                    task.options.depth = Integer.parseInt(arg);
+                } catch (NumberFormatException e) {
+                    throw new BadArgs("err.invalid.arg.for.option", opt);
+                }
+            }
+        },
     };
 
     private static final String PROGNAME = "jdeps";
     private final Options options = new Options();
     private final List<String> classes = new ArrayList<String>();

@@ -200,11 +228,11 @@
                 showHelp();
             }
             if (options.version || options.fullVersion) {
                 showVersion(options.fullVersion);
             }
-            if (classes.isEmpty() && !options.wildcard) {
+            if (classes.isEmpty() && options.includePattern == null) {
                 if (options.help || options.version || options.fullVersion) {
                     return EXIT_OK;
                 } else {
                     showHelp();
                     return EXIT_CMDERR;

@@ -231,23 +259,55 @@
         } finally {
             log.flush();
         }
     }
 
-    private final List<Archive> sourceLocations = new ArrayList<Archive>();
+    private final List<Archive> sourceLocations = new ArrayList<>();
     private boolean run() throws IOException {
         findDependencies();
         Analyzer analyzer = new Analyzer(options.verbose);
         analyzer.run(sourceLocations);
-        if (options.verbose == Analyzer.Type.SUMMARY) {
-            printSummary(log, analyzer);
+        if (options.dotOutputDir != null) {
+            Path dir = Paths.get(options.dotOutputDir);
+            Files.createDirectories(dir);
+            generateDotFiles(dir, analyzer);
         } else {
-            printDependencies(log, analyzer);
+            printRawOutput(log, analyzer);
         }
         return true;
     }
 
+    private void generateDotFiles(Path dir, Analyzer analyzer) throws IOException {
+        Path summary = dir.resolve("summary.dot");
+        try (PrintWriter sw = new PrintWriter(Files.newOutputStream(summary));
+             DotFileFormatter formatter = new DotFileFormatter(sw, "summary")) {
+            for (Archive archive : sourceLocations) {
+                 analyzer.visitArchiveDependences(archive, formatter);
+            }
+        }
+        if (options.verbose != Analyzer.Type.SUMMARY) {
+            for (Archive archive : sourceLocations) {
+                if (analyzer.hasDependences(archive)) {
+                    Path dotfile = dir.resolve(archive.getFileName() + ".dot");
+                    try (PrintWriter pw = new PrintWriter(Files.newOutputStream(dotfile));
+                         DotFileFormatter formatter = new DotFileFormatter(pw, archive)) {
+                        analyzer.visitDependences(archive, formatter);
+                    }
+                }
+            }
+        }
+    }
+
+    private void printRawOutput(PrintWriter writer, Analyzer analyzer) {
+        for (Archive archive : sourceLocations) {
+            RawOutputFormatter formatter = new RawOutputFormatter(writer);
+            analyzer.visitArchiveDependences(archive, formatter);
+            if (options.verbose != Analyzer.Type.SUMMARY) {
+                analyzer.visitDependences(archive, formatter);
+            }
+        }
+    }
     private boolean isValidClassName(String name) {
         if (!Character.isJavaIdentifierStart(name.charAt(0))) {
             return false;
         }
         for (int i=1; i < name.length(); i++) {

@@ -257,43 +317,58 @@
             }
         }
         return true;
     }
 
-    private void findDependencies() throws IOException {
-        Dependency.Finder finder = Dependencies.getClassDependencyFinder();
-        Dependency.Filter filter;
+    private Dependency.Filter getDependencyFilter() {
         if (options.regex != null) {
-            filter = Dependencies.getRegexFilter(Pattern.compile(options.regex));
+            return Dependencies.getRegexFilter(Pattern.compile(options.regex));
         } else if (options.packageNames.size() > 0) {
-            filter = Dependencies.getPackageFilter(options.packageNames, false);
+            return Dependencies.getPackageFilter(options.packageNames, false);
         } else {
-            filter = new Dependency.Filter() {
+            return new Dependency.Filter() {
+                @Override
                 public boolean accepts(Dependency dependency) {
                     return !dependency.getOrigin().equals(dependency.getTarget());
                 }
             };
         }
+    }
+
+    private boolean matches(String classname, AccessFlags flags) {
+        if (options.apiOnly && !flags.is(AccessFlags.ACC_PUBLIC)) {
+            return false;
+        } else if (options.includePattern != null) {
+            return options.includePattern.matcher(classname.replace('/', '.')).matches();
+        } else {
+            return true;
+        }
+    }
+
+    private void findDependencies() throws IOException {
+        Dependency.Finder finder =
+            options.apiOnly ? Dependencies.getAPIFinder(AccessFlags.ACC_PROTECTED)
+                            : Dependencies.getClassDependencyFinder();
+        Dependency.Filter filter = getDependencyFilter();
 
-        List<Archive> archives = new ArrayList<Archive>();
-        Deque<String> roots = new LinkedList<String>();
+        List<Archive> archives = new ArrayList<>();
+        Deque<String> roots = new LinkedList<>();
         for (String s : classes) {
-            File f = new File(s);
-            if (f.exists()) {
-                archives.add(new Archive(f, ClassFileReader.newInstance(f)));
+            Path p = Paths.get(s);
+            if (Files.exists(p)) {
+                archives.add(new Archive(p, ClassFileReader.newInstance(p)));
             } else {
                 if (isValidClassName(s)) {
                     roots.add(s);
                 } else {
                     warning("warn.invalid.arg", s);
                 }
             }
         }
 
-        List<Archive> classpaths = new ArrayList<Archive>(); // for class file lookup
-        if (options.wildcard) {
-            // include all archives from classpath to the initial list
+        List<Archive> classpaths = new ArrayList<>(); // for class file lookup
+        if (options.includePattern != null) {
             archives.addAll(getClassPathArchives(options.classpath));
         } else {
             classpaths.addAll(getClassPathArchives(options.classpath));
         }
         classpaths.addAll(PlatformClassPath.getArchives());

@@ -303,12 +378,12 @@
         sourceLocations.addAll(classpaths);
 
         // Work queue of names of classfiles to be searched.
         // Entries will be unique, and for classes that do not yet have
         // dependencies in the results map.
-        Deque<String> deque = new LinkedList<String>();
-        Set<String> doneClasses = new HashSet<String>();
+        Deque<String> deque = new LinkedList<>();
+        Set<String> doneClasses = new HashSet<>();
 
         // get the immediate dependencies of the input files
         for (Archive a : archives) {
             for (ClassFile cf : a.reader().getClassFiles()) {
                 String classFileName;

@@ -316,10 +391,11 @@
                     classFileName = cf.getName();
                 } catch (ConstantPoolException e) {
                     throw new ClassFileError(e);
                 }
 
+                if (matches(classFileName, cf.access_flags)) {
                 if (!doneClasses.contains(classFileName)) {
                     doneClasses.add(classFileName);
                 }
                 for (Dependency d : finder.findDependencies(cf)) {
                     if (filter.accepts(d)) {

@@ -330,10 +406,11 @@
                         a.addClass(d.getOrigin(), d.getTarget());
                     }
                 }
             }
         }
+        }
 
         // add Archive for looking up classes from the classpath
         // for transitive dependency analysis
         Deque<String> unresolved = roots;
         int depth = options.depth > 0 ? options.depth : Integer.MAX_VALUE;

@@ -377,59 +454,23 @@
                 if (cf == null) {
                     doneClasses.add(name);
                 }
             }
             unresolved = deque;
-            deque = new LinkedList<String>();
+            deque = new LinkedList<>();
         } while (!unresolved.isEmpty() && depth-- > 0);
     }
 
-    private void printSummary(final PrintWriter out, final Analyzer analyzer) {
-        Analyzer.Visitor visitor = new Analyzer.Visitor() {
-            public void visit(String origin, String target, String profile) {
-                if (options.showProfile) {
-                    out.format("%-30s -> %s%n", origin, target);
-                }
-            }
-            public void visit(Archive origin, Archive target) {
-                if (!options.showProfile) {
-                    out.format("%-30s -> %s%n", origin, target);
-                }
-            }
-        };
-        analyzer.visitSummary(visitor);
-    }
-
-    private void printDependencies(final PrintWriter out, final Analyzer analyzer) {
-        Analyzer.Visitor visitor = new Analyzer.Visitor() {
-            private String pkg = "";
-            public void visit(String origin, String target, String profile) {
-                if (!origin.equals(pkg)) {
-                    pkg = origin;
-                    out.format("   %s (%s)%n", origin, analyzer.getArchive(origin).getFileName());
-                }
-                out.format("      -> %-50s %s%n", target,
-                           (options.showProfile && !profile.isEmpty())
-                               ? profile
-                               : analyzer.getArchiveName(target, profile));
-            }
-            public void visit(Archive origin, Archive target) {
-                out.format("%s -> %s%n", origin, target);
-            }
-        };
-        analyzer.visit(visitor);
-    }
-
     public void handleOptions(String[] args) throws BadArgs {
         // process options
         for (int i=0; i < args.length; i++) {
             if (args[i].charAt(0) == '-') {
                 String name = args[i];
                 Option option = getOption(name);
                 String param = null;
                 if (option.hasArg) {
-                    if (name.startsWith("--") && name.indexOf('=') > 0) {
+                    if (name.startsWith("-") && name.indexOf('=') > 0) {
                         param = name.substring(name.indexOf('=') + 1, name.length());
                     } else if (i + 1 < args.length) {
                         param = args[++i];
                     }
                     if (param == null || param.isEmpty() || param.charAt(0) == '-') {

@@ -445,19 +486,15 @@
                 for (; i < args.length; i++) {
                     String name = args[i];
                     if (name.charAt(0) == '-') {
                         throw new BadArgs("err.option.after.class", name).showUsage(true);
                     }
-                    if (name.equals("*") || name.equals("\"*\"")) {
-                        options.wildcard = true;
-                    } else {
                         classes.add(name);
                     }
                 }
             }
         }
-    }
 
     private Option getOption(String name) throws BadArgs {
         for (Option o : recognizedOptions) {
             if (o.matches(name)) {
                 return o;

@@ -516,17 +553,19 @@
         boolean version;
         boolean fullVersion;
         boolean showProfile;
         boolean showSummary;
         boolean wildcard;
-        String regex;
+        boolean apiOnly;
+        String dotOutputDir;
         String classpath = "";
         int depth = 1;
         Analyzer.Type verbose = Analyzer.Type.PACKAGE;
-        Set<String> packageNames = new HashSet<String>();
+        Set<String> packageNames = new HashSet<>();
+        String regex;             // apply to the dependences
+        Pattern includePattern;   // apply to classes
     }
-
     private static class ResourceBundleHelper {
         static final ResourceBundle versionRB;
         static final ResourceBundle bundle;
 
         static {

@@ -545,31 +584,144 @@
     }
 
     private List<Archive> getArchives(List<String> filenames) throws IOException {
         List<Archive> result = new ArrayList<Archive>();
         for (String s : filenames) {
-            File f = new File(s);
-            if (f.exists()) {
-                result.add(new Archive(f, ClassFileReader.newInstance(f)));
+            Path p = Paths.get(s);
+            if (Files.exists(p)) {
+                result.add(new Archive(p, ClassFileReader.newInstance(p)));
             } else {
                 warning("warn.file.not.exist", s);
             }
         }
         return result;
     }
 
     private List<Archive> getClassPathArchives(String paths) throws IOException {
-        List<Archive> result = new ArrayList<Archive>();
+        List<Archive> result = new ArrayList<>();
         if (paths.isEmpty()) {
             return result;
         }
         for (String p : paths.split(File.pathSeparator)) {
             if (p.length() > 0) {
-                File f = new File(p);
-                if (f.exists()) {
+                List<Path> files = new ArrayList<>();
+                // wildcard to parse all JAR files e.g. -classpath dir/*
+                int i = p.lastIndexOf(".*");
+                if (i > 0) {
+                    Path dir = Paths.get(p.substring(0, i));
+                    try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, "*.jar")) {
+                        for (Path entry : stream) {
+                            files.add(entry);
+                        }
+                    }
+                } else {
+                    files.add(Paths.get(p));
+                }
+                for (Path f : files) {
+                    if (Files.exists(f)) {
                     result.add(new Archive(f, ClassFileReader.newInstance(f)));
                 }
             }
         }
+        }
         return result;
     }
+
+
+    /**
+     * Returns the file name of the archive for non-JRE class or
+     * internal JRE classes.  It returns empty string for SE API.
+     */
+    private static String getArchiveName(Archive source, String profile) {
+        String name = source.getFileName();
+        if (PlatformClassPath.contains(source))
+            return profile.isEmpty() ? "JDK internal API (" + name + ")" : "";
+        return name;
+    }
+
+    class RawOutputFormatter implements Analyzer.Visitor {
+        private final PrintWriter writer;
+        RawOutputFormatter(PrintWriter writer) {
+            this.writer = writer;
+        }
+
+        private String pkg = "";
+        @Override
+        public void visitDependence(String origin, Archive source,
+                                    String target, Archive archive, String profile) {
+            if (!origin.equals(pkg)) {
+                pkg = origin;
+                writer.format("   %s (%s)%n", origin, source.getFileName());
+            }
+            String name = (options.showProfile && !profile.isEmpty())
+                                ? profile
+                                : getArchiveName(archive, profile);
+            writer.format("      -> %-50s %s%n", target, name);
+        }
+
+        @Override
+        public void visitArchiveDependence(Archive origin, Archive target, String profile) {
+            writer.format("%s -> %s", origin, target);
+            if (options.showProfile && !profile.isEmpty()) {
+                writer.format(" (%s)%n", profile);
+            } else {
+                writer.format("%n");
+            }
+        }
+    }
+
+    class DotFileFormatter implements Analyzer.Visitor, AutoCloseable {
+        private final PrintWriter writer;
+        private final String name;
+        DotFileFormatter(PrintWriter writer, String name) {
+            this.writer = writer;
+            this.name = name;
+            writer.format("digraph \"%s\" {%n", name);
+        }
+        DotFileFormatter(PrintWriter writer, Archive archive) {
+            this.writer = writer;
+            this.name = archive.getFileName();
+            writer.format("digraph \"%s\" {%n", name);
+            writer.format("    // Path: %s%n", archive.toString());
+        }
+
+        @Override
+        public void close() {
+            writer.println("}");
+        }
+
+        private final Set<String> edges = new HashSet<>();
+        private String node = "";
+        @Override
+        public void visitDependence(String origin, Archive source,
+                                    String target, Archive archive, String profile) {
+            if (!node.equals(origin)) {
+                edges.clear();
+                node = origin;
+            }
+            // if -P option is specified, package name -> profile will
+            // be shown and filter out multiple same edges.
+            if (!edges.contains(target)) {
+                StringBuilder sb = new StringBuilder();
+                String name = options.showProfile && !profile.isEmpty()
+                                  ? profile
+                                  : getArchiveName(archive, profile);
+                writer.format("   %-50s -> %s;%n",
+                                 String.format("\"%s\"", origin),
+                                 name.isEmpty() ? String.format("\"%s\"", target)
+                                                :  String.format("\"%s (%s)\"", target, name));
+                edges.add(target);
+            }
+        }
+
+        @Override
+        public void visitArchiveDependence(Archive origin, Archive target, String profile) {
+             String name = options.showProfile && !profile.isEmpty()
+                                ? profile : "";
+             writer.format("   %-30s -> \"%s\";%n",
+                           String.format("\"%s\"", origin.getFileName()),
+                           name.isEmpty()
+                               ? target.getFileName()
+                               : String.format("%s (%s)", target.getFileName(), name));
+        }
+    }
 }