changeset 44747:dc7378149f20

8178404: jlink --suggest-providers should list providers from observable modules Reviewed-by: alanb
author mchung
date Tue, 18 Apr 2017 11:35:29 -0700
parents a06c12b3efb6
children e7d6b4646d04
files jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/Jlink.java jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/jlink.properties jdk/test/tools/jlink/bindservices/BindServices.java jdk/test/tools/jlink/bindservices/SuggestProviders.java jdk/test/tools/jlink/bindservices/src/m3/module-info.java jdk/test/tools/jlink/bindservices/src/m3/p3/MyProvider.java jdk/test/tools/jlink/bindservices/src/m3/p3/S.java
diffstat 8 files changed, 251 insertions(+), 94 deletions(-) [+]
line wrap: on
line diff
--- a/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/Jlink.java	Tue Apr 18 11:25:43 2017 -0700
+++ b/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/Jlink.java	Tue Apr 18 11:35:29 2017 -0700
@@ -158,7 +158,7 @@
          *
          * @param output Output directory, must not exist.
          * @param modulepaths Modules paths
-         * @param modules Root modules to resolve
+         * @param modules The possibly-empty set of root modules to resolve
          * @param limitmods Limit the universe of observable modules
          * @param endian Jimage byte order. Native order by default
          */
@@ -170,13 +170,10 @@
             if (Objects.requireNonNull(modulepaths).isEmpty()) {
                 throw new IllegalArgumentException("Empty module path");
             }
-            if (Objects.requireNonNull(modules).isEmpty()) {
-                throw new IllegalArgumentException("Empty modules");
-            }
 
             this.output = output;
             this.modulepaths = modulepaths;
-            this.modules = modules;
+            this.modules = Objects.requireNonNull(modules);
             this.limitmods = Objects.requireNonNull(limitmods);
             this.endian = Objects.requireNonNull(endian);
             this.finder = moduleFinder();
