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                     "--plugins-modulepath"));
 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                 }
 310 
 311                 if (plugin instanceof DefaultCompressPlugin) {
 312                     plugOption
 313                         = new PlugOption(false,
 314                             (task, opt, arg) -> {
 315                                 Map<String, String> m = addArgumentMap(plugin);
 316                                 m.put(DefaultCompressPlugin.NAME, DefaultCompressPlugin.LEVEL_2);
 317                             }, "-c");
 318                     mainOptions.add(plugOption);
 319                 } else if (plugin instanceof StripDebugPlugin) {
 320                     plugOption
 321                         = new PlugOption(false,
 322                             (task, opt, arg) -> {
 323                                 addArgumentMap(plugin);
 324                             }, "-G");
 325                     mainOptions.add(plugOption);
 326                 }
 327             }
 328         }
 329 
 330         private PlugOption getOption(String name) throws BadArgs {
 331             for (PlugOption o : pluginsOptions) {
 332                 if (o.matches(name)) {
 333                     return o;
 334                 }
 335             }
 336             for (PlugOption o : mainOptions) {
 337                 if (o.matches(name)) {
 338                     return o;
 339                 }
 340             }
 341             return null;
 342         }
 343 
 344         private PluginsConfiguration getPluginsConfig(Path output
 345                     ) throws IOException, BadArgs {
 346             if (output != null) {
 347                 if (Files.exists(output)) {
 348                     throw new PluginException(PluginsResourceBundle.
 349                             getMessage("err.dir.already.exits", output));
 350                 }
 351             }
 352 
 353             List<Plugin> pluginsList = new ArrayList<>();
 354             for (Entry<Plugin, List<Map<String, String>>> entry : pluginToMaps.entrySet()) {
 355                 Plugin plugin = entry.getKey();
 356                 List<Map<String, String>> argsMaps = entry.getValue();
 357 
 358                 // same plugin option may be used multiple times in command line.
 359                 // we call configure once for each occurrence. It is upto the plugin
 360                 // to 'merge' and/or 'override' arguments.
 361                 for (Map<String, String> map : argsMaps) {
 362                     plugin.configure(Collections.unmodifiableMap(map));
 363                 }
 364 
 365                 if (!Utils.isDisabled(plugin)) {
 366                     pluginsList.add(plugin);
 367                 }
 368             }
 369 
 370             // recreate or postprocessing don't require an output directory.
 371             ImageBuilder builder = null;
 372             if (output != null) {
 373                 builder = new DefaultImageBuilder(output);
 374 
 375             }
 376             return new Jlink.PluginsConfiguration(pluginsList,
 377                     builder, lastSorter);
 378         }
 379     }
 380 
 381     private static final class ResourceBundleHelper {
 382 
 383         private final ResourceBundle bundle;
 384         private final ResourceBundle pluginBundle;
 385 
 386         ResourceBundleHelper(String path) {
 387             Locale locale = Locale.getDefault();
 388             try {
 389                 bundle = ResourceBundle.getBundle(path, locale);
 390                 pluginBundle = ResourceBundle.getBundle("jdk.tools.jlink.resources.plugins", locale);
 391             } catch (MissingResourceException e) {
 392                 throw new InternalError("Cannot find jlink resource bundle for locale " + locale);
 393             }
 394         }
 395 
 396         String getMessage(String key, Object... args) {
 397             String val;
 398             try {
 399                 val = bundle.getString(key);
 400             } catch (MissingResourceException e) {
 401                 // XXX OK, check in plugin bundle
 402                 val = pluginBundle.getString(key);
 403             }
 404             return MessageFormat.format(val, args);
 405         }
 406 
 407     }
 408 
 409     public final class OptionsHelper<T> {
 410 
 411         private final List<Option<T>> options;
 412         private String[] command;
 413         private String defaults;
 414 
 415         OptionsHelper(List<Option<T>> options) {
 416             this.options = options;
 417         }
 418 
 419         private boolean hasArgument(String optionName) throws BadArgs {
 420             Option<?> opt = getOption(optionName);
 421             if (opt == null) {
 422                 opt = pluginOptions.getOption(optionName);
 423                 if (opt == null) {
 424                     throw new BadArgs("err.unknown.option", optionName).
 425                             showUsage(true);
 426                 }
 427             }
 428             return opt.hasArg;
 429         }
 430 
 431         public boolean listPlugins() {
 432             return pluginOptions.listPlugins;
 433         }
 434 
 435         private String getPluginsPath(String[] args) throws BadArgs {
 436             String pp = null;
 437             for (int i = 0; i < args.length; i++) {
 438                 if (args[i].equals(PluginsOptions.PLUGINS_PATH)) {
 439                     if (i == args.length - 1) {
 440                         throw new BadArgs("err.no.plugins.path").showUsage(true);
 441                     } else {
 442                         i += 1;
 443                         pp = args[i];
 444                         if (!pp.isEmpty() && pp.charAt(0) == '-') {
 445                             throw new BadArgs("err.no.plugins.path").showUsage(true);
 446                         }
 447                         break;
 448                     }
 449                 }
 450             }
 451             return pp;
 452         }
 453 
 454         public List<String> handleOptions(T task, String[] args) throws BadArgs {
 455             // findbugs warning, copy instead of keeping a reference.
 456             command = Arrays.copyOf(args, args.length);
 457 
 458             // Must extract it prior to do any option analysis.
 459             // Required to interpret custom plugin options.
 460             // Unit tests can call Task multiple time in same JVM.
 461             pluginOptions = new PluginsOptions(getPluginsPath(args));
 462 
 463             // First extract plugins path if any
 464             String pp = null;
 465             List<String> filteredArgs = new ArrayList<>();
 466             for (int i = 0; i < args.length; i++) {
 467                 if (args[i].equals(PluginsOptions.PLUGINS_PATH)) {
 468                     if (i == args.length - 1) {
 469                         throw new BadArgs("err.no.plugins.path").showUsage(true);
 470                     } else {
 471                         warning("warn.thirdparty.plugins.enabled");
 472                         log.println(bundleHelper.getMessage("warn.thirdparty.plugins"));
 473                         i += 1;
 474                         String arg = args[i];
 475                         if (!arg.isEmpty() && arg.charAt(0) == '-') {
 476                             throw new BadArgs("err.no.plugins.path").showUsage(true);
 477                         }
 478                         pp = args[i];
 479                     }
 480                 } else {
 481                     filteredArgs.add(args[i]);
 482                 }
 483             }
 484             String[] arr = new String[filteredArgs.size()];
 485             args = filteredArgs.toArray(arr);
 486 
 487             List<String> rest = new ArrayList<>();
 488             // process options
 489             for (int i = 0; i < args.length; i++) {
 490                 if (!args[i].isEmpty() && args[i].charAt(0) == '-') {
 491                     String name = args[i];
 492                     PlugOption pluginOption = null;
 493                     Option<T> option = getOption(name);
 494                     if (option == null) {
 495                         pluginOption = pluginOptions.getOption(name);
 496                         if (pluginOption == null) {
 497 
 498                             throw new BadArgs("err.unknown.option", name).
 499                                     showUsage(true);
 500                         }
 501                     }
 502                     Option<?> opt = pluginOption == null ? option : pluginOption;
 503                     String param = null;
 504                     if (opt.hasArg) {
 505                         if (name.startsWith("--") && name.indexOf('=') > 0) {
 506                             param = name.substring(name.indexOf('=') + 1,
 507                                     name.length());
 508                         } else if (i + 1 < args.length) {
 509                             param = args[++i];
 510                         }
 511                         if (param == null || param.isEmpty()
 512                                 || (param.length() >= 2 && param.charAt(0) == '-'
 513                                 && param.charAt(1) == '-')) {
 514                             throw new BadArgs("err.missing.arg", name).
 515                                     showUsage(true);
 516                         }
 517                     }
 518                     if (pluginOption != null) {
 519                         pluginOption.process(pluginOptions, name, param);
 520                     } else {
 521                         option.process(task, name, param);
 522                     }
 523                     if (opt.ignoreRest()) {
 524                         i = args.length;
 525                     }
 526                 } else {
 527                     rest.add(args[i]);
 528                 }
 529             }
 530             return rest;
 531         }
 532 
 533         private Option<T> getOption(String name) {
 534             for (Option<T> o : options) {
 535                 if (o.matches(name)) {
 536                     return o;
 537                 }
 538             }
 539             return null;
 540         }
 541 
 542         public void showHelp(String progName) {
 543             showHelp(progName, true);
 544         }
 545 
 546         private void showHelp(String progName, boolean showsImageBuilder) {
 547             log.println(bundleHelper.getMessage("main.usage", progName));
 548             for (Option<?> o : options) {
 549                 String name = o.aliases[0].substring(1); // there must always be at least one name
 550                 name = name.charAt(0) == '-' ? name.substring(1) : name;
 551                 if (o.isHidden() || name.equals("h")) {
 552                     continue;
 553                 }
 554                 log.println(bundleHelper.getMessage("main.opt." + name));
 555             }
 556 
 557             for (Option<?> o : pluginOptions.mainOptions) {
 558                 if (o.aliases[0].equals(PluginsOptions.POST_PROCESS)
 559                         && !showsImageBuilder) {
 560                     continue;
 561                 }
 562                 String name = o.aliases[0].substring(1); // there must always be at least one name
 563                 name = name.charAt(0) == '-' ? name.substring(1) : name;
 564                 if (o.isHidden()) {
 565                     continue;
 566                 }
 567                 log.println(bundleHelper.getMessage("plugin.opt." + name));
 568             }
 569 
 570             log.println(bundleHelper.getMessage("main.command.files"));
 571         }
 572 
 573         public void listPlugins(boolean showsImageBuilder) {
 574             log.println("\n" + bundleHelper.getMessage("main.extended.help"));
 575             List<Plugin> pluginList = PluginRepository.
 576                     getPlugins(pluginOptions.pluginsLayer);
 577             for (Plugin plugin : Utils.
 578                     getSortedPreProcessors(pluginList)) {
 579                 showPlugin(plugin, log, showsImageBuilder);
 580             }
 581 
 582             if (showsImageBuilder) {
 583                 for (Plugin plugin : Utils.getSortedPostProcessors(pluginList)) {
 584                     showPlugin(plugin, log, showsImageBuilder);
 585                 }
 586             }
 587             
 588             log.println("\n" + bundleHelper.getMessage("main.extended.help.footer"));
 589         }
 590 
 591         private void showPlugin(Plugin plugin, PrintWriter log, boolean showsImageBuilder) {
 592             if (showsPlugin(plugin, showsImageBuilder)) {
 593                 log.println("\n" + bundleHelper.getMessage("main.plugin.name")
 594                         + ": " + plugin.getName());
 595 
 596                 // print verbose details for non-builtin plugins
 597                 if (!Utils.isBuiltin(plugin)) {
 598                     log.println(bundleHelper.getMessage("main.plugin.class")
 599                          + ": " + plugin.getClass().getName());
 600                     log.println(bundleHelper.getMessage("main.plugin.module")
 601                          + ": " + plugin.getClass().getModule().getName());
 602                     Category category = Utils.getCategory(plugin);
 603                     log.println(bundleHelper.getMessage("main.plugin.category")
 604                          + ": " + category.getName());
 605                     log.println(bundleHelper.getMessage("main.plugin.state")
 606                         + ": " + plugin.getStateDescription());
 607                 }
 608 
 609                 String option = plugin.getOption();
 610                 if (option != null) {
 611                     log.println(bundleHelper.getMessage("main.plugin.option")
 612                         + ": --" + plugin.getOption()
 613                         + (plugin.hasArguments()? ("=" + plugin.getArgumentsDescription()) : ""));
 614                 }
 615 
 616                 // description can be long spanning more than one line and so
 617                 // print a newline after description label.
 618                 log.println(bundleHelper.getMessage("main.plugin.description")
 619                         + ": " + plugin.getDescription());
 620             }
 621         }
 622 
 623         String[] getInputCommand() {
 624             return command;
 625         }
 626 
 627         String getDefaults() {
 628             return defaults;
 629         }
 630 
 631         public Layer getPluginsLayer() {
 632             return pluginOptions.pluginsLayer;
 633         }
 634     }
 635 
 636     private PluginsOptions pluginOptions;
 637     private PrintWriter log;
 638     private final ResourceBundleHelper bundleHelper;
 639 
 640     public TaskHelper(String path) {
 641         if (!JLINK_BUNDLE.equals(path) && !JIMAGE_BUNDLE.equals(path)) {
 642             throw new IllegalArgumentException("Invalid Bundle");
 643         }
 644         this.bundleHelper = new ResourceBundleHelper(path);
 645     }
 646 
 647     public <T> OptionsHelper<T> newOptionsHelper(Class<T> clazz,
 648             Option<?>[] options) {
 649         List<Option<T>> optionsList = new ArrayList<>();
 650         for (Option<?> o : options) {
 651             @SuppressWarnings("unchecked")
 652             Option<T> opt = (Option<T>) o;
 653             optionsList.add(opt);
 654         }
 655         return new OptionsHelper<>(optionsList);
 656     }
 657 
 658     public BadArgs newBadArgs(String key, Object... args) {
 659         return new BadArgs(key, args);
 660     }
 661 
 662     public String getMessage(String key, Object... args) {
 663         return bundleHelper.getMessage(key, args);
 664     }
 665 
 666     public void setLog(PrintWriter log) {
 667         this.log = log;
 668     }
 669 
 670     public void reportError(String key, Object... args) {
 671         log.println(bundleHelper.getMessage("error.prefix") + " "
 672                 + bundleHelper.getMessage(key, args));
 673     }
 674 
 675     public void reportUnknownError(String message) {
 676         log.println(bundleHelper.getMessage("error.prefix") + " " + message);
 677     }
 678 
 679     public void warning(String key, Object... args) {
 680         log.println(bundleHelper.getMessage("warn.prefix") + " "
 681                 + bundleHelper.getMessage(key, args));
 682     }
 683 
 684     public PluginsConfiguration getPluginsConfig(Path output)
 685             throws IOException, BadArgs {
 686         return pluginOptions.getPluginsConfig(output);
 687     }
 688 
 689     public Path getExistingImage() {
 690         return pluginOptions.existingImage;
 691     }
 692 
 693     public void showVersion(boolean full) {
 694         log.println(version(full ? "full" : "release"));
 695     }
 696 
 697     public String version(String key) {
 698         return System.getProperty("java.version");
 699     }
 700 
 701     static Layer createPluginsLayer(List<Path> paths) {
 702         Path[] arr = new Path[paths.size()];
 703         paths.toArray(arr);
 704         ModuleFinder finder = ModuleFinder.of(arr);
 705 
 706         // jmods are located at link-time
 707         if (finder instanceof ConfigurableModuleFinder) {
 708             ((ConfigurableModuleFinder) finder).configurePhase(Phase.LINK_TIME);
 709         }
 710 
 711         Configuration bootConfiguration = Layer.boot().configuration();
 712         try {
 713             Configuration cf = bootConfiguration
 714                 .resolveRequiresAndUses(ModuleFinder.of(),
 715                                         finder,
 716                                         Collections.emptySet());
 717             ClassLoader scl = ClassLoader.getSystemClassLoader();
 718             return Layer.boot().defineModulesWithOneLoader(cf, scl);
 719         } catch (Exception ex) {
 720             // Malformed plugin modules (e.g.: same package in multiple modules).
 721             throw new PluginException("Invalid modules in the plugins path: " + ex);
 722         }
 723     }
 724 
 725     // Display all plugins or pre processors only.
 726     private static boolean showsPlugin(Plugin plugin, boolean showsImageBuilder) {
 727         if (!Utils.isDisabled(plugin) && plugin.getOption() != null) {
 728             if (Utils.isPostProcessor(plugin) && !showsImageBuilder) {
 729                 return false;
 730             }
 731             return true;
 732         }
 733         return false;
 734     }
 735 }