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