--- a/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java	Tue Apr 18 11:25:43 2017 -0700
+++ b/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java	Tue Apr 18 11:35:29 2017 -0700
@@ -40,7 +40,20 @@
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
@@ -331,11 +344,6 @@
     // the token for "all modules on the module path"
     private static final String ALL_MODULE_PATH = "ALL-MODULE-PATH";
     private JlinkConfiguration initJlinkConfig() throws BadArgs {
-        if (options.addMods.isEmpty()) {
-            throw taskHelper.newBadArgs("err.mods.must.be.specified", "--add-modules")
-                .showUsage(true);
-        }
-
         Set<String> roots = new HashSet<>();
         for (String mod : options.addMods) {
             if (mod.equals(ALL_MODULE_PATH)) {
@@ -369,6 +377,10 @@
         if (options.output == null) {
             throw taskHelper.newBadArgs("err.output.must.be.specified").showUsage(true);
         }
+        if (options.addMods.isEmpty()) {
+            throw taskHelper.newBadArgs("err.mods.must.be.specified", "--add-modules")
+                            .showUsage(true);
+        }
 
         // First create the image provider
         ImageProvider imageProvider = createImageProvider(config,
@@ -430,7 +442,7 @@
             // print modules to be linked in
             cf.modules().stream()
               .sorted(Comparator.comparing(ResolvedModule::name))
-              .forEach(rm -> log.format("module %s (%s)%n",
+              .forEach(rm -> log.format("%s %s%n",
                                         rm.name(), rm.reference().location().get()));
 
             // print provider info
@@ -505,16 +517,22 @@
 
     /*
      * Returns a map of each service type to the modules that use it
+     * It will include services that are provided by a module but may not used
+     * by any of the observable modules.
      */
     private static Map<String, Set<String>> uses(Set<ModuleReference> modules) {
         // collects the services used by the modules and print uses
-        Map<String, Set<String>> uses = new HashMap<>();
+        Map<String, Set<String>> services = new HashMap<>();
         modules.stream()
                .map(ModuleReference::descriptor)
-               .forEach(md -> md.uses().forEach(s ->
-                   uses.computeIfAbsent(s, _k -> new HashSet<>()).add(md.name()))
-               );
-        return uses;
+               .forEach(md -> {
+                   // include services that may not be used by any observable modules
+                   md.provides().forEach(p ->
+                       services.computeIfAbsent(p.service(), _k -> new HashSet<>()));
+                   md.uses().forEach(s -> services.computeIfAbsent(s, _k -> new HashSet<>())
+                                                  .add(md.name()));
+               });
+        return services;
     }
 
     private static void printProviders(PrintWriter log,
@@ -524,17 +542,16 @@
     }
 
     /*
-     * Prints the providers that are used by the services specified in
-     * the given modules.
+     * Prints the providers that are used by the specified services.
      *
-     * The specified uses maps a service type name to the modules
-     * using the service type and that may or may not be present
-     * the given modules.
+     * The specified services maps a service type name to the modules
+     * using the service type which may be empty if no observable module uses
+     * that service.
      */
     private static void printProviders(PrintWriter log,
                                        String header,
                                        Set<ModuleReference> modules,
-                                       Map<String, Set<String>> uses) {
+                                       Map<String, Set<String>> serviceToUses) {
         if (modules.isEmpty())
             return;
 
@@ -544,7 +561,7 @@
             .map(ModuleReference::descriptor)
             .forEach(md -> {
                 md.provides().stream()
-                  .filter(p -> uses.containsKey(p.service()))
+                  .filter(p -> serviceToUses.containsKey(p.service()))
                   .forEach(p -> providers.computeIfAbsent(p.service(), _k -> new HashSet<>())
                                          .add(md));
             });
@@ -564,11 +581,18 @@
                  .forEach(md ->
                      md.provides().stream()
                        .filter(p -> p.service().equals(service))
-                       .forEach(p -> log.format("  module %s provides %s, used by %s%n",
-                                                md.name(), p.service(),
-                                                uses.get(p.service()).stream()
-                                                    .sorted()
-                                                    .collect(Collectors.joining(","))))
+                       .forEach(p -> {
+                           String usedBy;
+                           if (serviceToUses.get(p.service()).isEmpty()) {
+                               usedBy = "not used by any observable module";
+                           } else {
+                               usedBy = serviceToUses.get(p.service()).stream()
+                                            .sorted()
+                                            .collect(Collectors.joining(",", "used by ", ""));
+                           }
+                           log.format("  %s provides %s %s%n",
+                                      md.name(), p.service(), usedBy);
+                       })
                  );
             });
     }
@@ -589,25 +613,21 @@
 
         ModuleFinder finder = config.finder();
         if (args.isEmpty()) {
-            // print providers used by the modules resolved without service binding
-            Configuration cf = config.resolve();
-            Set<ModuleReference> mrefs = cf.modules().stream()
-                .map(ResolvedModule::reference)
-                .collect(Collectors.toSet());
-
+            // print providers used by the observable modules without service binding
+            Set<ModuleReference> mrefs = finder.findAll();
             // print uses of the modules that would be linked into the image
             mrefs.stream()
                  .sorted(Comparator.comparing(mref -> mref.descriptor().name()))
                  .forEach(mref -> {
                      ModuleDescriptor md = mref.descriptor();
-                     log.format("module %s located (%s)%n", md.name(),
+                     log.format("%s %s%n", md.name(),
                                 mref.location().get());
                      md.uses().stream().sorted()
                        .forEach(s -> log.format("    uses %s%n", s));
                  });
 
             String msg = String.format("%n%s:", taskHelper.getMessage("suggested.providers.header"));
-            printProviders(log, msg, finder.findAll(), uses(mrefs));
+            printProviders(log, msg, mrefs, uses(mrefs));
 
         } else {
             // comma-separated service types, if specified
@@ -620,17 +640,22 @@
                                     .anyMatch(names::contains))
                 .collect(Collectors.toSet());
 
-            // the specified services may or may not be in the modules that
-            // would be linked in.  So find uses declared in all observable modules
-            Map<String, Set<String>> uses = uses(finder.findAll());
+            // find the modules that uses the specified services
+            Map<String, Set<String>> uses = new HashMap<>();
+            names.forEach(s -> uses.computeIfAbsent(s, _k -> new HashSet<>()));
+            finder.findAll().stream()
+                  .map(ModuleReference::descriptor)
+                  .forEach(md -> md.uses().stream()
+                                   .filter(names::contains)
+                                   .forEach(s -> uses.get(s).add(md.name())));
 
-            // check if any name given on the command line are unused service
+            // check if any name given on the command line are not provided by any module
             mrefs.stream()
                  .flatMap(mref -> mref.descriptor().provides().stream()
                                       .map(ModuleDescriptor.Provides::service))
                  .forEach(names::remove);
             if (!names.isEmpty()) {
-                log.println(taskHelper.getMessage("warn.unused.services",
+                log.println(taskHelper.getMessage("warn.provider.notfound",
                                                   toString(names)));
             }
 
--- a/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/jlink.properties	Tue Apr 18 11:25:43 2017 -0700
+++ b/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/jlink.properties	Tue Apr 18 11:35:29 2017 -0700
@@ -57,12 +57,12 @@
 \                                        if specified  
 
 main.opt.bind-services=\
-\      --bind-services                   Do full service binding
+\      --bind-services                   Link in service provider modules and\n\
+\                                        their dependences
 
 main.opt.suggest-providers=\
-\      --suggest-providers [<name>,...]  Suggest providers of services used by\n\
-\                                        the modules that would be linked, or\n\
-\                                        of the given service types
+\      --suggest-providers [<name>,...]  Suggest providers that implement the\n\
+\                                        given service types from the module path
 
 main.command.files=\
 \      @<filename>                       Read options from file
@@ -138,7 +138,7 @@
 warn.signing=WARNING: signed modular JAR {0} is currently not supported
 warn.invalid.arg=invalid classname or pathname not exist: {0}
 warn.split.package=package {0} defined in {1} {2}
-warn.unused.services=Services specified in --suggest-providers not used: {0}
+warn.provider.notfound=No provider found for service specified to --suggest-providers: {0}
 no.suggested.providers=--bind-services option is specified. No additional providers suggested.
 suggested.providers.header=Suggested providers
 providers.header=Providers
--- a/jdk/test/tools/jlink/bindservices/BindServices.java	Tue Apr 18 11:25:43 2017 -0700
+++ b/jdk/test/tools/jlink/bindservices/BindServices.java	Tue Apr 18 11:35:29 2017 -0700
@@ -33,7 +33,6 @@
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
-import static jdk.testlibrary.Asserts.assertTrue;
 import static jdk.testlibrary.ProcessTools.*;
 
 import org.testng.annotations.BeforeTest;
@@ -114,7 +113,7 @@
                   "--module-path", MODULE_PATH,
                   "--add-modules", "m1",
                   "--bind-services",
-                  "--limit-modules", "m1,m2,m3,java.base");
+                  "--limit-modules", "m1,m2,m3");
 
         testImage(dir, "m1", "m2", "m3");
     }
@@ -131,16 +130,18 @@
                       "--add-modules", "m1",
                       "--bind-services",
                       "--verbose",
-                      "--limit-modules", "m1,m2,m3,java.base").output();
+                      "--limit-modules", "m1,m2,m3").output();
 
         List<String> expected = List.of(
-            "module m1 (" + MODS_DIR.resolve("m1").toUri().toString() + ")",
-            "module m2 (" + MODS_DIR.resolve("m2").toUri().toString() + ")",
-            "module m3 (" + MODS_DIR.resolve("m3").toUri().toString() + ")",
-            "module m1 provides p1.S, used by m1",
-            "module m2 provides p1.S, used by m1",
-            "module m2 provides p2.T, used by m2",
-            "module m3 provides p2.T, used by m2"
+            "m1 " + MODS_DIR.resolve("m1").toUri().toString(),
+            "m2 " + MODS_DIR.resolve("m2").toUri().toString(),
+            "m3 " + MODS_DIR.resolve("m3").toUri().toString(),
+            "java.base provides java.nio.file.spi.FileSystemProvider used by java.base",
+            "m1 provides p1.S used by m1",
+            "m2 provides p1.S used by m1",
+            "m2 provides p2.T used by m2",
+            "m3 provides p2.T used by m2",
+            "m3 provides p3.S not used by any observable module"
         );
 
         assertTrue(output.containsAll(expected));
--- a/jdk/test/tools/jlink/bindservices/SuggestProviders.java	Tue Apr 18 11:25:43 2017 -0700
+++ b/jdk/test/tools/jlink/bindservices/SuggestProviders.java	Tue Apr 18 11:35:29 2017 -0700
@@ -33,8 +33,6 @@
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
-import static jdk.testlibrary.Asserts.assertTrue;
-
 import org.testng.annotations.BeforeTest;
 import org.testng.annotations.Test;
 import static org.testng.Assert.*;
@@ -85,34 +83,102 @@
         }
     }
 
