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