8225773: jdeps --check produces NPE if there are missing module dependences

Reviewed-by: alanb
diff --git a/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/JdepsTask.java b/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/JdepsTask.java
index 2127df6..307a488 100644
--- a/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/JdepsTask.java
+++ b/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/JdepsTask.java
@@ -986,7 +986,7 @@
                 throw new UncheckedBadArgs(new BadArgs("err.invalid.options",
                                                        list, "--check"));
             }
-            return new ModuleAnalyzer(config, log, modules).run();
+            return new ModuleAnalyzer(config, log, modules).run(options.ignoreMissingDeps);
         }
 
         /*
diff --git a/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/ModuleAnalyzer.java b/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/ModuleAnalyzer.java
index 233f62b..3bab7be 100644
--- a/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/ModuleAnalyzer.java
+++ b/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/ModuleAnalyzer.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 2020, 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
@@ -24,22 +24,16 @@
  */
 package com.sun.tools.jdeps;
 
-import static com.sun.tools.jdeps.Graph.*;
 import static com.sun.tools.jdeps.JdepsFilter.DEFAULT_FILTER;
 import static com.sun.tools.jdeps.Module.*;
 import static java.lang.module.ModuleDescriptor.Requires.Modifier.*;
 import static java.util.stream.Collectors.*;
 
 import com.sun.tools.classfile.Dependency;
-import com.sun.tools.jdeps.JdepsTask.BadArgs;
 
 import java.io.IOException;
-import java.io.OutputStream;
 import java.io.PrintWriter;
 import java.lang.module.ModuleDescriptor;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -80,23 +74,32 @@
         }
     }
 
