1 /*
   2  * Copyright (c) 2015, 2016, 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.lang.module.Configuration;
  31 import java.lang.module.ModuleFinder;
  32 import java.lang.reflect.Layer;
  33 import java.nio.file.Files;
  34 import java.nio.file.Path;
  35 import java.nio.file.Paths;
  36 import java.text.MessageFormat;
  37 import java.util.ArrayList;
  38 import java.util.Arrays;
  39 import java.util.Collections;
  40 import java.util.HashMap;
  41 import java.util.HashSet;
  42 import java.util.List;
  43 import java.util.Locale;
  44 import java.util.Map;
  45 import java.util.Map.Entry;
  46 import java.util.MissingResourceException;
  47 import java.util.ResourceBundle;
  48 import java.util.Set;
  49 
  50 import jdk.internal.module.ConfigurableModuleFinder;
  51 import jdk.internal.module.ConfigurableModuleFinder.Phase;
  52 import jdk.tools.jlink.Jlink;
  53 import jdk.tools.jlink.Jlink.PluginsConfiguration;
  54 import jdk.tools.jlink.internal.plugins.ExcludeFilesPlugin;
  55 import jdk.tools.jlink.internal.plugins.ExcludeJmodSectionPlugin;
  56 import jdk.tools.jlink.plugin.Plugin;
  57 import jdk.tools.jlink.plugin.Plugin.Category;
  58 import jdk.tools.jlink.builder.DefaultImageBuilder;
  59 import jdk.tools.jlink.builder.ImageBuilder;
  60 import jdk.tools.jlink.plugin.PluginException;
  61 import jdk.tools.jlink.internal.plugins.PluginsResourceBundle;
  62 import jdk.tools.jlink.internal.plugins.DefaultCompressPlugin;
  63 import jdk.tools.jlink.internal.plugins.StripDebugPlugin;
  64 
  65 /**
  66  *
  67  * JLink and JImage tools shared helper.
  68  */
  69 public final class TaskHelper {
  70 
  71     public static final String JLINK_BUNDLE = "jdk.tools.jlink.resources.jlink";
  72     public static final String JIMAGE_BUNDLE = "jdk.tools.jimage.resources.jimage";
  73 
  74     private static final String DEFAULTS_PROPERTY = "jdk.jlink.defaults";
  75 
  76     public final class BadArgs extends Exception {
  77 
  78         static final long serialVersionUID = 8765093759964640721L;
  79 
  80         private BadArgs(String key, Object... args) {
  81             super(bundleHelper.getMessage(key, args));
  82             this.key = key;
  83             this.args = args;
  84         }
  85 
  86         public BadArgs showUsage(boolean b) {
  87             showUsage = b;
  88             return this;
  89         }
  90         public final String key;
  91         public final Object[] args;
  92         public boolean showUsage;
  93     }
  94 
  95     public static class Option<T> {
  96 
  97         public interface Processing<T> {
  98 
  99             void process(T task, String opt, String arg) throws BadArgs;
 100         }
 101 
 102         final boolean hasArg;
 103         final Processing<T> processing;
 104         final boolean hidden;
 105         final String[] aliases;
 106 
 107         public Option(boolean hasArg, Processing<T> processing, boolean hidden, String... aliases) {
 108             this.hasArg = hasArg;
 109             this.processing = processing;
 110             this.aliases = aliases;
 111             this.hidden = hidden;
 112         }
 113 
 114         public Option(boolean hasArg, Processing<T> processing, String... aliases) {
 115             this(hasArg, processing, false, aliases);
 116         }
 117 
 118         public boolean isHidden() {
 119             return hidden;
 120         }
 121 
 122         public boolean matches(String opt) {
 123             for (String a : aliases) {
 124                 if (a.equals(opt)) {
 125                     return true;
 126                 } else if (opt.startsWith("--")
 127                         && (hasArg && opt.startsWith(a + "="))) {
 128                     return true;
 129                 }
 130             }
 131             return false;
 132         }
 133 
 134         public boolean ignoreRest() {
 135             return false;
 136         }
 137 
 138         void process(T task, String opt, String arg) throws BadArgs {
 139             processing.process(task, opt, arg);
 140         }
 141 
 142         public String[] aliases() {
 143             return aliases;
 144         }
 145     }
 146 
 147     private static class PlugOption extends Option<PluginsOptions> {
 148 
 149         public PlugOption(boolean hasArg,
 150                 Processing<PluginsOptions> processing, boolean hidden, String... aliases) {
 151             super(hasArg, processing, hidden, aliases);
 152         }
 153 
 154         public PlugOption(boolean hasArg,
 155                 Processing<PluginsOptions> processing, String... aliases) {
 156             super(hasArg, processing, aliases);
 157         }
 158     }
 159 
 160     private final class PluginsOptions {
 161 
 162         private static final String PLUGINS_PATH = "--plugin-module-path";
 163         private static final String POST_PROCESS = "--post-process-path";
 164 
 165         private Layer pluginsLayer = Layer.boot();
 166         private String lastSorter;
 167         private boolean listPlugins;
 168         private Path existingImage;
 169 
 170         // plugin to args maps. Each plugin may be used more than once in command line.
 171         // Each such occurrence results in a Map of arguments. So, there could be multiple
 172         // args maps per plugin instance.
 173         private final Map<Plugin, List<Map<String, String>>> pluginToMaps = new HashMap<>();
 174         private final List<PlugOption> pluginsOptions = new ArrayList<>();
 175         private final List<PlugOption> mainOptions = new ArrayList<>();
 176 
 177         private PluginsOptions(String pp) throws BadArgs {
 178 
 179             if (pp != null) {
 180                 String[] dirs = pp.split(File.pathSeparator);
 181                 List<Path> paths = new ArrayList<>(dirs.length);
 182                 for (String dir : dirs) {
 183                     paths.add(Paths.get(dir));
 184                 }
 185 
 186                 pluginsLayer = createPluginsLayer(paths);
 187             }
 188 
 189             Set<String> optionsSeen = new HashSet<>();
 190             for (Plugin plugin : PluginRepository.
 191                     getPlugins(pluginsLayer)) {
 192                 if (!Utils.isDisabled(plugin)) {
 193                     addOrderedPluginOptions(plugin, optionsSeen);
 194                 }
 195             }
 196             mainOptions.add(new PlugOption(false,
 197                     (task, opt, arg) -> {
 198                         // This option is handled prior
 199                         // to have the options parsed.
 200                     },
 201                     "--plugin-module-path"));
 202             mainOptions.add(new PlugOption(true, (task, opt, arg) -> {
 203                 Path path = Paths.get(arg);
 204                 if (!Files.exists(path) || !Files.isDirectory(path)) {
 205                     throw newBadArgs("err.existing.image.must.exist");
 206                 }
 207                 existingImage = path.toAbsolutePath();
 208             }, true, POST_PROCESS));
 209             mainOptions.add(new PlugOption(true,
 210                     (task, opt, arg) -> {
 211                         lastSorter = arg;
 212                     },
 213                     true, "--resources-last-sorter"));
 214             mainOptions.add(new PlugOption(false,
 215                     (task, opt, arg) -> {
 216                         listPlugins = true;
 217                     },
 218                     "--list-plugins"));
 219         }
 220 
 221         private List<Map<String, String>> argListFor(Plugin plugin) {
 222             List<Map<String, String>> mapList = pluginToMaps.get(plugin);
 223             if (mapList == null) {
 224                 mapList = new ArrayList<>();
 225                 pluginToMaps.put(plugin, mapList);
 226             }
 227             return mapList;
 228         }
 229 
 230         private void addEmptyArgumentMap(Plugin plugin) {
 231             argListFor(plugin).add(Collections.emptyMap());
 232         }
 233 
 234         private Map<String, String> addArgumentMap(Plugin plugin) {
 235             Map<String, String> map = new HashMap<>();
 236             argListFor(plugin).add(map);
 237             return map;
 238         }
 239 
 240         private void addOrderedPluginOptions(Plugin plugin,
 241             Set<String> optionsSeen) throws BadArgs {
 242             String option = plugin.getOption();
 243             if (option == null) {
 244                 return;
 245             }
 246 
 247             // make sure that more than one plugin does not use the same option!
 248             if (optionsSeen.contains(option)) {
 249                 throw new BadArgs("err.plugin.mutiple.options",
 250                         option);
 251             }
 252             optionsSeen.add(option);
 253 
 254             PlugOption plugOption
 255                     = new PlugOption(plugin.hasArguments(),
 256                             (task, opt, arg) -> {
 257                                 if (!Utils.isFunctional(plugin)) {
 258                                     throw newBadArgs("err.provider.not.functional",
 259                                             option);
 260                                 }
 261 
 262                                 if (! plugin.hasArguments()) {
 263                                     addEmptyArgumentMap(plugin);
 264                                     return;
 265                                 }
 266 
 267                                 Map<String, String> m = addArgumentMap(plugin);
 268                                 // handle one or more arguments
 269                                 if (arg.indexOf(':') == -1) {
 270                                     // single argument case
 271                                     m.put(option, arg);
 272                                 } else {
 273                                     // This option can accept more than one arguments
 274                                     // like --option_name=arg_value:arg2=value2:arg3=value3
 275 
 276                                     // ":" followed by word char condition takes care of args that
 277                                     // like Windows absolute paths "C:\foo", "C:/foo" [cygwin] etc.
 278                                     // This enforces that key names start with a word character.
 279                                     String[] args = arg.split(":(?=\\w)", -1);
 280                                     String firstArg = args[0];
 281                                     if (firstArg.isEmpty()) {
 282                                         throw newBadArgs("err.provider.additional.arg.error",
 283                                             option, arg);
 284                                     }
 285                                     m.put(option, firstArg);
 286                                     // process the additional arguments
 287                                     for (int i = 1; i < args.length; i++) {
 288                                         String addArg = args[i];
 289                                         int eqIdx = addArg.indexOf('=');
 290                                         if (eqIdx == -1) {
 291                                             throw newBadArgs("err.provider.additional.arg.error",
 292                                                 option, arg);
 293                                         }
 294 
 295                                         String addArgName = addArg.substring(0, eqIdx);
 296                                         String addArgValue = addArg.substring(eqIdx+1);
 297                                         if (addArgName.isEmpty() || addArgValue.isEmpty()) {
 298                                             throw newBadArgs("err.provider.additional.arg.error",
 299                                                 option, arg);
 300                                         }
 301                                         m.put(addArgName, addArgValue);
 302                                     }
 303                                 }
 304                             },
 305                             "--" + option);
 306             pluginsOptions.add(plugOption);
 307 
 308             if (Utils.isFunctional(plugin)) {
 309                 if (Utils.isAutoEnabled(plugin)) {
 310                     addEmptyArgumentMap(plugin);
 311                 }
 312 
 313                 if (plugin instanceof DefaultCompressPlugin) {
 314                     plugOption
 315                         = new PlugOption(false,
 316                             (task, opt, arg) -> {
 317                                 Map<String, String> m = addArgumentMap(plugin);
 318                                 m.put(DefaultCompressPlugin.NAME, DefaultCompressPlugin.LEVEL_2);
 319                             }, "-c");
 320                     mainOptions.add(plugOption);
 321                 } else if (plugin instanceof StripDebugPlugin) {
 322                     plugOption
 323                         = new PlugOption(false,
 324                             (task, opt, arg) -> {
 325                                 addArgumentMap(plugin);
 326                             }, "-G");
 327                     mainOptions.add(plugOption);
 328                 } else if (plugin instanceof ExcludeJmodSectionPlugin) {
 329                     plugOption = new PlugOption(false, (task, opt, arg) -> {
 330                             Map<String, String> m = addArgumentMap(plugin);
 331                             m.put(ExcludeJmodSectionPlugin.NAME,
 332                                   ExcludeJmodSectionPlugin.MAN_PAGES);
 333                         }, "--no-man-pages");
 334                     mainOptions.add(plugOption);
 335 
 336                     plugOption = new PlugOption(false, (task, opt, arg) -> {
 337                         Map<String, String> m = addArgumentMap(plugin);
 338                         m.put(ExcludeJmodSectionPlugin.NAME,
 339                               ExcludeJmodSectionPlugin.INCLUDE_HEADER_FILES);
 340                     }, "--no-header-files");
 341                     mainOptions.add(plugOption);
 342                 }
 343             }
 344         }
 345 
 346         private PlugOption getOption(String name) throws BadArgs {
 347             for (PlugOption o : pluginsOptions) {
 348                 if (o.matches(name)) {
 349                     return o;
 350                 }
 351             }
 352             for (PlugOption o : mainOptions) {
 353                 if (o.matches(name)) {
 354                     return o;
 355                 }
 356             }
 357             return null;
 358         }
 359 
 360         private PluginsConfiguration getPluginsConfig(Path output
 361                     ) throws IOException, BadArgs {
 362             if (output != null) {
 363                 if (Files.exists(output)) {
 364                     throw new PluginException(PluginsResourceBundle.
 365                             getMessage("err.dir.already.exits", output));
 366                 }
 367             }
 368 
 369             List<Plugin> pluginsList = new ArrayList<>();
 370             for (Entry<Plugin, List<Map<String, String>>> entry : pluginToMaps.entrySet()) {
 371                 Plugin plugin = entry.getKey();
 372                 List<Map<String, String>> argsMaps = entry.getValue();
 373 
 374                 // same plugin option may be used multiple times in command line.
 375                 // we call configure once for each occurrence. It is upto the plugin
 376                 // to 'merge' and/or 'override' arguments.
 377                 for (Map<String, String> map : argsMaps) {
 378                     plugin.configure(Collections.unmodifiableMap(map));
 379                 }
 380 
 381                 if (!Utils.isDisabled(plugin)) {
 382                     pluginsList.add(plugin);
 383                 }
 384             }
 385 
 386             // recreate or postprocessing don't require an output directory.
 387             ImageBuilder builder = null;
 388             if (output != null) {
 389                 builder = new DefaultImageBuilder(output);
 390 
 391             }
 392             return new Jlink.PluginsConfiguration(pluginsList,
 393                     builder, lastSorter);
 394         }
 395     }
 396 
 397     private static final class ResourceBundleHelper {
 398 
 399         private final ResourceBundle bundle;
 400         private final ResourceBundle pluginBundle;
 401 
 402         ResourceBundleHelper(String path) {
 403             Locale locale = Locale.getDefault();
 404             try {
 405                 bundle = ResourceBundle.getBundle(path, locale);
 406                 pluginBundle = ResourceBundle.getBundle("jdk.tools.jlink.resources.plugins", locale);
 407             } catch (MissingResourceException e) {
 408                 throw new InternalError("Cannot find jlink resource bundle for locale " + locale);
 409             }
 410         }
 411 
 412         String getMessage(String key, Object... args) {
 413             String val;
 414             try {
 415                 val = bundle.getString(key);
 416             } catch (MissingResourceException e) {
 417                 // XXX OK, check in plugin bundle
 418                 val = pluginBundle.getString(key);
 419             }
 420             return MessageFormat.format(val, args);
 421         }
 422 
 423     }
 424 
 425     public final class OptionsHelper<T> {
 426 
 427         private final List<Option<T>> options;
 428         private String[] command;
 429         private String defaults;
 430 
 431         OptionsHelper(List<Option<T>> options) {
 432             this.options = options;
 433         }
 434 
 435         private boolean hasArgument(String optionName) throws BadArgs {
 436             Option<?> opt = getOption(optionName);
 437             if (opt == null) {
 438                 opt = pluginOptions.getOption(optionName);
 439                 if (opt == null) {
 440                     throw new BadArgs("err.unknown.option", optionName).
 441                             showUsage(true);
 442                 }
 443             }
 444             return opt.hasArg;
 445         }
 446 
 447         public boolean shouldListPlugins() {
 448             return pluginOptions.listPlugins;
 449         }
 450 
 451         private String getPluginsPath(String[] args) throws BadArgs {
 452             String pp = null;
 453             for (int i = 0; i < args.length; i++) {
 454                 if (args[i].equals(PluginsOptions.PLUGINS_PATH)) {
 455                     if (i == args.length - 1) {
 456                         throw new BadArgs("err.no.plugins.path").showUsage(true);
 457                     } else {
 458                         i += 1;
 459                         pp = args[i];
 460                         if (!pp.isEmpty() && pp.charAt(0) == '-') {
 461                             throw new BadArgs("err.no.plugins.path").showUsage(true);
 462                         }
 463                         break;
 464                     }
 465                 }
 466             }
 467             return pp;
 468         }
 469 
 470         public List<String> handleOptions(T task, String[] args) throws BadArgs {
 471             // findbugs warning, copy instead of keeping a reference.
 472             command = Arrays.copyOf(args, args.length);
 473 
 474             // Must extract it prior to do any option analysis.
 475             // Required to interpret custom plugin options.
 476             // Unit tests can call Task multiple time in same JVM.
 477             pluginOptions = new PluginsOptions(getPluginsPath(args));
 478 
 479             // First extract plugins path if any
 480             String pp = null;
 481             List<String> filteredArgs = new ArrayList<>();
 482             for (int i = 0; i < args.length; i++) {
 483                 if (args[i].equals(PluginsOptions.PLUGINS_PATH)) {
 484                     if (i == args.length - 1) {
 485                         throw new BadArgs("err.no.plugins.path").showUsage(true);
 486                     } else {
 487                         warning("warn.thirdparty.plugins.enabled");
 488                         log.println(bundleHelper.getMessage("warn.thirdparty.plugins"));
 489                         i += 1;
 490                         String arg = args[i];
 491                         if (!arg.isEmpty() && arg.charAt(0) == '-') {
 492                             throw new BadArgs("err.no.plugins.path").showUsage(true);
 493                         }
 494                         pp = args[i];
 495                     }
 496                 } else {
 497                     filteredArgs.add(args[i]);
 498                 }
 499             }
 500             String[] arr = new String[filteredArgs.size()];
 501             args = filteredArgs.toArray(arr);
 502 
 503             List<String> rest = new ArrayList<>();
 504             // process options
 505             for (int i = 0; i < args.length; i++) {
 506                 if (!args[i].isEmpty() && args[i].charAt(0) == '-') {
 507                     String name = args[i];
 508                     PlugOption pluginOption = null;
 509                     Option<T> option = getOption(name);
 510                     if (option == null) {
 511                         pluginOption = pluginOptions.getOption(name);
 512                         if (pluginOption == null) {
 513 
 514                             throw new BadArgs("err.unknown.option", name).
 515                                     showUsage(true);
 516                         }
 517                     }
 518                     Option<?> opt = pluginOption == null ? option : pluginOption;
 519                     String param = null;
 520                     if (opt.hasArg) {
 521                         if (name.startsWith("--") && name.indexOf('=') > 0) {
 522                             param = name.substring(name.indexOf('=') + 1,
 523                                     name.length());
 524                         } else if (i + 1 < args.length) {
 525                             param = args[++i];
 526                         }
 527                         if (param == null || param.isEmpty()
 528                                 || (param.length() >= 2 && param.charAt(0) == '-'
 529                                 && param.charAt(1) == '-')) {
 530                             throw new BadArgs("err.missing.arg", name).
 531                                     showUsage(true);
 532                         }
 533                     }
 534                     if (pluginOption != null) {
 535                         pluginOption.process(pluginOptions, name, param);
 536                     } else {
 537                         option.process(task, name, param);
 538                     }
 539                     if (opt.ignoreRest()) {
 540                         i = args.length;
 541                     }
 542                 } else {
 543                     rest.add(args[i]);
 544                 }
 545             }
 546             return rest;
 547         }
 548 
 549         private Option<T> getOption(String name) {
 550             for (Option<T> o : options) {
 551                 if (o.matches(name)) {
 552                     return o;
 553                 }
 554             }
 555             return null;
 556         }
 557 
 558         public void showHelp(String progName) {
 559             showHelp(progName, true);
 560         }
 561 
 562         private void showHelp(String progName, boolean showsImageBuilder) {
 563             log.println(bundleHelper.getMessage("main.usage", progName));
 564             for (Option<?> o : options) {
 565                 String name = o.aliases[0].substring(1); // there must always be at least one name
 566                 name = name.charAt(0) == '-' ? name.substring(1) : name;
 567                 if (o.isHidden() || name.equals("h")) {
 568                     continue;
 569                 }
 570                 log.println(bundleHelper.getMessage("main.opt." + name));
 571             }
 572 
 573             for (Option<?> o : pluginOptions.mainOptions) {
 574                 if (o.aliases[0].equals(PluginsOptions.POST_PROCESS)
 575                         && !showsImageBuilder) {
 576                     continue;
 577                 }
 578                 String name = o.aliases[0].substring(1); // there must always be at least one name
 579                 name = name.charAt(0) == '-' ? name.substring(1) : name;
 580                 if (o.isHidden()) {
 581                     continue;
 582                 }
 583                 log.println(bundleHelper.getMessage("plugin.opt." + name));
 584             }
 585 
 586             log.println(bundleHelper.getMessage("main.command.files"));
 587         }
 588 
 589         public void listPlugins() {
 590             log.println("\n" + bundleHelper.getMessage("main.extended.help"));
 591             List<Plugin> pluginList = PluginRepository.
 592                     getPlugins(pluginOptions.pluginsLayer);
 593             for (Plugin plugin : Utils.getSortedPlugins(pluginList)) {
 594                 showPlugin(plugin, log);
 595             }
 596 
 597             log.println("\n" + bundleHelper.getMessage("main.extended.help.footer"));
 598         }
 599 
 600         private void showPlugin(Plugin plugin, PrintWriter log) {
 601             if (showsPlugin(plugin)) {
 602                 log.println("\n" + bundleHelper.getMessage("main.plugin.name")
 603                         + ": " + plugin.getName());
 604 
 605                 // print verbose details for non-builtin plugins
 606                 if (!Utils.isBuiltin(plugin)) {
 607                     log.println(bundleHelper.getMessage("main.plugin.class")
 608                          + ": " + plugin.getClass().getName());
 609                     log.println(bundleHelper.getMessage("main.plugin.module")
 610                          + ": " + plugin.getClass().getModule().getName());
 611                     Category category = plugin.getType();
 612                     log.println(bundleHelper.getMessage("main.plugin.category")
 613                          + ": " + category.getName());
 614                     log.println(bundleHelper.getMessage("main.plugin.state")
 615                         + ": " + plugin.getStateDescription());
 616                 }
 617 
 618                 String option = plugin.getOption();
 619                 if (option != null) {
 620                     log.println(bundleHelper.getMessage("main.plugin.option")
 621                         + ": --" + plugin.getOption()
 622                         + (plugin.hasArguments()? ("=" + plugin.getArgumentsDescription()) : ""));
 623                 }
 624 
 625                 // description can be long spanning more than one line and so
 626                 // print a newline after description label.
 627                 log.println(bundleHelper.getMessage("main.plugin.description")
 628                         + ": " + plugin.getDescription());
 629             }
 630         }
 631 
 632         String[] getInputCommand() {
 633             return command;
 634         }
 635 
 636         String getDefaults() {
 637             return defaults;
 638         }
 639 
 640         public Layer getPluginsLayer() {
 641             return pluginOptions.pluginsLayer;
 642         }
 643     }
 644 
 645     private PluginsOptions pluginOptions;
 646     private PrintWriter log;
 647     private final ResourceBundleHelper bundleHelper;
 648 
 649     public TaskHelper(String path) {
 650         if (!JLINK_BUNDLE.equals(path) && !JIMAGE_BUNDLE.equals(path)) {
 651             throw new IllegalArgumentException("Invalid Bundle");
 652         }
 653         this.bundleHelper = new ResourceBundleHelper(path);
 654     }
 655 
 656     public <T> OptionsHelper<T> newOptionsHelper(Class<T> clazz,
 657             Option<?>[] options) {
 658         List<Option<T>> optionsList = new ArrayList<>();
 659         for (Option<?> o : options) {
 660             @SuppressWarnings("unchecked")
 661             Option<T> opt = (Option<T>) o;
 662             optionsList.add(opt);
 663         }
 664         return new OptionsHelper<>(optionsList);
 665     }
 666 
 667     public BadArgs newBadArgs(String key, Object... args) {
 668         return new BadArgs(key, args);
 669     }
 670 
 671     public String getMessage(String key, Object... args) {
 672         return bundleHelper.getMessage(key, args);
 673     }
 674 
 675     public void setLog(PrintWriter log) {
 676         this.log = log;
 677     }
 678 
 679     public void reportError(String key, Object... args) {
 680         log.println(bundleHelper.getMessage("error.prefix") + " "
 681                 + bundleHelper.getMessage(key, args));
 682     }
 683 
 684     public void reportUnknownError(String message) {
 685         log.println(bundleHelper.getMessage("error.prefix") + " " + message);
 686     }
 687 
 688     public void warning(String key, Object... args) {
 689         log.println(bundleHelper.getMessage("warn.prefix") + " "
 690                 + bundleHelper.getMessage(key, args));
 691     }
 692 
 693     public PluginsConfiguration getPluginsConfig(Path output)
 694             throws IOException, BadArgs {
 695         return pluginOptions.getPluginsConfig(output);
 696     }
 697 
 698     public Path getExistingImage() {
 699         return pluginOptions.existingImage;
 700     }
 701 
 702     public void showVersion(boolean full) {
 703         log.println(version(full ? "full" : "release"));
 704     }
 705 
 706     public String version(String key) {
 707         return System.getProperty("java.version");
 708     }
 709 
 710     static Layer createPluginsLayer(List<Path> paths) {
 711         Path[] arr = new Path[paths.size()];
 712         paths.toArray(arr);
 713         ModuleFinder finder = ModuleFinder.of(arr);
 714 
 715         // jmods are located at link-time
 716         if (finder instanceof ConfigurableModuleFinder) {
 717             ((ConfigurableModuleFinder) finder).configurePhase(Phase.LINK_TIME);
 718         }
 719 
 720         Configuration bootConfiguration = Layer.boot().configuration();
 721         try {
 722             Configuration cf = bootConfiguration
 723                 .resolveRequiresAndUses(ModuleFinder.of(),
 724                                         finder,
 725                                         Collections.emptySet());
 726             ClassLoader scl = ClassLoader.getSystemClassLoader();
 727             return Layer.boot().defineModulesWithOneLoader(cf, scl);
 728         } catch (Exception ex) {
 729             // Malformed plugin modules (e.g.: same package in multiple modules).
 730             throw new PluginException("Invalid modules in the plugins path: " + ex);
 731         }
 732     }
 733 
 734     // Display all plugins
 735     private static boolean showsPlugin(Plugin plugin) {
 736         return (!Utils.isDisabled(plugin) && plugin.getOption() != null);
 737     }
 738 }