+    // check a subset of services used by java.base
+    private final List<String> JAVA_BASE_USES = List.of(
+        "uses java.lang.System$LoggerFinder",
+        "uses java.net.ContentHandlerFactory",
+        "uses java.net.spi.URLStreamHandlerProvider",
+        "uses java.nio.channels.spi.AsynchronousChannelProvider",
+        "uses java.nio.channels.spi.SelectorProvider",
+        "uses java.nio.charset.spi.CharsetProvider",
+        "uses java.nio.file.spi.FileSystemProvider",
+        "uses java.nio.file.spi.FileTypeDetector",
+        "uses java.security.Provider",
+        "uses java.util.spi.ToolProvider"
+    );
+
+    private final List<String> JAVA_BASE_PROVIDERS = List.of(
+        "java.base provides java.nio.file.spi.FileSystemProvider used by java.base"
+    );
+
+    private final List<String> SYSTEM_PROVIDERS = List.of(
+        "jdk.charsets provides java.nio.charset.spi.CharsetProvider used by java.base",
+        "jdk.compiler provides java.util.spi.ToolProvider used by java.base",
+        "jdk.compiler provides javax.tools.JavaCompiler used by java.compiler",
+        "jdk.jlink provides jdk.tools.jlink.plugin.Plugin used by jdk.jlink",
+        "jdk.jlink provides java.util.spi.ToolProvider used by java.base"
+    );
+
+    private final List<String> APP_USES = List.of(
+        "uses p1.S",
+        "uses p2.T"
+    );
+
+    private final List<String> APP_PROVIDERS = List.of(
+        "m1 provides p1.S used by m1",
+        "m2 provides p1.S used by m1",
+        "m2 provides p2.T used by m2",
+        "m3 provides p2.T used by m2",
+        "m3 provides p3.S not used by any observable module"
+    );
+
     @Test
     public void suggestProviders() throws Throwable {
         if (!hasJmods()) return;
 
         List<String> output = JLink.run("--module-path", MODULE_PATH,
+                                        "--suggest-providers").output();
+
+        Stream<String> uses =
+            Stream.concat(JAVA_BASE_USES.stream(), APP_USES.stream());
+        Stream<String> providers =
+            Stream.concat(SYSTEM_PROVIDERS.stream(), APP_PROVIDERS.stream());
+
+        assertTrue(output.containsAll(Stream.concat(uses, providers)
+                                            .collect(Collectors.toList())));
+    }
+
+    /**
+     * find providers from the observable modules and --add-modules has no
+     * effect on the suggested providers
+     */
+    @Test
+    public void observableModules() throws Throwable {
+        if (!hasJmods()) return;
+
+        List<String> output = JLink.run("--module-path", MODULE_PATH,
                                         "--add-modules", "m1",
                                         "--suggest-providers").output();
-        // check a subset of services used by java.base
-        List<String> expected = List.of(
-            "uses java.lang.System$LoggerFinder",
-            "uses java.net.ContentHandlerFactory",
-            "uses java.net.spi.URLStreamHandlerProvider",
-            "uses java.nio.channels.spi.AsynchronousChannelProvider",
-            "uses java.nio.channels.spi.SelectorProvider",
-            "uses java.nio.charset.spi.CharsetProvider",
-            "uses java.nio.file.spi.FileSystemProvider",
-            "uses java.nio.file.spi.FileTypeDetector",
-            "uses java.security.Provider",
-            "uses java.util.spi.ToolProvider",
-            "uses p1.S",
-            "module jdk.charsets provides java.nio.charset.spi.CharsetProvider, used by java.base",
-            "module jdk.compiler provides java.util.spi.ToolProvider, used by java.base",
-            "module jdk.jlink provides java.util.spi.ToolProvider, used by java.base",
-            "module m1 provides p1.S, used by m1",
-            "module m2 provides p1.S, used by m1"
+
+        Stream<String> uses =
+            Stream.concat(JAVA_BASE_USES.stream(), Stream.of("uses p1.S"));
+        Stream<String> providers =
+            Stream.concat(SYSTEM_PROVIDERS.stream(), APP_PROVIDERS.stream());
+
+        assertTrue(output.containsAll(Stream.concat(uses, providers)
+                                            .collect(Collectors.toList())));
+    }
+
+    /**
+     * find providers from the observable modules with --limit-modules
+     */
+    @Test
+    public void limitModules() throws Throwable {
+        if (!hasJmods()) return;
+
+        List<String> output = JLink.run("--module-path", MODULE_PATH,
+                                        "--limit-modules", "m1",
+                                        "--suggest-providers").output();
+
+        Stream<String> uses =
+            Stream.concat(JAVA_BASE_USES.stream(), Stream.of("uses p1.S"));
+        Stream<String> providers =
+            Stream.concat(JAVA_BASE_PROVIDERS.stream(),
+                          Stream.of("m1 provides p1.S used by m1")
         );
 
-        assertTrue(output.containsAll(expected));
+        assertTrue(output.containsAll(Stream.concat(uses, providers)
+                                            .collect(Collectors.toList())));
     }
 
     @Test
@@ -121,20 +187,17 @@
 
         List<String> output =
             JLink.run("--module-path", MODULE_PATH,
-                      "--add-modules", "m1",
                       "--suggest-providers",
-                      "java.nio.charset.spi.CharsetProvider,p1.S,p2.T").output();
+                      "java.nio.charset.spi.CharsetProvider,p1.S").output();
 
         System.out.println(output);