-    public boolean run() throws IOException {
+    public boolean run(boolean ignoreMissingDeps) throws IOException {
         try {
-            // compute "requires transitive" dependences
-            modules.values().forEach(ModuleDeps::computeRequiresTransitive);
-
-            modules.values().forEach(md -> {
+            for (ModuleDeps md: modules.values()) {
+                // compute "requires transitive" dependences
+                md.computeRequiresTransitive(ignoreMissingDeps);
                 // compute "requires" dependences
-                md.computeRequires();
+                md.computeRequires(ignoreMissingDeps);
+                // print module descriptor
+                md.printModuleDescriptor();
+
                 // apply transitive reduction and reports recommended requires.
-                md.analyzeDeps();
-            });
+                boolean ok = md.analyzeDeps();
+                if (!ok) return false;
+
+                if (ignoreMissingDeps && md.hasMissingDependencies()) {
+                    log.format("Warning: --ignore-missing-deps specified. Missing dependencies from %s are ignored%n",
+                               md.root.name());
+                }
+            }
         } finally {
             dependencyFinder.shutdown();
         }
         return true;
     }
 
+
     class ModuleDeps {
         final Module root;
         Set<Module> requiresTransitive;
@@ -110,23 +113,22 @@
         /**
          * Compute 'requires transitive' dependences by analyzing API dependencies
          */
-        private void computeRequiresTransitive() {
+        private void computeRequiresTransitive(boolean ignoreMissingDeps) {
             // record requires transitive
-            this.requiresTransitive = computeRequires(true)
+            this.requiresTransitive = computeRequires(true, ignoreMissingDeps)
                 .filter(m -> !m.name().equals(JAVA_BASE))
                 .collect(toSet());
 
             trace("requires transitive: %s%n", requiresTransitive);
         }
 
-        private void computeRequires() {
-            this.requires = computeRequires(false).collect(toSet());
+        private void computeRequires(boolean ignoreMissingDeps) {
+            this.requires = computeRequires(false, ignoreMissingDeps).collect(toSet());
             trace("requires: %s%n", requires);
         }
 
-        private Stream<Module> computeRequires(boolean apionly) {
+        private Stream<Module> computeRequires(boolean apionly, boolean ignoreMissingDeps) {
             // analyze all classes
-
             if (apionly) {
                 dependencyFinder.parseExportedAPIs(Stream.of(root));
             } else {
@@ -135,9 +137,14 @@
 
             // find the modules of all the dependencies found
             return dependencyFinder.getDependences(root)
+                        .filter(a -> !(ignoreMissingDeps && Analyzer.notFound(a)))
                         .map(Archive::getModule);
         }
 
+        boolean hasMissingDependencies() {
+            return dependencyFinder.getDependences(root).anyMatch(Analyzer::notFound);
+        }
+
         ModuleDescriptor descriptor() {
             return descriptor(requiresTransitive, requires);
         }
@@ -196,12 +203,30 @@
             return descriptor(requiresTransitive, g.adjacentNodes(root));
         }
 
+        private void showMissingDeps() {
+            // build the analyzer if there are missing dependences
+            Analyzer analyzer = new Analyzer(configuration, Analyzer.Type.CLASS, DEFAULT_FILTER);
+            analyzer.run(Set.of(root), dependencyFinder.locationToArchive());
+            log.println("Error: Missing dependencies: classes not found from the module path.");
+            Analyzer.Visitor visitor = new Analyzer.Visitor() {
+                @Override
+                public void visitDependence(String origin, Archive originArchive, String target, Archive targetArchive) {
+                    log.format("   %-50s -> %-50s %s%n", origin, target, targetArchive.getName());
+                }
+            };
+            analyzer.visitDependences(root, visitor, Analyzer.Type.VERBOSE, Analyzer::notFound);
+            log.println();
+        }
+
         /**
          * Apply transitive reduction on the resulting graph and reports
          * recommended requires.
          */
-        private void analyzeDeps() {
-            printModuleDescriptor(log, root);
+        private boolean analyzeDeps() {
+            if (requires.stream().anyMatch(m -> m == UNNAMED_MODULE)) {
+                showMissingDeps();
+                return false;
+            }
 
             ModuleDescriptor analyzedDescriptor = descriptor();
             if (!matches(root.descriptor(), analyzedDescriptor)) {
@@ -223,6 +248,7 @@
 
             checkQualifiedExports();
             log.println();
+            return true;
         }
 
         private void checkQualifiedExports() {
@@ -239,6 +265,10 @@
                         .collect(joining(","))));
         }
 
+        void printModuleDescriptor() {
+            printModuleDescriptor(log, root);
+        }
+
         private void printModuleDescriptor(PrintWriter out, Module module) {
             ModuleDescriptor descriptor = module.descriptor();
             out.format("%s (%s)%n", descriptor.name(), module.location());
diff --git a/test/langtools/tools/jdeps/missingDeps/MissingDepsTest.java b/test/langtools/tools/jdeps/missingDeps/MissingDepsTest.java
index 430fe40..edd3f3f 100644
--- a/test/langtools/tools/jdeps/missingDeps/MissingDepsTest.java
+++ b/test/langtools/tools/jdeps/missingDeps/MissingDepsTest.java
@@ -84,6 +84,14 @@
     }
 
     @Test
+    public void checkModuleDeps() {
+        JdepsTest test = new JdepsTest();
+        test.options(List.of("--module-path", "m1.jar", "--multi-release", VERSION, "--check", "m1"));
+        test.checkMissingDeps();
+        test.ignoreMissingDeps("requires java.management");
+    }
+
+    @Test
     public void genModuleInfo() {
         JdepsTest test = new JdepsTest();
         test.options(List.of("--generate-module-info", ".", "--multi-release", VERSION, "mr.jar"));
diff --git a/test/langtools/tools/jdeps/modules/CheckModuleTest.java b/test/langtools/tools/jdeps/modules/CheckModuleTest.java
index 128832cb..850b69e 100644
--- a/test/langtools/tools/jdeps/modules/CheckModuleTest.java
+++ b/test/langtools/tools/jdeps/modules/CheckModuleTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 2020, 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
@@ -86,7 +86,7 @@
             jdeps.appModulePath(MODS_DIR.toString());
 
             ModuleAnalyzer analyzer = jdeps.getModuleAnalyzer(Set.of(name));
-            assertTrue(analyzer.run());
+            assertTrue(analyzer.run(false));
             jdeps.dumpOutput(System.err);
 
             ModuleDescriptor[] descriptors = analyzer.descriptors(name);
@@ -146,7 +146,7 @@
             jdeps.appModulePath(MODS_DIR.toString());
 
             ModuleAnalyzer analyzer = jdeps.getModuleAnalyzer(Set.of(name));
-            assertTrue(analyzer.run());
+            assertTrue(analyzer.run(false));
             jdeps.dumpOutput(System.err);
 
             // compare the module descriptors and the suggested versions