8213909: jdeps --print-module-deps should report missing dependences
authormchung
Wed, 21 Nov 2018 22:34:01 -0800
changeset 52650 c16b6cc93272
parent 52649 e00cf18e2593
child 52651 526b2490c616
8213909: jdeps --print-module-deps should report missing dependences 8168869: jdeps: localized messages don't use proper line breaks Reviewed-by: sundar
src/jdk.jdeps/share/classes/com/sun/tools/jdeps/Analyzer.java
src/jdk.jdeps/share/classes/com/sun/tools/jdeps/DependencyFinder.java
src/jdk.jdeps/share/classes/com/sun/tools/jdeps/JdepsConfiguration.java
src/jdk.jdeps/share/classes/com/sun/tools/jdeps/JdepsFilter.java
src/jdk.jdeps/share/classes/com/sun/tools/jdeps/JdepsTask.java
src/jdk.jdeps/share/classes/com/sun/tools/jdeps/Module.java
src/jdk.jdeps/share/classes/com/sun/tools/jdeps/ModuleExportsAnalyzer.java
src/jdk.jdeps/share/classes/com/sun/tools/jdeps/ModuleInfoBuilder.java
src/jdk.jdeps/share/classes/com/sun/tools/jdeps/resources/jdeps.properties
test/langtools/tools/jdeps/Basic.java
test/langtools/tools/jdeps/MultiReleaseJar.java
test/langtools/tools/jdeps/Options.java
test/langtools/tools/jdeps/listdeps/ListModuleDeps.java
test/langtools/tools/jdeps/listdeps/src/lib/Lib.java
test/langtools/tools/jdeps/mrjar/Main.java
test/langtools/tools/jdeps/mrjar/test/Main.java
--- a/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/Analyzer.java	Wed Nov 21 22:33:33 2018 -0800
+++ b/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/Analyzer.java	Wed Nov 21 22:34:01 2018 -0800
@@ -32,6 +32,7 @@
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.UncheckedIOException;
+import java.lang.module.ModuleDescriptor;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
@@ -39,6 +40,7 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.function.Predicate;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
@@ -74,6 +76,7 @@
     protected final Map<Location, Archive> locationToArchive = new HashMap<>();
     static final Archive NOT_FOUND
         = new Archive(JdepsTask.getMessage("artifact.not.found"));
+    static final Predicate<Archive> ANY = a -> true;
 
     /**
      * Constructs an Analyzer instance.
@@ -161,7 +164,7 @@
      * Visit the dependencies of the given source.
      * If the requested level is SUMMARY, it will visit the required archives list.
      */