-        List<String> expected = List.of(
-            "module jdk.charsets provides java.nio.charset.spi.CharsetProvider, used by java.base",
-            "module m1 provides p1.S, used by m1",
-            "module m2 provides p1.S, used by m1",
-            "module m2 provides p2.T, used by m2",
-            "module m3 provides p2.T, used by m2"
+        Stream<String> expected = Stream.concat(
+            Stream.of("jdk.charsets provides java.nio.charset.spi.CharsetProvider used by java.base"),
+            Stream.of("m1 provides p1.S used by m1",
+                      "m2 provides p1.S used by m1")
         );
 
-        assertTrue(output.containsAll(expected));
+        assertTrue(output.containsAll(expected.collect(Collectors.toList())));
     }
 
     @Test
@@ -143,15 +206,30 @@
 
         List<String> output =
             JLink.run("--module-path", MODULE_PATH,
-                "--add-modules", "m1",
-                "--suggest-providers",
-                "nonExistentType").output();
+                      "--suggest-providers",
+                      "p3.S").output();
 
-        System.out.println(output);
         List<String> expected = List.of(
-            "Services specified in --suggest-providers not used: nonExistentType"
+            "m3 provides p3.S not used by any observable module"
         );
+        assertTrue(output.containsAll(expected));
 
+        // should not print other services m3 provides
+        assertFalse(output.contains("m3 provides p2.T used by m2"));
+    }
+
+    @Test
+    public void nonExistentService() throws Throwable {
+        if (!hasJmods()) return;
+
+        List<String> output =
+            JLink.run("--module-path", MODULE_PATH,
+                      "--suggest-providers",
+                      "nonExistentType").output();
+
+        List<String> expected = List.of(
+            "No provider found for service specified to --suggest-providers: nonExistentType"
+        );
         assertTrue(output.containsAll(expected));
     }
 
@@ -161,9 +239,7 @@
 
         List<String> output =
             JLink.run("--module-path", MODULE_PATH,
-                      "--add-modules", "m1",
                       "--bind-services",
-                      "--limit-modules", "m1,m2,m3,java.base",
                       "--suggest-providers").output();
 
         String expected = "--bind-services option is specified. No additional providers suggested.";
--- a/jdk/test/tools/jlink/bindservices/src/m3/module-info.java	Tue Apr 18 11:25:43 2017 -0700
+++ b/jdk/test/tools/jlink/bindservices/src/m3/module-info.java	Tue Apr 18 11:35:29 2017 -0700
@@ -24,4 +24,5 @@
 module m3 {
     requires m2;
     provides p2.T with p3.Impl;
+    provides p3.S with p3.MyProvider;
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/tools/jlink/bindservices/src/m3/p3/MyProvider.java	Tue Apr 18 11:35:29 2017 -0700
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2017, 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 p3;
+
+public class MyProvider implements S {
+    public void run() {
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/tools/jlink/bindservices/src/m3/p3/S.java	Tue Apr 18 11:35:29 2017 -0700
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2017, 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 p3;
+
+public interface S {
+    void run();
+}