1 /*
   2  * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package jdk.tools.jlink.internal;
  26 
  27 import java.io.File;
  28 import java.io.IOException;
  29 import java.io.PrintWriter;
  30 import java.io.UncheckedIOException;
  31 import java.lang.module.Configuration;
  32 import java.lang.module.FindException;
  33 import java.lang.module.ModuleDescriptor;
  34 import java.lang.module.ModuleFinder;
  35 import java.lang.module.ModuleReference;
  36 import java.lang.module.ResolutionException;
  37 import java.lang.module.ResolvedModule;
  38 import java.net.URI;
  39 import java.nio.ByteOrder;
  40 import java.nio.file.Files;
  41 import java.nio.file.Path;
  42 import java.nio.file.Paths;
  43 import java.util.*;
  44 import java.util.stream.Collectors;
  45 import java.util.stream.Stream;
  46 
  47 import jdk.tools.jlink.internal.TaskHelper.BadArgs;
  48 import static jdk.tools.jlink.internal.TaskHelper.JLINK_BUNDLE;
  49 import jdk.tools.jlink.internal.Jlink.JlinkConfiguration;
  50 import jdk.tools.jlink.internal.Jlink.PluginsConfiguration;
  51 import jdk.tools.jlink.internal.TaskHelper.Option;
  52 import jdk.tools.jlink.internal.TaskHelper.OptionsHelper;
  53 import jdk.tools.jlink.internal.ImagePluginStack.ImageProvider;
  54 import jdk.tools.jlink.plugin.PluginException;
  55 import jdk.tools.jlink.builder.DefaultImageBuilder;
  56 import jdk.tools.jlink.plugin.Plugin;
  57 import jdk.internal.module.ModulePath;
  58 import jdk.internal.module.ModuleResolution;
  59 
  60 /**
  61  * Implementation for the jlink tool.
  62  *
  63  * ## Should use jdk.joptsimple some day.
  64  */
  65 public class JlinkTask {
  66     static final boolean DEBUG = Boolean.getBoolean("jlink.debug");
  67 
  68     // jlink API ignores by default. Remove when signing is implemented.
  69     static final boolean IGNORE_SIGNING_DEFAULT = true;
  70 
  71     private static final TaskHelper taskHelper
  72             = new TaskHelper(JLINK_BUNDLE);
  73 
  74     private static final Option<?>[] recognizedOptions = {
  75         new Option<JlinkTask>(false, (task, opt, arg) -> {
  76             task.options.help = true;
  77         }, "--help", "-h"),
  78         new Option<JlinkTask>(true, (task, opt, arg) -> {
  79             // if used multiple times, the last one wins!
  80             // So, clear previous values, if any.
  81             task.options.modulePath.clear();
  82             String[] dirs = arg.split(File.pathSeparator);
  83             int i = 0;
  84             Arrays.stream(dirs)
  85                   .map(Paths::get)
  86                   .forEach(task.options.modulePath::add);
  87         }, "--module-path", "-p"),
  88         new Option<JlinkTask>(true, (task, opt, arg) -> {
  89             // if used multiple times, the last one wins!
  90             // So, clear previous values, if any.
  91             task.options.limitMods.clear();
  92             for (String mn : arg.split(",")) {
  93                 if (mn.isEmpty()) {
  94                     throw taskHelper.newBadArgs("err.mods.must.be.specified",
  95                             "--limit-modules");
  96                 }
  97                 task.options.limitMods.add(mn);
  98             }
  99         }, "--limit-modules"),
 100         new Option<JlinkTask>(true, (task, opt, arg) -> {
 101             for (String mn : arg.split(",")) {
 102                 if (mn.isEmpty()) {
 103                     throw taskHelper.newBadArgs("err.mods.must.be.specified",
 104                             "--add-modules");
 105                 }
 106                 task.options.addMods.add(mn);
 107             }
 108         }, "--add-modules"),
 109         new Option<JlinkTask>(true, (task, opt, arg) -> {
 110             Path path = Paths.get(arg);
 111             task.options.output = path;
 112         }, "--output"),
 113         new Option<JlinkTask>(false, (task, opt, arg) -> {
 114             task.options.bindServices = true;
 115         }, "--bind-services"),
 116         new Option<JlinkTask>(false, (task, opt, arg) -> {
 117             task.options.suggestProviders = true;
 118         }, "--suggest-providers", "", true),
 119         new Option<JlinkTask>(true, (task, opt, arg) -> {
 120             String[] values = arg.split("=");
 121             // check values
 122             if (values.length != 2 || values[0].isEmpty() || values[1].isEmpty()) {
 123                 throw taskHelper.newBadArgs("err.launcher.value.format", arg);
 124             } else {
 125                 String commandName = values[0];
 126                 String moduleAndMain = values[1];
 127                 int idx = moduleAndMain.indexOf("/");
 128                 if (idx != -1) {
 129                     if (moduleAndMain.substring(0, idx).isEmpty()) {
 130                         throw taskHelper.newBadArgs("err.launcher.module.name.empty", arg);
 131                     }
 132 
 133                     if (moduleAndMain.substring(idx + 1).isEmpty()) {
 134                         throw taskHelper.newBadArgs("err.launcher.main.class.empty", arg);
 135                     }
 136                 }
 137                 task.options.launchers.put(commandName, moduleAndMain);
 138             }
 139         }, "--launcher"),
 140         new Option<JlinkTask>(true, (task, opt, arg) -> {
 141             if ("little".equals(arg)) {
 142                 task.options.endian = ByteOrder.LITTLE_ENDIAN;
 143             } else if ("big".equals(arg)) {
 144                 task.options.endian = ByteOrder.BIG_ENDIAN;
 145             } else {
 146                 throw taskHelper.newBadArgs("err.unknown.byte.order", arg);
 147             }
 148         }, "--endian"),
 149         new Option<JlinkTask>(false, (task, opt, arg) -> {
 150             task.options.verbose = true;
 151         }, "--verbose", "-v"),
 152         new Option<JlinkTask>(false, (task, opt, arg) -> {
 153             task.options.version = true;
 154         }, "--version"),
 155         new Option<JlinkTask>(true, (task, opt, arg) -> {
 156             Path path = Paths.get(arg);
 157             if (Files.exists(path)) {
 158                 throw taskHelper.newBadArgs("err.dir.exists", path);
 159             }
 160             task.options.packagedModulesPath = path;
 161         }, true, "--keep-packaged-modules"),
 162         new Option<JlinkTask>(true, (task, opt, arg) -> {
 163             task.options.saveoptsfile = arg;
 164         }, "--save-opts"),
 165         new Option<JlinkTask>(false, (task, opt, arg) -> {
 166             task.options.fullVersion = true;
 167         }, true, "--full-version"),
 168         new Option<JlinkTask>(false, (task, opt, arg) -> {
 169             task.options.ignoreSigning = true;
 170         }, "--ignore-signing-information"),};
 171 
 172     private static final String PROGNAME = "jlink";
 173     private final OptionsValues options = new OptionsValues();
 174 
 175     private static final OptionsHelper<JlinkTask> optionsHelper
 176             = taskHelper.newOptionsHelper(JlinkTask.class, recognizedOptions);
 177     private PrintWriter log;
 178 
 179     void setLog(PrintWriter out, PrintWriter err) {
 180         log = out;
 181         taskHelper.setLog(log);
 182     }
 183 
 184     /**
 185      * Result codes.
 186      */
 187     static final int
 188             EXIT_OK = 0, // Completed with no errors.
 189             EXIT_ERROR = 1, // Completed but reported errors.
 190             EXIT_CMDERR = 2, // Bad command-line arguments
 191             EXIT_SYSERR = 3, // System error or resource exhaustion.
 192             EXIT_ABNORMAL = 4;// terminated abnormally
 193 
 194     static class OptionsValues {
 195         boolean help;
 196         String  saveoptsfile;
 197         boolean verbose;
 198         boolean version;
 199         boolean fullVersion;
 200         final List<Path> modulePath = new ArrayList<>();
 201         final Set<String> limitMods = new HashSet<>();
 202         final Set<String> addMods = new HashSet<>();
 203         Path output;
 204         final Map<String, String> launchers = new HashMap<>();
 205         Path packagedModulesPath;
 206         ByteOrder endian = ByteOrder.nativeOrder();
 207         boolean ignoreSigning = false;
 208         boolean bindServices = false;
 209         boolean suggestProviders = false;
 210     }
 211 
 212     int run(String[] args) {
 213         if (log == null) {
 214             setLog(new PrintWriter(System.out, true),
 215                    new PrintWriter(System.err, true));
 216         }
 217         try {
 218             List<String> remaining = optionsHelper.handleOptions(this, args);
 219             if (remaining.size() > 0 && !options.suggestProviders) {
 220                 throw taskHelper.newBadArgs("err.orphan.arguments", toString(remaining))
 221                                 .showUsage(true);
 222             }
 223             if (options.help) {
 224                 optionsHelper.showHelp(PROGNAME);
 225                 return EXIT_OK;
 226             }
 227             if (optionsHelper.shouldListPlugins()) {
 228                 optionsHelper.listPlugins();
 229                 return EXIT_OK;
 230             }
 231             if (options.version || options.fullVersion) {
 232                 taskHelper.showVersion(options.fullVersion);
 233                 return EXIT_OK;
 234             }
 235 
 236             if (taskHelper.getExistingImage() != null) {
 237                 postProcessOnly(taskHelper.getExistingImage());
 238                 return EXIT_OK;
 239             }
 240 
 241             if (options.modulePath.isEmpty()) {
 242                 throw taskHelper.newBadArgs("err.modulepath.must.be.specified")
 243                                 .showUsage(true);
 244             }
 245 
 246             JlinkConfiguration config = initJlinkConfig();
 247             if (options.suggestProviders) {
 248                 suggestProviders(config, remaining);
 249             } else {
 250                 createImage(config);
 251                 if (options.saveoptsfile != null) {
 252                     Files.write(Paths.get(options.saveoptsfile), getSaveOpts().getBytes());
 253                 }
 254             }
 255 
 256             return EXIT_OK;
 257         } catch (PluginException | IllegalArgumentException |
 258                  UncheckedIOException |IOException | FindException | ResolutionException e) {
 259             log.println(taskHelper.getMessage("error.prefix") + " " + e.getMessage());
 260             if (DEBUG) {
 261                 e.printStackTrace(log);
 262             }
 263             return EXIT_ERROR;
 264         } catch (BadArgs e) {
 265             taskHelper.reportError(e.key, e.args);
 266             if (e.showUsage) {
 267                 log.println(taskHelper.getMessage("main.usage.summary", PROGNAME));
 268             }
 269             if (DEBUG) {
 270                 e.printStackTrace(log);
 271             }
 272             return EXIT_CMDERR;
 273         } catch (Throwable x) {
 274             log.println(taskHelper.getMessage("error.prefix") + " " + x.getMessage());
 275             x.printStackTrace(log);
 276             return EXIT_ABNORMAL;
 277         } finally {
 278             log.flush();
 279         }
 280     }
 281 
 282     /*
 283      * Jlink API entry point.
 284      */
 285     public static void createImage(JlinkConfiguration config,
 286                                    PluginsConfiguration plugins)
 287             throws Exception {
 288         Objects.requireNonNull(config);
 289         Objects.requireNonNull(config.getOutput());
 290         plugins = plugins == null ? new PluginsConfiguration() : plugins;
 291 
 292         // First create the image provider
 293         ImageProvider imageProvider =
 294                 createImageProvider(config,
 295                                     null,
 296                                     IGNORE_SIGNING_DEFAULT,
 297                                     false,
 298                                     false,
 299                                     null);
 300 
 301         // Then create the Plugin Stack
 302         ImagePluginStack stack = ImagePluginConfiguration.parseConfiguration(plugins);
 303 
 304         //Ask the stack to proceed;
 305         stack.operate(imageProvider);
 306     }
 307 
 308     /*
 309      * Jlink API entry point.
 310      */
 311     public static void postProcessImage(ExecutableImage image, List<Plugin> postProcessorPlugins)
 312             throws Exception {
 313         Objects.requireNonNull(image);
 314         Objects.requireNonNull(postProcessorPlugins);
 315         PluginsConfiguration config = new PluginsConfiguration(postProcessorPlugins);
 316         ImagePluginStack stack = ImagePluginConfiguration.
 317                 parseConfiguration(config);
 318 
 319         stack.operate((ImagePluginStack stack1) -> image);
 320     }
 321 
 322     private void postProcessOnly(Path existingImage) throws Exception {
 323         PluginsConfiguration config = taskHelper.getPluginsConfig(null, null);
 324         ExecutableImage img = DefaultImageBuilder.getExecutableImage(existingImage);
 325         if (img == null) {
 326             throw taskHelper.newBadArgs("err.existing.image.invalid");
 327         }
 328         postProcessImage(img, config.getPlugins());
 329     }
 330 
 331     // the token for "all modules on the module path"
 332     private static final String ALL_MODULE_PATH = "ALL-MODULE-PATH";
 333     private JlinkConfiguration initJlinkConfig() throws BadArgs {
 334         if (options.addMods.isEmpty()) {
 335             throw taskHelper.newBadArgs("err.mods.must.be.specified", "--add-modules")
 336                 .showUsage(true);
 337         }
 338 
 339         Set<String> roots = new HashSet<>();
 340         for (String mod : options.addMods) {
 341             if (mod.equals(ALL_MODULE_PATH)) {
 342                 Path[] entries = options.modulePath.toArray(new Path[0]);
 343                 ModuleFinder finder = ModulePath.of(Runtime.version(), true, entries);
 344                 if (!options.limitMods.isEmpty()) {
 345                     // finder for the observable modules specified in
 346                     // the --module-path and --limit-modules options
 347                     finder = limitFinder(finder, options.limitMods, Collections.emptySet());
 348                 }
 349 
 350                 // all observable modules are roots
 351                 finder.findAll()
 352                       .stream()
 353                       .map(ModuleReference::descriptor)
 354                       .map(ModuleDescriptor::name)
 355                       .forEach(mn -> roots.add(mn));
 356             } else {
 357                 roots.add(mod);
 358             }
 359         }
 360 
 361         return new JlinkConfiguration(options.output,
 362                                       options.modulePath,
 363                                       roots,
 364                                       options.limitMods,
 365                                       options.endian);
 366     }
 367 
 368     private void createImage(JlinkConfiguration config) throws Exception {
 369         if (options.output == null) {
 370             throw taskHelper.newBadArgs("err.output.must.be.specified").showUsage(true);
 371         }
 372 
 373         // First create the image provider
 374         ImageProvider imageProvider = createImageProvider(config,
 375                                                           options.packagedModulesPath,
 376                                                           options.ignoreSigning,
 377                                                           options.bindServices,
 378                                                           options.verbose,
 379                                                           log);
 380 
 381         // Then create the Plugin Stack
 382         ImagePluginStack stack = ImagePluginConfiguration.parseConfiguration(
 383             taskHelper.getPluginsConfig(options.output, options.launchers));
 384 
 385         //Ask the stack to proceed
 386         stack.operate(imageProvider);
 387     }
 388 
 389     /*
 390      * Returns a module finder of the given module path that limits
 391      * the observable modules to those in the transitive closure of
 392      * the modules specified in {@code limitMods} plus other modules
 393      * specified in the {@code roots} set.
 394      */
 395     public static ModuleFinder newModuleFinder(List<Path> paths,
 396                                                Set<String> limitMods,
 397                                                Set<String> roots)
 398     {
 399         Path[] entries = paths.toArray(new Path[0]);
 400         ModuleFinder finder = ModulePath.of(Runtime.version(), true, entries);
 401 
 402         // if limitmods is specified then limit the universe
 403         if (!limitMods.isEmpty()) {
 404             finder = limitFinder(finder, limitMods, roots);
 405         }
 406         return finder;
 407     }
 408 
 409     private static Path toPathLocation(ResolvedModule m) {
 410         Optional<URI> ouri = m.reference().location();
 411         if (!ouri.isPresent())
 412             throw new InternalError(m + " does not have a location");
 413         URI uri = ouri.get();
 414         return Paths.get(uri);
 415     }
 416 
 417 
 418     private static ImageProvider createImageProvider(JlinkConfiguration config,
 419                                                      Path retainModulesPath,
 420                                                      boolean ignoreSigning,
 421                                                      boolean bindService,
 422                                                      boolean verbose,
 423                                                      PrintWriter log)
 424             throws IOException
 425     {
 426         Configuration cf = bindService ? config.resolveAndBind()
 427                                        : config.resolve();
 428 
 429         if (verbose && log != null) {
 430             // print modules to be linked in
 431             cf.modules().stream()
 432               .sorted(Comparator.comparing(ResolvedModule::name))
 433               .forEach(rm -> log.format("module %s (%s)%n",
 434                                         rm.name(), rm.reference().location().get()));
 435 
 436             // print provider info
 437             Set<ModuleReference> references = cf.modules().stream()
 438                 .map(ResolvedModule::reference).collect(Collectors.toSet());
 439 
 440             String msg = String.format("%n%s:", taskHelper.getMessage("providers.header"));
 441             printProviders(log, msg, references);
 442         }
 443 
 444         // emit a warning for any incubating modules in the configuration
 445         if (log != null) {
 446             String im = cf.modules()
 447                           .stream()
 448                           .map(ResolvedModule::reference)
 449                           .filter(ModuleResolution::hasIncubatingWarning)
 450                           .map(ModuleReference::descriptor)
 451                           .map(ModuleDescriptor::name)
 452                           .collect(Collectors.joining(", "));
 453 
 454             if (!"".equals(im))
 455                 log.println("WARNING: Using incubator modules: " + im);
 456         }
 457 
 458         Map<String, Path> mods = cf.modules().stream()
 459             .collect(Collectors.toMap(ResolvedModule::name, JlinkTask::toPathLocation));
 460         return new ImageHelper(cf, mods, config.getByteOrder(), retainModulesPath, ignoreSigning);
 461     }
 462 
 463     /*
 464      * Returns a ModuleFinder that limits observability to the given root
 465      * modules, their transitive dependences, plus a set of other modules.
 466      */
 467     public static ModuleFinder limitFinder(ModuleFinder finder,
 468                                            Set<String> roots,
 469                                            Set<String> otherMods) {
 470 
 471         // resolve all root modules
 472         Configuration cf = Configuration.empty()
 473                 .resolve(finder,
 474                          ModuleFinder.of(),
 475                          roots);
 476 
 477         // module name -> reference
 478         Map<String, ModuleReference> map = new HashMap<>();
 479         cf.modules().forEach(m -> {
 480             ModuleReference mref = m.reference();
 481             map.put(mref.descriptor().name(), mref);
 482         });
 483 
 484         // add the other modules
 485         otherMods.stream()
 486             .map(finder::find)
 487             .flatMap(Optional::stream)
 488             .forEach(mref -> map.putIfAbsent(mref.descriptor().name(), mref));
 489 
 490         // set of modules that are observable
 491         Set<ModuleReference> mrefs = new HashSet<>(map.values());
 492 
 493         return new ModuleFinder() {
 494             @Override
 495             public Optional<ModuleReference> find(String name) {
 496                 return Optional.ofNullable(map.get(name));
 497             }
 498 
 499             @Override
 500             public Set<ModuleReference> findAll() {
 501                 return mrefs;
 502             }
 503         };
 504     }
 505 
 506     /*
 507      * Returns a map of each service type to the modules that use it
 508      */
 509     private static Map<String, Set<String>> uses(Set<ModuleReference> modules) {
 510         // collects the services used by the modules and print uses
 511         Map<String, Set<String>> uses = new HashMap<>();
 512         modules.stream()
 513                .map(ModuleReference::descriptor)
 514                .forEach(md -> md.uses().forEach(s ->
 515                    uses.computeIfAbsent(s, _k -> new HashSet<>()).add(md.name()))
 516                );
 517         return uses;
 518     }
 519 
 520     private static void printProviders(PrintWriter log,
 521                                        String header,
 522                                        Set<ModuleReference> modules) {
 523         printProviders(log, header, modules, uses(modules));
 524     }
 525 
 526     /*
 527      * Prints the providers that are used by the services specified in
 528      * the given modules.
 529      *
 530      * The specified uses maps a service type name to the modules
 531      * using the service type and that may or may not be present
 532      * the given modules.
 533      */
 534     private static void printProviders(PrintWriter log,
 535                                        String header,
 536                                        Set<ModuleReference> modules,
 537                                        Map<String, Set<String>> uses) {
 538         if (modules.isEmpty())
 539             return;
 540 
 541         // Build a map of a service type to the provider modules
 542         Map<String, Set<ModuleDescriptor>> providers = new HashMap<>();
 543         modules.stream()
 544             .map(ModuleReference::descriptor)
 545             .forEach(md -> {
 546                 md.provides().stream()
 547                   .filter(p -> uses.containsKey(p.service()))
 548                   .forEach(p -> providers.computeIfAbsent(p.service(), _k -> new HashSet<>())
 549                                          .add(md));
 550             });
 551 
 552         if (!providers.isEmpty()) {
 553             log.println(header);
 554         }
 555 
 556         // print the providers of the service types used by the specified modules
 557         // sorted by the service type name and then provider's module name
 558         providers.entrySet().stream()
 559             .sorted(Map.Entry.comparingByKey())
 560             .forEach(e -> {
 561                 String service = e.getKey();
 562                 e.getValue().stream()
 563                  .sorted(Comparator.comparing(ModuleDescriptor::name))
 564                  .forEach(md ->
 565                      md.provides().stream()
 566                        .filter(p -> p.service().equals(service))
 567                        .forEach(p -> log.format("  module %s provides %s, used by %s%n",
 568                                                 md.name(), p.service(),
 569                                                 uses.get(p.service()).stream()
 570                                                     .sorted()
 571                                                     .collect(Collectors.joining(","))))
 572                  );
 573             });
 574     }
 575 
 576     private void suggestProviders(JlinkConfiguration config, List<String> args)
 577         throws BadArgs
 578     {
 579         if (args.size() > 1) {
 580             throw taskHelper.newBadArgs("err.orphan.argument",
 581                                         toString(args.subList(1, args.size())))
 582                             .showUsage(true);
 583         }
 584 
 585         if (options.bindServices) {
 586             log.println(taskHelper.getMessage("no.suggested.providers"));
 587             return;
 588         }
 589 
 590         ModuleFinder finder = config.finder();
 591         if (args.isEmpty()) {
 592             // print providers used by the modules resolved without service binding
 593             Configuration cf = config.resolve();
 594             Set<ModuleReference> mrefs = cf.modules().stream()
 595                 .map(ResolvedModule::reference)
 596                 .collect(Collectors.toSet());
 597 
 598             // print uses of the modules that would be linked into the image
 599             mrefs.stream()
 600                  .sorted(Comparator.comparing(mref -> mref.descriptor().name()))
 601                  .forEach(mref -> {
 602                      ModuleDescriptor md = mref.descriptor();
 603                      log.format("module %s located (%s)%n", md.name(),
 604                                 mref.location().get());
 605                      md.uses().stream().sorted()
 606                        .forEach(s -> log.format("    uses %s%n", s));
 607                  });
 608 
 609             String msg = String.format("%n%s:", taskHelper.getMessage("suggested.providers.header"));
 610             printProviders(log, msg, finder.findAll(), uses(mrefs));
 611 
 612         } else {
 613             // comma-separated service types, if specified
 614             Set<String> names = Stream.of(args.get(0).split(","))
 615                 .collect(Collectors.toSet());
 616             // find the modules that provide the specified service
 617             Set<ModuleReference> mrefs = finder.findAll().stream()
 618                 .filter(mref -> mref.descriptor().provides().stream()
 619                                     .map(ModuleDescriptor.Provides::service)
 620                                     .anyMatch(names::contains))
 621                 .collect(Collectors.toSet());
 622 
 623             // the specified services may or may not be in the modules that
 624             // would be linked in.  So find uses declared in all observable modules
 625             Map<String, Set<String>> uses = uses(finder.findAll());
 626 
 627             // check if any name given on the command line are unused service
 628             mrefs.stream()
 629                  .flatMap(mref -> mref.descriptor().provides().stream()
 630                                       .map(ModuleDescriptor.Provides::service))
 631                  .forEach(names::remove);
 632             if (!names.isEmpty()) {
 633                 log.println(taskHelper.getMessage("warn.unused.services",
 634                                                   toString(names)));
 635             }
 636 
 637             String msg = String.format("%n%s:", taskHelper.getMessage("suggested.providers.header"));
 638             printProviders(log, msg, mrefs, uses);
 639         }
 640     }
 641 
 642     private static String toString(Collection<String> collection) {
 643         return collection.stream().sorted()
 644                          .collect(Collectors.joining(","));
 645     }
 646 
 647     private String getSaveOpts() {
 648         StringBuilder sb = new StringBuilder();
 649         sb.append('#').append(new Date()).append("\n");
 650         for (String c : optionsHelper.getInputCommand()) {
 651             sb.append(c).append(" ");
 652         }
 653 
 654         return sb.toString();
 655     }
 656 
 657     private static String getBomHeader() {
 658         StringBuilder sb = new StringBuilder();
 659         sb.append("#").append(new Date()).append("\n");
 660         sb.append("#Please DO NOT Modify this file").append("\n");
 661         return sb.toString();
 662     }
 663 
 664     private String genBOMContent() throws IOException {
 665         StringBuilder sb = new StringBuilder();
 666         sb.append(getBomHeader());
 667         StringBuilder command = new StringBuilder();
 668         for (String c : optionsHelper.getInputCommand()) {
 669             command.append(c).append(" ");
 670         }
 671         sb.append("command").append(" = ").append(command);
 672         sb.append("\n");
 673 
 674         return sb.toString();
 675     }
 676 
 677     private static String genBOMContent(JlinkConfiguration config,
 678             PluginsConfiguration plugins)
 679             throws IOException {
 680         StringBuilder sb = new StringBuilder();
 681         sb.append(getBomHeader());
 682         sb.append(config);
 683         sb.append(plugins);
 684         return sb.toString();
 685     }
 686 
 687     private static class ImageHelper implements ImageProvider {
 688         final ByteOrder order;
 689         final Path packagedModulesPath;
 690         final boolean ignoreSigning;
 691         final Set<Archive> archives;
 692 
 693         ImageHelper(Configuration cf,
 694                     Map<String, Path> modsPaths,
 695                     ByteOrder order,
 696                     Path packagedModulesPath,
 697                     boolean ignoreSigning) throws IOException {
 698             this.order = order;
 699             this.packagedModulesPath = packagedModulesPath;
 700             this.ignoreSigning = ignoreSigning;
 701             this.archives = modsPaths.entrySet().stream()
 702                                 .map(e -> newArchive(e.getKey(), e.getValue()))
 703                                 .collect(Collectors.toSet());
 704         }
 705 
 706         private Archive newArchive(String module, Path path) {
 707             if (path.toString().endsWith(".jmod")) {
 708                 return new JmodArchive(module, path);
 709             } else if (path.toString().endsWith(".jar")) {
 710                 ModularJarArchive modularJarArchive = new ModularJarArchive(module, path);
 711 
 712                 Stream<Archive.Entry> signatures = modularJarArchive.entries().filter((entry) -> {
 713                     String name = entry.name().toUpperCase(Locale.ENGLISH);
 714 
 715                     return name.startsWith("META-INF/") && name.indexOf('/', 9) == -1 && (
 716                                 name.endsWith(".SF") ||
 717                                 name.endsWith(".DSA") ||
 718                                 name.endsWith(".RSA") ||
 719                                 name.endsWith(".EC") ||
 720                                 name.startsWith("META-INF/SIG-")
 721                             );
 722                 });
 723 
 724                 if (signatures.count() != 0) {
 725                     if (ignoreSigning) {
 726                         System.err.println(taskHelper.getMessage("warn.signing", path));
 727                     } else {
 728                         throw new IllegalArgumentException(taskHelper.getMessage("err.signing", path));
 729                     }
 730                 }
 731 
 732                 return modularJarArchive;
 733             } else if (Files.isDirectory(path)) {
 734                 return new DirArchive(path);
 735             } else {
 736                 throw new IllegalArgumentException(
 737                     taskHelper.getMessage("err.not.modular.format", module, path));
 738             }
 739         }
 740 
 741         @Override
 742         public ExecutableImage retrieve(ImagePluginStack stack) throws IOException {
 743             ExecutableImage image = ImageFileCreator.create(archives, order, stack);
 744             if (packagedModulesPath != null) {
 745                 // copy the packaged modules to the given path
 746                 Files.createDirectories(packagedModulesPath);
 747                 for (Archive a : archives) {
 748                     Path file = a.getPath();
 749                     Path dest = packagedModulesPath.resolve(file.getFileName());
 750                     Files.copy(file, dest);
 751                 }
 752             }
 753             return image;
 754         }
 755     }
 756 }