-    void visitDependences(Archive source, Visitor v, Type level) {
+    void visitDependences(Archive source, Visitor v, Type level, Predicate<Archive> targetFilter) {
         if (level == Type.SUMMARY) {
             final Dependences result = results.get(source);
             final Set<Archive> reqs = result.requires();
@@ -184,7 +187,7 @@
             Dependences result = results.get(source);
             if (level != type) {
                 // requesting different level of analysis
-                result = new Dependences(source, level);
+                result = new Dependences(source, level, targetFilter);
                 source.visitDependences(result);
             }
             result.dependencies().stream()
@@ -196,7 +199,11 @@
     }
 
     void visitDependences(Archive source, Visitor v) {
-        visitDependences(source, v, type);
+        visitDependences(source, v, type, ANY);
+    }
+
+    void visitDependences(Archive source, Visitor v, Type level) {
+        visitDependences(source, v, level, ANY);
     }
 
     /**
@@ -208,12 +215,17 @@
         protected final Set<Archive> requires;
         protected final Set<Dep> deps;
         protected final Type level;
+        protected final Predicate<Archive> targetFilter;
         private Profile profile;
         Dependences(Archive archive, Type level) {
+            this(archive, level, ANY);
+        }
+        Dependences(Archive archive, Type level, Predicate<Archive> targetFilter) {
             this.archive = archive;
             this.deps = new HashSet<>();
             this.requires = new HashSet<>();
             this.level = level;
+            this.targetFilter = targetFilter;
         }
 
         Set<Dep> dependencies() {
@@ -266,7 +278,7 @@
         @Override
         public void visit(Location o, Location t) {
             Archive targetArchive = findArchive(t);
-            if (filter.accepts(o, archive, t, targetArchive)) {
+            if (filter.accepts(o, archive, t, targetArchive) && targetFilter.test(targetArchive)) {
                 addDep(o, t);
                 if (archive != targetArchive && !requires.contains(targetArchive)) {
                     requires.add(targetArchive);
@@ -368,13 +380,21 @@
         }
     }
 
+    /*
+     * Returns true if the given archive represents not found.
+     */
+    static boolean notFound(Archive archive) {
+        return archive == NOT_FOUND || archive == REMOVED_JDK_INTERNALS;
+    }
+
     static final Jdk8Internals REMOVED_JDK_INTERNALS = new Jdk8Internals();
 
     static class Jdk8Internals extends Module {
-        private final String JDK8_INTERNALS = "/com/sun/tools/jdeps/resources/jdk8_internals.txt";
+        private static final String NAME = "JDK removed internal API";
+        private static final String JDK8_INTERNALS = "/com/sun/tools/jdeps/resources/jdk8_internals.txt";
         private final Set<String> jdk8Internals;
         private Jdk8Internals() {
-            super("JDK removed internal API");
+            super(NAME, ModuleDescriptor.newModule("jdk8internals").build(), true);
             try (InputStream in = JdepsTask.class.getResourceAsStream(JDK8_INTERNALS);
                  BufferedReader reader = new BufferedReader(new InputStreamReader(in))) {
                 this.jdk8Internals = reader.lines()
@@ -394,11 +414,6 @@
         }
 
         @Override
-        public String name() {
-            return getName();
-        }
-
-        @Override
         public boolean isJDK() {
             return true;
         }
--- a/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/DependencyFinder.java	Wed Nov 21 22:33:33 2018 -0800
+++ b/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/DependencyFinder.java	Wed Nov 21 22:34:01 2018 -0800
@@ -38,6 +38,7 @@
 
 import java.io.IOException;
 import java.io.UncheckedIOException;
+import java.nio.file.Paths;
 import java.util.Collections;
 import java.util.Deque;
 import java.util.HashMap;
@@ -172,7 +173,7 @@
 
         parsedArchives.get(finder).add(archive);
 
-        trace("parsing %s %s%n", archive.getName(), archive.path());
+        trace("parsing %s %s%n", archive.getName(), archive.getPathName());
         FutureTask<Set<Location>> task = new FutureTask<>(() -> {
             Set<Location> targets = new HashSet<>();
             for (ClassFile cf : archive.reader().getClassFiles()) {
@@ -206,7 +207,6 @@
                     parsedClasses.putIfAbsent(d.getOrigin(), archive);
                 }
             }
-
             return targets;
         });
         tasks.add(task);
@@ -264,8 +264,7 @@
             FutureTask<Set<Location>> task;
             while ((task = tasks.poll()) != null) {
                 // wait for completion
-                if (!task.isDone())
-                    targets.addAll(task.get());
+                targets.addAll(task.get());
             }
             return targets;
         } catch (InterruptedException|ExecutionException e) {
--- a/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/JdepsConfiguration.java	Wed Nov 21 22:33:33 2018 -0800
+++ b/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/JdepsConfiguration.java	Wed Nov 21 22:34:01 2018 -0800
@@ -83,42 +83,27 @@
     private final List<Archive> classpathArchives = new ArrayList<>();
     private final List<Archive> initialArchives = new ArrayList<>();
     private final Set<Module> rootModules = new HashSet<>();
-    private final Configuration configuration;
     private final Runtime.Version version;
 
-    private JdepsConfiguration(SystemModuleFinder systemModulePath,
+    private JdepsConfiguration(Configuration config,
+                               SystemModuleFinder systemModulePath,
                                ModuleFinder finder,
                                Set<String> roots,
                                List<Path> classpaths,
                                List<Archive> initialArchives,
-                               Set<String> tokens,
                                Runtime.Version version)
         throws IOException
     {
         trace("root: %s%n", roots);
-
+        trace("initial archives: %s%n", initialArchives);
+        trace("class path: %s%n", classpaths);
         this.system = systemModulePath;
         this.finder = finder;
         this.version = version;
 
-        // build root set for resolution
-        Set<String> mods = new HashSet<>(roots);
-        if (tokens.contains(ALL_SYSTEM)) {
-            systemModulePath.findAll().stream()
-                .map(mref -> mref.descriptor().name())
-                .forEach(mods::add);
-        }
-
-        if (tokens.contains(ALL_DEFAULT)) {
-            mods.addAll(systemModulePath.defaultSystemRoots());
-        }
-
-        this.configuration = Configuration.empty()
-                .resolve(finder, ModuleFinder.of(), mods);
-
-        this.configuration.modules().stream()
-                .map(ResolvedModule::reference)
-                .forEach(this::addModuleReference);
+        config.modules().stream()
+              .map(ResolvedModule::reference)
+              .forEach(this::addModuleReference);
 
         // packages in unnamed module
         initialArchives.forEach(archive -> {
@@ -538,14 +523,6 @@
                         .forEach(rootModules::add);
             }
 
-            // add all modules to the root set for unnamed module or set explicitly
-            boolean unnamed = !initialArchives.isEmpty() || !classPaths.isEmpty();
-            if ((unnamed || tokens.contains(ALL_MODULE_PATH)) && appModulePath != null) {
-                appModulePath.findAll().stream()
-                    .map(mref -> mref.descriptor().name())
-                    .forEach(rootModules::add);
-            }
-
             // no archive is specified for analysis
             // add all system modules as root if --add-modules ALL-SYSTEM is specified
             if (tokens.contains(ALL_SYSTEM) && rootModules.isEmpty() &&
@@ -556,16 +533,41 @@
                     .forEach(rootModules::add);
             }
 
-            if (unnamed && !tokens.contains(ALL_DEFAULT)) {
-                tokens.add(ALL_SYSTEM);
+            // add all modules on app module path as roots if ALL-MODULE-PATH is specified
+            if ((tokens.contains(ALL_MODULE_PATH)) && appModulePath != null) {
+                appModulePath.findAll().stream()
+                    .map(mref -> mref.descriptor().name())
+                    .forEach(rootModules::add);
             }
 
-            return new JdepsConfiguration(systemModulePath,
+
+            // build root set for module resolution
+            Set<String> mods = new HashSet<>(rootModules);
+            // if archives are specified for analysis, then consider as unnamed module
+            boolean unnamed = !initialArchives.isEmpty() || !classPaths.isEmpty();
+            if (tokens.contains(ALL_DEFAULT)) {
+                mods.addAll(systemModulePath.defaultSystemRoots());
+            } else if (tokens.contains(ALL_SYSTEM) || unnamed) {
+                // resolve all system modules as unnamed module may reference any class
+                systemModulePath.findAll().stream()
+                    .map(mref -> mref.descriptor().name())
+                    .forEach(mods::add);
+            }
+            if (unnamed && appModulePath != null) {
+                // resolve all modules on module path as unnamed module may reference any class
+                appModulePath.findAll().stream()
+                    .map(mref -> mref.descriptor().name())
+                    .forEach(mods::add);
+            }
+
+            // resolve the module graph
+            Configuration config = Configuration.empty().resolve(finder, ModuleFinder.of(), mods);
+            return new JdepsConfiguration(config,
+                                          systemModulePath,
                                           finder,
                                           rootModules,
                                           classPaths,
                                           initialArchives,
-                                          tokens,
                                           version);
         }
 
--- a/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/JdepsFilter.java	Wed Nov 21 22:33:33 2018 -0800
+++ b/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/JdepsFilter.java	Wed Nov 21 22:34:01 2018 -0800
@@ -55,6 +55,7 @@
     private final boolean filterSamePackage;
     private final boolean filterSameArchive;
     private final boolean findJDKInternals;
+    private final boolean findMissingDeps;
     private final Pattern includePattern;
 
     private final Set<String> requires;
@@ -64,6 +65,7 @@
                         boolean filterSamePackage,
                         boolean filterSameArchive,
                         boolean findJDKInternals,
+                        boolean findMissingDeps,
                         Pattern includePattern,
                         Set<String> requires) {
         this.filter = filter;
@@ -71,6 +73,7 @@
         this.filterSamePackage = filterSamePackage;
         this.filterSameArchive = filterSameArchive;
         this.findJDKInternals = findJDKInternals;
+        this.findMissingDeps = findMissingDeps;
         this.includePattern = includePattern;
         this.requires = requires;
     }
@@ -153,6 +156,8 @@
             Module module = targetArchive.getModule();
             return originArchive != targetArchive &&
                     isJDKInternalPackage(module, target.getPackageName());
+        } else if (findMissingDeps) {
+            return Analyzer.notFound(targetArchive);
         } else if (filterSameArchive) {
             // accepts origin and target that from different archive
             return originArchive != targetArchive;
@@ -188,6 +193,7 @@
         boolean filterSamePackage;
         boolean filterSameArchive;
         boolean findJDKInterals;
+        boolean findMissingDeps;
         // source filters
         Pattern includePattern;
         Set<String> requires = new HashSet<>();
@@ -221,6 +227,10 @@
             this.findJDKInterals = value;
             return this;
         }
+        public Builder findMissingDeps(boolean value) {
+            this.findMissingDeps = value;
+            return this;
+        }
         public Builder includePattern(Pattern regex) {
             this.includePattern = regex;
             return this;
@@ -238,6 +248,7 @@
                                    filterSamePackage,
                                    filterSameArchive,
                                    findJDKInterals,
+                                   findMissingDeps,
                                    includePattern,
                                    requires);
         }
--- a/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/JdepsTask.java	Wed Nov 21 22:33:33 2018 -0800
+++ b/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/JdepsTask.java	Wed Nov 21 22:34:01 2018 -0800
@@ -357,6 +357,11 @@
                 task.command = task.listModuleDeps(CommandOption.PRINT_MODULE_DEPS);
             }
         },
+        new Option(false, "--ignore-missing-deps") {
+            void process(JdepsTask task, String opt, String arg) {
+                task.options.ignoreMissingDeps = true;
+            }
+        },
 
         // ---- Target filtering options ----
         new Option(true, "-p", "-package", "--package") {
@@ -401,6 +406,11 @@
                 }
             }
         },
