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