+        new Option(false, "--missing-deps") {
+            void process(JdepsTask task, String opt, String arg) {
+                task.options.findMissingDeps = true;
+            }
+        },
 
         // ---- Source filtering options ----
         new Option(true, "-include") {
@@ -415,15 +425,19 @@
             }
         },
 
-        new Option(false, "-R", "-recursive") {
-            void process(JdepsTask task, String opt, String arg) {
-                task.options.depth = 0;
+        new Option(false, "-R", "-recursive", "--recursive") {
+            void process(JdepsTask task, String opt, String arg) throws BadArgs {
+                task.options.recursive = Options.RECURSIVE;
                 // turn off filtering
                 task.options.filterSameArchive = false;
                 task.options.filterSamePackage = false;
             }
         },
-
+        new Option(false, "--no-recursive") {
+            void process(JdepsTask task, String opt, String arg) throws BadArgs {
+                task.options.recursive = Options.NO_RECURSIVE;
+            }
+        },
         new Option(false, "-I", "--inverse") {
             void process(JdepsTask task, String opt, String arg) {
                 task.options.inverse = true;
@@ -437,9 +451,9 @@
         new Option(false, "--compile-time") {
             void process(JdepsTask task, String opt, String arg) {
                 task.options.compileTimeView = true;
+                task.options.recursive = Options.RECURSIVE;
                 task.options.filterSamePackage = true;
                 task.options.filterSameArchive = true;
-                task.options.depth = 0;
             }
         },
 
@@ -611,6 +625,13 @@
     }
 
     private ListModuleDeps listModuleDeps(CommandOption option) throws BadArgs {
+        // do transitive dependence analysis unless --no-recursive is set
+        if (options.recursive != Options.NO_RECURSIVE) {
+            options.recursive = Options.RECURSIVE;
+        }
+        // no need to record the dependences on the same archive or same package
+        options.filterSameArchive = true;
+        options.filterSamePackage = true;
         switch (option) {
             case LIST_DEPS:
                 return new ListModuleDeps(option, true, false);
@@ -677,16 +698,16 @@
 
         @Override
         boolean checkOptions() {
-            if (options.findJDKInternals) {
+            if (options.findJDKInternals || options.findMissingDeps) {
                 // cannot set any filter, -verbose and -summary option
                 if (options.showSummary || options.verbose != null) {
                     reportError("err.invalid.options", "-summary or -verbose",
-                                "-jdkinternals");
+                        options.findJDKInternals ? "-jdkinternals" : "--missing-deps");
                     return false;
                 }
                 if (options.hasFilter()) {
                     reportError("err.invalid.options", "--package, --regex, --require",
-                                "-jdkinternals");
+                        options.findJDKInternals ? "-jdkinternals" : "--missing-deps");
                     return false;
                 }
             }
@@ -715,7 +736,7 @@
             if (options.showSummary)
                 return Type.SUMMARY;
 
-            if (options.findJDKInternals)
+            if (options.findJDKInternals || options.findMissingDeps)
                 return Type.CLASS;
 
             // default to package-level verbose
@@ -744,7 +765,7 @@
                                                      type,
                                                      options.apiOnly);
 
-            boolean ok = analyzer.run(options.compileTimeView, options.depth);
+            boolean ok = analyzer.run(options.compileTimeView, options.depth());
 
             // print skipped entries, if any
             if (!options.nowarning) {
@@ -797,8 +818,8 @@
 
         @Override
         boolean checkOptions() {
-            if (options.depth != 1) {
-                reportError("err.invalid.options", "-R", "--inverse");
+            if (options.recursive != -1 || options.depth != -1) {
+                reportError("err.invalid.options", "--recursive and --no-recursive", "--inverse");
                 return false;
             }
 
@@ -925,12 +946,7 @@
 
             if (!ok && !options.nowarning) {
                 reportError("err.missing.dependences");
-                builder.visitMissingDeps(
-                        (origin, originArchive, target, targetArchive) -> {
-                            if (builder.notFound(targetArchive))
-                                log.format("   %-50s -> %-50s %s%n",
-                                    origin, target, targetArchive.getName());
-                        });
+                builder.visitMissingDeps(new SimpleDepVisitor());
             }
             return ok;
         }
@@ -993,13 +1009,15 @@
         @Override
         boolean checkOptions() {
             if (options.showSummary || options.verbose != null) {
-                reportError("err.invalid.options", "-summary or -verbose",
-                            option);
+                reportError("err.invalid.options", "-summary or -verbose", option);
                 return false;
             }
             if (options.findJDKInternals) {
-                reportError("err.invalid.options", "-jdkinternals",
-                            option);
+                reportError("err.invalid.options", "-jdkinternals", option);
+                return false;
+            }
+            if (options.findMissingDeps) {
+                reportError("err.invalid.options", "--missing-deps", option);
                 return false;
             }
 
@@ -1015,16 +1033,22 @@
 
         @Override
         boolean run(JdepsConfiguration config) throws IOException {
-            return new ModuleExportsAnalyzer(config,
-                                             dependencyFilter(config),
-                                             jdkinternals,
-                                             reduced,
-                                             log,
-                                             separator).run();
+            ModuleExportsAnalyzer analyzer = new ModuleExportsAnalyzer(config,
+                                                                       dependencyFilter(config),
+                                                                       jdkinternals,
+                                                                       reduced,
+                                                                       log,
+                                                                       separator);
+            boolean ok = analyzer.run(options.depth(), options.ignoreMissingDeps);
+            if (!ok) {
+                reportError("err.cant.list.module.deps");
+                log.println();
+                analyzer.visitMissingDeps(new SimpleDepVisitor());
+            }
+            return ok;
         }
     }
 
-
     class GenDotFile extends AnalyzeDeps {
         final Path dotOutputDir;
         GenDotFile(Path dotOutputDir) {
@@ -1053,6 +1077,18 @@
         }
     }
 
+    class SimpleDepVisitor implements Analyzer.Visitor {
+        private Archive source;
+        @Override
+        public void visitDependence(String origin, Archive originArchive, String target, Archive targetArchive) {
+            if (source != originArchive) {
+                source = originArchive;
+                log.format("%s%n", originArchive);
+            }
+            log.format("   %-50s -> %-50s %s%n", origin, target, targetArchive.getName());
+        }
+    }
+
     /**
      * Returns a filter used during dependency analysis
      */
@@ -1066,6 +1102,7 @@
         // target filters
         builder.filter(options.filterSamePackage, options.filterSameArchive);
         builder.findJDKInternals(options.findJDKInternals);
+        builder.findMissingDeps(options.findMissingDeps);
 
         // --require
         if (!options.requires.isEmpty()) {
@@ -1158,11 +1195,8 @@
     private String version(String key) {
         // key=version:  mm.nn.oo[-milestone]
         // key=full:     mm.mm.oo[-milestone]-build
-        if (ResourceBundleHelper.versionRB == null) {
-            return System.getProperty("java.version");
-        }
         try {
-            return ResourceBundleHelper.versionRB.getString(key);
+            return ResourceBundleHelper.getVersion(key);
         } catch (MissingResourceException e) {
             return getMessage("version.unknown", System.getProperty("java.version"));
         }
@@ -1170,13 +1204,15 @@
 
     static String getMessage(String key, Object... args) {
         try {
-            return MessageFormat.format(ResourceBundleHelper.bundle.getString(key), args);
+            return MessageFormat.format(ResourceBundleHelper.getMessage(key), args);
         } catch (MissingResourceException e) {
             throw new InternalError("Missing message: " + key);
         }
     }
 
     private static class Options {
+        static final int NO_RECURSIVE = 0;
+        static final int RECURSIVE = 1;
         boolean help;
         boolean version;
         boolean fullVersion;
@@ -1186,6 +1222,8 @@
         boolean apiOnly;
         boolean showLabel;
         boolean findJDKInternals;
+        boolean findMissingDeps;
+        boolean ignoreMissingDeps;
         boolean nowarning = false;
         Analyzer.Type verbose;
         // default filter references from same package
@@ -1193,7 +1231,8 @@
         boolean filterSameArchive = false;
         Pattern filterRegex;
         String classpath;
-        int depth = 1;
+        int recursive = -1;     // 0: --no-recursive, 1: --recursive
+        int depth = -1;
         Set<String> requires = new HashSet<>();
         Set<String> packageNames = new HashSet<>();
         Pattern regex;             // apply to the dependences
@@ -1222,9 +1261,23 @@
             if (packageNames.size() > 0) count++;
             return count;
         }
+
+        int depth() {
+            // ignore -depth if --no-recursive is set
+            if (recursive == NO_RECURSIVE)
+                return 1;
+
+            // depth == 0 if recursive
+            if (recursive == RECURSIVE && depth == -1)
+                return 0;
+
+            // default depth is 1 unless specified via -depth option
+            return depth == -1 ? 1 : depth;
+        }
     }
 
     private static class ResourceBundleHelper {
+        static final String LS = System.lineSeparator();
         static final ResourceBundle versionRB;
         static final ResourceBundle bundle;
         static final ResourceBundle jdkinternals;
@@ -1247,6 +1300,21 @@
                 throw new InternalError("Cannot find jdkinternals resource bundle");
             }
         }
+
+        static String getMessage(String key) {
+            return bundle.getString(key).replace("\n", LS);
+        }
+
+        static String getVersion(String key) {
+            if (ResourceBundleHelper.versionRB == null) {
+                return System.getProperty("java.version");
+            }
+            return versionRB.getString(key).replace("\n", LS);
+        }
+
+        static String getSuggestedReplacement(String key) {
+            return ResourceBundleHelper.jdkinternals.getString(key).replace("\n", LS);
+        }
     }
 
     /**
@@ -1258,7 +1326,7 @@
         String value = null;
         while (value == null && name != null) {
             try {
-                value = ResourceBundleHelper.jdkinternals.getString(name);
+                value = ResourceBundleHelper.getSuggestedReplacement(name);
             } catch (MissingResourceException e) {
                 // go up one subpackage level
                 int i = name.lastIndexOf('.');
--- a/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/Module.java	Wed Nov 21 22:33:33 2018 -0800
+++ b/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/Module.java	Wed Nov 21 22:34:01 2018 -0800
@@ -58,12 +58,16 @@
     private final URI location;
 
     protected Module(String name) {
+        this(name, null, false);
+    }
+
+    protected Module(String name, ModuleDescriptor descriptor, boolean isSystem) {
         super(name);
-        this.descriptor = null;
+        this.descriptor = descriptor;
         this.location = null;
         this.exports = Collections.emptyMap();
         this.opens = Collections.emptyMap();
-        this.isSystem = true;
+        this.isSystem = isSystem;
     }
 
     private Module(String name,
@@ -89,11 +93,11 @@
     }
 
     public boolean isNamed() {
-        return true;
+        return descriptor != null;
     }
 
     public boolean isAutomatic() {
-        return descriptor.isAutomatic();
+        return descriptor != null && descriptor.isAutomatic();
     }
 
     public Module getModule() {
@@ -232,9 +236,7 @@
 
     private static class UnnamedModule extends Module {
         private UnnamedModule() {
-            super("unnamed", null, null,
-                  Collections.emptyMap(), Collections.emptyMap(),
-                  false, null);
+            super("unnamed", null, false);
         }
 
         @Override
@@ -243,16 +245,6 @@
         }
 
         @Override
-        public boolean isNamed() {
-            return false;
-        }
-
-        @Override
-        public boolean isAutomatic() {
-            return false;
-        }
-
-        @Override
         public boolean isExported(String pn) {
             return true;
         }
--- a/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/ModuleExportsAnalyzer.java	Wed Nov 21 22:33:33 2018 -0800
+++ b/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/ModuleExportsAnalyzer.java	Wed Nov 21 22:34:01 2018 -0800
@@ -49,42 +49,47 @@
 public class ModuleExportsAnalyzer extends DepsAnalyzer {
     // source archive to its dependences and JDK internal APIs it references
     private final Map<Archive, Map<Archive,Set<String>>> deps = new HashMap<>();
-    private final boolean showJdkInternals;
+    private final Map<String, Set<String>> missingDeps = new HashMap<>();
+    private final boolean showInternals;
     private final boolean reduced;
     private final PrintWriter writer;
     private final String separator;
     public ModuleExportsAnalyzer(JdepsConfiguration config,
                                  JdepsFilter filter,
-                                 boolean showJdkInternals,
+                                 boolean showInternals,
                                  boolean reduced,
                                  PrintWriter writer,
                                  String separator) {
         super(config, filter, null,
               Analyzer.Type.PACKAGE,
               false /* all classes */);
-        this.showJdkInternals = showJdkInternals;
+        this.showInternals = showInternals;
         this.reduced = reduced;
         this.writer = writer;
         this.separator = separator;
     }
 
-    @Override
-    public boolean run() throws IOException {
-        // analyze dependences
-        boolean rc = super.run();
+    public boolean run(int maxDepth, boolean ignoreMissingDeps) throws IOException {
+        // use compile time view so that the entire archive on classpath is analyzed
+        boolean rc = super.run(true, maxDepth);
 
         // A visitor to record the module-level dependences as well as
-        // use of JDK internal APIs
+        // use of internal APIs
         Analyzer.Visitor visitor = (origin, originArchive, target, targetArchive) -> {
-            Set<String> jdkInternals =
+            Set<String> internals =
                 deps.computeIfAbsent(originArchive, _k -> new HashMap<>())
                     .computeIfAbsent(targetArchive, _k -> new HashSet<>());
 
             Module module = targetArchive.getModule();
-            if (showJdkInternals && originArchive.getModule() != module &&
-                    module.isJDK() && !module.isExported(target)) {
-                // use of JDK internal APIs
-                jdkInternals.add(target);
+            if (showInternals && originArchive.getModule() != module &&
+                    module.isNamed() && !module.isExported(target, module.name())) {
+                // use of internal APIs
+                internals.add(target);
+            }
+            if (!ignoreMissingDeps && Analyzer.notFound(targetArchive)) {
+                Set<String> notFound =
+                    missingDeps.computeIfAbsent(origin, _k -> new HashSet<>());
+                notFound.add(target);
             }
         };
 
@@ -94,10 +99,26 @@
             .sorted(Comparator.comparing(Archive::getName))
             .forEach(archive -> analyzer.visitDependences(archive, visitor));
 
+        // error if any missing dependence
+        if (!rc || !missingDeps.isEmpty()) {
+            return false;
+        }
+
+        Map<Module, Set<String>> internalPkgs = internalPackages();
         Set<Module> modules = modules();
-        if (showJdkInternals) {
+        if (showInternals) {
             // print modules and JDK internal API dependences
-            printDependences(modules);
+            Stream.concat(modules.stream(), internalPkgs.keySet().stream())
+                    .sorted(Comparator.comparing(Module::name))
+                    .distinct()
+                    .forEach(m -> {
+                        if (internalPkgs.containsKey(m)) {
+                            internalPkgs.get(m).stream()
+                                .forEach(pn -> writer.format("   %s/%s%s", m, pn, separator));
+                        } else {
+                            writer.format("   %s%s", m, separator);
+                        }
+                    });
         } else {
             // print module dependences
             writer.println(modules.stream().map(Module::name).sorted()
@@ -106,16 +127,28 @@
         return rc;
     }
 
+    /*
+     * Prints missing dependences
+     */
+    void visitMissingDeps(Analyzer.Visitor visitor) {
+        archives.stream()
+            .filter(analyzer::hasDependences)
+            .sorted(Comparator.comparing(Archive::getName))
+            .filter(m -> analyzer.requires(m).anyMatch(Analyzer::notFound))
+            .forEach(m -> {
+                analyzer.visitDependences(m, visitor, Analyzer.Type.VERBOSE, Analyzer::notFound);
+            });
+    }
+
     private Set<Module> modules() {
         // build module graph
         ModuleGraphBuilder builder = new ModuleGraphBuilder(configuration);
-        Module root = new RootModule("root");
+        Module root = new RootModule();
         builder.addModule(root);
         // find named module dependences
         dependenceStream()
             .flatMap(map -> map.keySet().stream())
-            .filter(m -> m.getModule().isNamed()
-                && !configuration.rootModules().contains(m))
+            .filter(m -> m.getModule().isNamed() && !configuration.rootModules().contains(m))
             .map(Archive::getModule)
             .forEach(m -> builder.addEdge(root, m));
 
@@ -125,28 +158,15 @@
         return g.adjacentNodes(root);
     }
 
-    private void printDependences(Set<Module> modules) {
-        // find use of JDK internals
-        Map<Module, Set<String>> jdkinternals = new HashMap<>();
+    private Map<Module, Set<String>> internalPackages() {
+        Map<Module, Set<String>> internalPkgs = new HashMap<>();
         dependenceStream()
             .flatMap(map -> map.entrySet().stream())
             .filter(e -> e.getValue().size() > 0)
-            .forEach(e -> jdkinternals.computeIfAbsent(e.getKey().getModule(),
-                                                       _k -> new TreeSet<>())
+            .forEach(e -> internalPkgs.computeIfAbsent(e.getKey().getModule(),
+                                                             _k -> new TreeSet<>())
                                       .addAll(e.getValue()));
-
-        // print modules and JDK internal API dependences
-        Stream.concat(modules.stream(), jdkinternals.keySet().stream())
-              .sorted(Comparator.comparing(Module::name))
-              .distinct()
-              .forEach(m -> {
-                  if (jdkinternals.containsKey(m)) {
-                      jdkinternals.get(m).stream()
-                          .forEach(pn -> writer.format("   %s/%s%s", m, pn, separator));
-                  } else {
-                      writer.format("   %s%s", m, separator);
-                  }
-              });
+        return internalPkgs;
     }
 
     /*
@@ -160,18 +180,13 @@
                    .map(deps::get);
     }
 
-    private class RootModule extends Module {
-        final ModuleDescriptor descriptor;
-        RootModule(String name) {
-            super(name);
-
-            ModuleDescriptor.Builder builder = ModuleDescriptor.newModule(name);
-            this.descriptor = builder.build();
-        }
-
-        @Override
-        public ModuleDescriptor descriptor() {
-            return descriptor;
+    /*
+     * RootModule serves as the root node for building the module graph
+     */
+    private static class RootModule extends Module {
+        static final String NAME = "root";
+        RootModule() {
+            super(NAME, ModuleDescriptor.newModule(NAME).build(), false);
         }
     }
 
--- a/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/ModuleInfoBuilder.java	Wed Nov 21 22:33:33 2018 -0800
+++ b/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/ModuleInfoBuilder.java	Wed Nov 21 22:34:01 2018 -0800
@@ -137,17 +137,13 @@
         }
     }
 
-    boolean notFound(Archive m) {
-        return m == NOT_FOUND || m == REMOVED_JDK_INTERNALS;
-    }
-
     private Module toNormalModule(Module module, Set<Archive> requiresTransitive)
         throws IOException
     {
         // done analysis
         module.close();
 
-        if (analyzer.requires(module).anyMatch(this::notFound)) {
+        if (analyzer.requires(module).anyMatch(Analyzer::notFound)) {
             // missing dependencies
             return null;
         }
@@ -182,9 +178,9 @@
 
     void visitMissingDeps(Analyzer.Visitor visitor) {
         automaticModules().stream()
-            .filter(m -> analyzer.requires(m).anyMatch(this::notFound))
+            .filter(m -> analyzer.requires(m).anyMatch(Analyzer::notFound))
             .forEach(m -> {
-                analyzer.visitDependences(m, visitor, Analyzer.Type.VERBOSE);
+                analyzer.visitDependences(m, visitor, Analyzer.Type.VERBOSE, Analyzer::notFound);
             });
     }
 
--- a/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/resources/jdeps.properties	Wed Nov 21 22:33:33 2018 -0800
+++ b/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/resources/jdeps.properties	Wed Nov 21 22:34:01 2018 -0800
@@ -57,6 +57,13 @@
 \                                name (may be given multiple times). --package,\n\
 \                                --regex, --require are mutual exclusive.
 
+main.opt.missing-deps=\
+\  --missing-deps                Finds missing dependences.  This option\n\
+\                                cannot be used with -p, -e and -s options.
+
+main.opt.ignore-missing-deps=\
+\  --ignore-missing-deps         Ignore missing dependences.
+
 main.opt.include=\n\
  \Options to filter classes to be analyzed:\n\
 \  -include <regex>              Restrict analysis to classes matching pattern\n\
@@ -86,13 +93,18 @@
 \                                Adds modules to the root set for analysis
 
 main.opt.R=\
-\  -R       -recursive           Recursively traverse all run-time dependences.\n\
+\  -R\n\
+\  --recursive                   Recursively traverse all run-time dependences.\n\
 \                                The -R option implies -filter:none.  If -p,\n\
 \                                -e, -f option is specified, only the matching\n\
 \                                dependences are analyzed.
 
+main.opt.no-recursive=\
+\  --no-recursive                Do not recursively traverse dependences.
+
 main.opt.I=\
-\  -I       --inverse            Analyzes the dependences per other given options\n\
+\  -I\n\
+\  --inverse                     Analyzes the dependences per other given options\n\
 \                                and then find all artifacts that directly\n\
 \                                and indirectly depend on the matching nodes.\n\
 \                                This is equivalent to the inverse of\n\
@@ -157,9 +169,11 @@
 
 main.opt.list-deps=\
 \  --list-deps                   Lists the module dependences.  It also prints\n\
-\                                any JDK internal API packages if referenced.\n\
-\                                This option does not show dependences on the\n\
-\                                class path or not found.
+\                                any internal API packages if referenced.\n\
+\                                This option transitively analyzes libraries on\n\
+\                                class path and module path if referenced.\n\
+\                                Use --no-recursive option for non-transitive\n\
+\                                dependency analysis.
 
 main.opt.list-reduced-deps=\
 \  --list-reduced-deps           Same as --list-deps with not listing\n\
@@ -207,6 +221,10 @@
 err.multirelease.option.notfound={0} is a multi-release jar file but --multi-release option is not set
 err.multirelease.version.associated=class {0} already associated with version {1}, trying to add version {2}
 err.multirelease.jar.malformed=malformed multi-release jar, {0}, bad entry: {1}
+err.cant.list.module.deps=\
+Missing dependencies from the module path and classpath.\n\
+To suppress this error, use --ignore-missing-deps to continue.
+
 warn.invalid.arg=Path does not exist: {0}
 warn.skipped.entry={0}
 warn.split.package=split package: {0} {1}
--- a/test/langtools/tools/jdeps/Basic.java	Wed Nov 21 22:33:33 2018 -0800
+++ b/test/langtools/tools/jdeps/Basic.java	Wed Nov 21 22:34:01 2018 -0800
@@ -37,6 +37,7 @@
 import java.io.StringWriter;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.*;
 import java.util.regex.*;
 import java.util.stream.Collectors;
@@ -135,6 +136,19 @@
              new String[] {"java.lang.Object", "java.lang.String", "p.Foo", "p.Bar"},
              new String[] {"compact1", "compact1", dir1.toFile().getName(), dir2.toFile().getName()},
              new String[] {"-v", "-classpath", cpath.toString(), "Test.class"});
+
+        // tests --missing-deps option
+        test(new File(testDir, "Test.class"),
+             new String[] {"p.Foo", "p.Bar"},
+             new String[] {"not found", "not found"},
+             new String[] {"--missing-deps"});
+
+        // no missing dependence
+        test(new File(testDir, "Test.class"),
+             new String[0],
+             new String[0],
+             new String[] {"--missing-deps", "-classpath", cpath.toString()});
+
         return errors;
     }
 
--- a/test/langtools/tools/jdeps/MultiReleaseJar.java	Wed Nov 21 22:33:33 2018 -0800
+++ b/test/langtools/tools/jdeps/MultiReleaseJar.java	Wed Nov 21 22:34:01 2018 -0800
@@ -27,7 +27,7 @@
  * @summary Tests for jdeps tool with multi-release jar files
  * @modules jdk.jdeps/com.sun.tools.jdeps
  * @library mrjar mrjar/base mrjar/9 mrjar/10 mrjar/v9 mrjar/v10
- * @build test.* p.* q.* foo/*
+ * @build test.* p.* q.* foo/* Main
  * @run testng MultiReleaseJar
  */
 
@@ -59,10 +59,7 @@
         testJdk = System.getProperty("test.jdk");
         fileSep = System.getProperty("file.separator");
         cmdPath = Paths.get(testJdk, "bin");
-    }
 
-    @Test
-    public void basic() throws Exception {
         // build Version.jar, Version_9.jar and main.jar
         Result r = run("jar -cf Version.jar -C base test --release 9 -C 9 test --release 10 -C 10 test");
         checkResult(r);
@@ -70,11 +67,20 @@
         r = run("jar -cf Version_9.jar -C base test --release 9 -C 9 test");
         checkResult(r);
 
-        r = run("jar -cf Main.jar test/Main.class");
+        r = run("jar -cf Main.jar Main.class");
+        checkResult(r);
+
+        r = run("jar -cf Foo.jar -C base p");
         checkResult(r);
 
-        // try out a bunch of things
-        r = run("jdeps --multi-release 9  -v missing.jar");
+        Path foo = Paths.get(System.getProperty("test.classes")).resolve("modules").resolve("foo");
+        r = run("jar -uf Foo.jar --release 9 -C " + foo.toString() + " module-info.class --release 10 -C v10 q");
+        checkResult(r);
+    }
+
+    @Test
+    public void basic() throws Exception {
+        Result r = run("jdeps --multi-release 9  -v missing.jar");
         checkResult(r, false, "Warning: Path does not exist: missing.jar");
 
         r = run("jdeps -v Version.jar");
@@ -115,7 +121,7 @@
         r = run("jdeps --multi-release 9.1  -v Version.jar");
         checkResult(r, false, "Error: invalid argument for option: 9.1");
 
-        runJdeps("test/Main.class");
+        runJdeps("Main.class");
         runJdeps("Main.jar");
     }
 
@@ -124,14 +130,14 @@
         Result r = run("jdeps -v -R -cp Version.jar " + path);
         checkResult(r, false, "--multi-release option is not set");
 
-        r = run("jdeps -v -R -cp Version.jar -multi-release 9 " + path);
+        r = run("jdeps -v -R -cp Version.jar --module-path Foo.jar -multi-release 9 " + path);
         checkResult(r, false,
             "Error: unknown option: -multi-release",
             "Usage: jdeps <options> <path",
             "use --help"
         );
 
-        r = run("jdeps -v -R -cp Version.jar --multi-release 9 " + path);
+        r = run("jdeps -v -R -cp Version.jar --module-path Foo.jar --multi-release 9 " + path);
 
         String name = path;
         int index = path.lastIndexOf('/');
@@ -139,66 +145,94 @@
             name = path.substring(index + 1, path.length());
         }
         checkResult(r, true,
-            name + " ->",
-            name + " ->",
-            "test.Main",
-            "test.Main",
-            "test.Main",
-            "Version.jar ->",
+            name + " -> Version.jar",
+            name + " -> foo",
+            name + " -> java.base",
+            "Main",
+            "Main",
+            "Main",
+            "Main",
+            "Version.jar -> java.base",
             "9/test.NonPublic",
             "9/test.NonPublic",
             "9/test.Version",
             "9/test.Version",
             "9/test.Version",
-            "9/test.Version"
+            "9/test.Version",
+            "foo",
+            "Foo.jar",
+            "requires mandated java.base",
+            "foo -> java.base",
+            "p.Foo"
         );
 
-        r = run("jdeps -v -R -cp Version.jar --multi-release 10 " + path);
+        r = run("jdeps -v -R -cp Version.jar --module-path Foo.jar --multi-release 10 " + path);
         checkResult(r, true,
-            name + " ->",
-            name + " ->",
-            "test.Main",
-            "test.Main",
-            "test.Main",
+            name + " -> Version.jar",
+            name + " -> foo",
+            name + " -> java.base",
+            "Main",
+            "Main",
+            "Main",
+            "Main",
             "Version.jar ->",
             "10/test.Version",
             "10/test.Version",
             "10/test.Version",
             "10/test.Version",
             "9/test.NonPublic",
-            "9/test.NonPublic"
+            "9/test.NonPublic",
+            "foo",
+            "Foo.jar",
+            "requires mandated java.base",
+            "foo -> java.base",
+            "p.Foo"
         );
 
-        r = run("jdeps -v -R -cp Version.jar --multi-release base " + path);
+        r = run("jdeps -v -R -cp Version.jar --module-path Foo.jar --multi-release base " + path);
         checkResult(r, true,
-            name + " ->",
-            name + " ->",
-            "test.Main",
-            "test.Main",
-            "test.Main",
+            name + " -> Version.jar",
+            name + " -> foo",
+            name + " -> java.base",
+            "Main",
+            "Main",
+            "Main",
+            "Main",
             "Version.jar ->",
             "test.Version",
-            "test.Version"
+            "test.Version",
+            "foo",
+            "Foo.jar",
+            "requires mandated java.base",
+            "foo -> java.base",
+            "p.Foo"
         );
 
-        r = run("jdeps -v -R -cp Version.jar --multi-release 9.1 " + path);
+        r = run("jdeps -v -R -cp Version.jar --module-path  Foo.jar --multi-release 9.1 " + path);
         checkResult(r, false, "Error: invalid argument for option: 9.1");
 
         // Version_9.jar does not have any version 10 entry
-        r = run("jdeps -v -R -cp Version_9.jar --multi-release 10 " + path);
+        r = run("jdeps -v -R -cp Version_9.jar --module-path Foo.jar --multi-release 10 " + path);
         checkResult(r, true,
-            name + " ->",
-            name + " ->",
-            "test.Main",
-            "test.Main",
-            "test.Main",
+            name + " -> Version_9.jar",
+            name + " -> foo",
+            name + " -> java.base",
+            "Main",
+            "Main",
+            "Main",
+            "Main",
             "Version_9.jar ->",
             "9/test.NonPublic",
             "9/test.NonPublic",
             "9/test.Version",
             "9/test.Version",
             "9/test.Version",
-            "9/test.Version"
+            "9/test.Version",
+            "foo",
+            "Foo.jar",
+            "requires mandated java.base",
+            "foo -> java.base",
+            "p.Foo"
         );
     }
 
@@ -236,17 +270,10 @@
 
     @Test
     public void modularJar() throws Exception {
-        Result r = run("jar -cf foo.jar -C base p");
-        checkResult(r);
-
-        Path foo = Paths.get(System.getProperty("test.classes")).resolve("modules").resolve("foo");
-        r = run("jar -uf foo.jar --release 9 -C " + foo.toString() + " module-info.class --release 10 -C v10 q");
-        checkResult(r);
-
-        r = run("jdeps -v --multi-release 10 --module-path foo.jar -m foo");
+        Result r = run("jdeps -v --multi-release 10 --module-path Foo.jar -m foo");
         checkResult(r, true,
             "foo",                   // module name
-            "foo.jar",                      // the path to foo.jar
+            "Foo.jar",                      // the path to Foo.jar
             "requires mandated java.base",  // module dependences
             "foo -> java.base",
             "10/q.Bar",
@@ -255,27 +282,24 @@
             "p.Foo"
         );
 
-        r = run("jdeps --multi-release 9 --module-path foo.jar Main.jar");
+        r = run("jdeps --multi-release 9 -cp Version.jar --module-path Foo.jar Main.jar");
         checkResult(r, true,
-            "Main.jar ->",
-            "test",
-            "foo",                          // module name
-            "foo.jar",                      // the path to foo.jar
-            "requires mandated java.base",  // module dependences
-            "foo -> java.base",
-            "p"
+            "Main.jar -> Version.jar",
+            "Main.jar -> foo",
+            "Main.jar -> java.base",
+            "-> java.lang",
+            "-> p",
+            "-> test"
         );
 
-        r = run("jdeps --multi-release 10 --module-path foo.jar Main.jar");
+        r = run("jdeps --multi-release 10 -cp Version.jar --module-path Foo.jar Main.jar");
         checkResult(r, true,
-            "Main.jar ->",
-            "test",
-            "foo",                          // module name
-            "foo.jar",                      // the path to foo.jar
-            "requires mandated java.base",  // module dependences
-            "foo -> java.base",
-            "p",
-            "q"
+            "Main.jar -> Version.jar",
+            "Main.jar -> foo",
+            "Main.jar -> java.base",
+            "-> java.lang",
+            "-> p",
+            "-> test"
         );
     }
 
--- a/test/langtools/tools/jdeps/Options.java	Wed Nov 21 22:33:33 2018 -0800
+++ b/test/langtools/tools/jdeps/Options.java	Wed Nov 21 22:34:01 2018 -0800
@@ -53,7 +53,15 @@
             },
             {
                 new String[] { "-jdkinternal", "-p", "java.lang", TEST_CLASSES },
-                "--package, --regex, --require cannot be used with  -jdkinternals option"
+                "--package, --regex, --require cannot be used with -jdkinternals option"
+            },
+            {
+                new String[] { "--missing-deps", "-summary", TEST_CLASSES },
+                "-summary or -verbose cannot be used with --missing-deps option"
+            },
+            {
+                new String[] { "--missing-deps", "-p", "java.lang", TEST_CLASSES },
+                "--package, --regex, --require cannot be used with --missing-deps option"
             },
             {
                 new String[] { "--inverse", TEST_CLASSES },
--- a/test/langtools/tools/jdeps/listdeps/ListModuleDeps.java	Wed Nov 21 22:33:33 2018 -0800
+++ b/test/langtools/tools/jdeps/listdeps/ListModuleDeps.java	Wed Nov 21 22:34:01 2018 -0800
@@ -35,9 +35,11 @@
  * @run testng ListModuleDeps
  */
 
+import java.io.File;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.Arrays;
+import java.util.List;
 import java.util.stream.Collectors;
 
 import org.testng.annotations.BeforeTest;
@@ -53,6 +55,7 @@
     private static final Path SRC_DIR = Paths.get(TEST_SRC, "src");
     private static final Path CLASSES_DIR = Paths.get("classes");
     private static final Path LIB_DIR = Paths.get("lib");
+    private static final Path LIB2_DIR = Paths.get("lib2");
 
     private static final Path HI_CLASS =
         CLASSES_DIR.resolve("hi").resolve("Hi.class");
@@ -69,7 +72,8 @@
     @BeforeTest
     public void compileAll() throws Exception {
         // compile library
-        assertTrue(CompilerUtils.compile(Paths.get(TEST_SRC, "src", "lib"), LIB_DIR));
+        assertTrue(CompilerUtils.compile(Paths.get(TEST_SRC, "src", "lib2"), LIB2_DIR));
+        assertTrue(CompilerUtils.compile(Paths.get(TEST_SRC, "src", "lib"), LIB_DIR, "-cp", LIB2_DIR.toString()));
 
         // simple program depends only on java.base
         assertTrue(CompilerUtils.compile(Paths.get(TEST_SRC, "src", "hi"), CLASSES_DIR));
@@ -111,7 +115,7 @@
     @Test(dataProvider = "listdeps")
     public void testListDeps(Path classes, String[] expected) {
         JdepsRunner jdeps = JdepsRunner.run(
-            "--class-path", LIB_DIR.toString(),
+            "--class-path", LIB_DIR.toString() + File.pathSeparator + LIB2_DIR.toString(),
             "--list-deps", classes.toString()
         );
         String[] output = Arrays.stream(jdeps.output())
@@ -123,7 +127,7 @@
     @Test(dataProvider = "reduceddeps")
     public void testListReducedDeps(Path classes, String[]  expected) {
         JdepsRunner jdeps = JdepsRunner.run(
-            "--class-path", LIB_DIR.toString(),
+            "--class-path", LIB_DIR.toString() + File.pathSeparator + LIB2_DIR.toString(),
             "--list-reduced-deps", classes.toString()
         );
         String[] output = Arrays.stream(jdeps.output())
@@ -140,6 +144,7 @@
                                 "java.base/jdk.internal.misc",
                                 "java.base/sun.security.util",
                                 "java.logging",
+                                "java.management",
                                 "java.sql",
                                 "java.xml/jdk.xml.internal",
                                 "jdk.unsupported"
@@ -154,6 +159,7 @@
             { FOO_CLASS,    new String[] {
                                 "java.base",
                                 "java.logging",
+                                "java.management",
                                 "java.sql",
                                 "java.xml"
                             }
@@ -181,6 +187,7 @@
             { CLASSES_DIR,  new String[] {
                                 "java.base/jdk.internal.misc",
                                 "java.base/sun.security.util",
+                                "java.management",
                                 "java.sql",
                                 "java.xml/jdk.xml.internal",
                                 "jdk.unsupported"
@@ -194,6 +201,7 @@
 
             { FOO_CLASS,    new String[] {
                                 "java.base",
+                                "java.management",
                                 "java.sql"
                             }
             },
@@ -215,7 +223,7 @@
     @Test(dataProvider = "moduledeps")
     public void testPrintModuleDeps(Path classes, String expected) {
         JdepsRunner jdeps = JdepsRunner.run(
-            "--class-path", LIB_DIR.toString(),
+            "--class-path", LIB_DIR.toString() + File.pathSeparator + LIB2_DIR.toString(),
             "--print-module-deps", classes.toString()
         );
         String output = Arrays.stream(jdeps.output())
@@ -231,6 +239,32 @@
 
         return new Object[][] {
             // java.xml is an implied reads edge from java.sql
+            { CLASSES_DIR,  "java.base,java.management,java.sql,jdk.unsupported"},
+            { HI_CLASS,     "java.base"},
+            { FOO_CLASS,    "java.base,java.management,java.sql"},
+            { BAR_CLASS,    "java.base,java.xml"},
+            { UNSAFE_CLASS, "java.base,jdk.unsupported"},
+        };
+    }
+
+    @Test(dataProvider = "noRecursiveModuledeps")
+    public void testNoRecursiveModuleDeps(Path classes, String expected) {
+        JdepsRunner jdeps = JdepsRunner.run(
+            "--class-path", LIB_DIR.toString() + File.pathSeparator + LIB2_DIR.toString(),
+            "--print-module-deps", "--no-recursive", classes.toString()
+        );
+        String output = Arrays.stream(jdeps.output())
+            .map(s -> s.trim())
+            .collect(Collectors.joining(","));
+        assertEquals(output, expected);
+    }
+
+    @DataProvider(name = "noRecursiveModuledeps")
+    public Object[][] noRecursiveModuledeps() {
+        Path barClass = CLASSES_DIR.resolve("z").resolve("Bar.class");
+
+        return new Object[][] {
+            // java.xml is an implied reads edge from java.sql
             { CLASSES_DIR,  "java.base,java.sql,jdk.unsupported"},
             { HI_CLASS,     "java.base"},
             { FOO_CLASS,    "java.base,java.sql"},
@@ -238,4 +272,47 @@
             { UNSAFE_CLASS, "java.base,jdk.unsupported"},
         };
     }
+
+    @DataProvider(name = "recursiveDeps")
+    public Object[][] recursiveDeps() {
+        return new Object[][] {
+            {   // lib2 is classpath but not analyzed because lib.Lib is not present
+                // but it is the only class depending on lib2.Lib2
+                List.of("--list-deps", "--class-path", LIB2_DIR.toString(),
+                        "--ignore-missing-deps", CLASSES_DIR.toString()),
+                new String[] {
+                    "java.base/jdk.internal.misc",
+                    "java.base/sun.security.util",
+                    "java.logging",
+                    "java.sql",
+                    "java.xml/jdk.xml.internal",
+                    "jdk.unsupported"
+                }
+            },
+            {   // lib2 is classpath but not analyzed because lib.Lib is not present
+                // but it is the only class depending on lib2.Lib2
+                List.of("--print-module-deps", "--class-path", LIB2_DIR.toString(),
+                        "--ignore-missing-deps", CLASSES_DIR.toString()),
+                new String[] {
+                    "java.base,java.sql,jdk.unsupported"
+                }
+            },
+            {   // Foo depends on lib.Lib which depends on lib2.Libs
+                List.of("--print-module-deps",
+                        "--ignore-missing-deps", FOO_CLASS.toString()),
+                new String[] {
+                    "java.base,java.sql"
+                }
+            },
+        };
+    }
+
+    @Test(dataProvider = "recursiveDeps")
+    public void testRecursiveDeps(List<String> options, String[] expected) {
+        JdepsRunner jdeps = JdepsRunner.run(options.toArray(new String[0]));
+        String[] output = Arrays.stream(jdeps.output())
+                                .map(s -> s.trim())
+                                .toArray(String[]::new);
+        assertEquals(output, expected);
+    }
 }
--- a/test/langtools/tools/jdeps/listdeps/src/lib/Lib.java	Wed Nov 21 22:33:33 2018 -0800
+++ b/test/langtools/tools/jdeps/listdeps/src/lib/Lib.java	Wed Nov 21 22:34:01 2018 -0800
@@ -28,4 +28,5 @@
 public class Lib {
     public static final String isCoalescing = XMLInputFactory.IS_COALESCING;
     public static boolean check() { return true; }
+    public static long getPid() { return lib2.Lib2.getPid(); }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/langtools/tools/jdeps/mrjar/Main.java	Wed Nov 21 22:34:01 2018 -0800
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import test.Version;
+import p.Foo;
+
+public class Main {
+    public void run() {
+        Version v = new Version();
+        v.getVersion();
+    }
+
+    public static void main(String[] args) {
+        (new Main()).run();
+        Foo foo = new Foo();
+    }
+}
--- a/test/langtools/tools/jdeps/mrjar/test/Main.java	Wed Nov 21 22:33:33 2018 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,35 +0,0 @@
-/*
- * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package test;
-
-public class Main {
-    public void run() {
-        Version v = new Version();
-        v.getVersion();
-    }
-
-    public static void main(String[] args) {
-        (new Main()).run();
-    }
-}