1 /* 2 * Copyright (c) 2015, 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; 26 27 import java.io.File; 28 import java.io.FileInputStream; 29 import java.io.FileNotFoundException; 30 import java.io.IOException; 31 import java.io.PrintWriter; 32 import java.lang.module.Configuration; 33 import java.lang.module.ModuleDescriptor; 34 import java.lang.module.ModuleFinder; 35 import java.lang.module.ModuleReference; 36 import java.lang.module.ResolutionException; 37 import java.lang.reflect.Layer; 38 import java.nio.file.Path; 39 import java.nio.file.Paths; 40 import java.text.MessageFormat; 41 import java.util.ArrayList; 42 import java.util.HashMap; 43 import java.util.List; 44 import java.util.Locale; 45 import java.util.Map; 46 import java.util.Map.Entry; 47 import java.util.MissingResourceException; 48 import java.util.Properties; 49 import java.util.ResourceBundle; 50 import java.util.Set; 51 52 import jdk.internal.module.ConfigurableModuleFinder; 53 import jdk.internal.module.ConfigurableModuleFinder.Phase; 54 import jdk.tools.jlink.internal.ImagePluginProviderRepository; 55 import jdk.tools.jlink.internal.ImagePluginConfiguration; 56 import jdk.tools.jlink.plugins.CmdPluginProvider; 57 import jdk.tools.jlink.plugins.CmdResourcePluginProvider; 58 import jdk.tools.jlink.plugins.ImageBuilderProvider; 59 import jdk.tools.jlink.plugins.OnOffImageFilePluginProvider; 60 import jdk.tools.jlink.plugins.OnOffResourcePluginProvider; 61 import jdk.tools.jlink.plugins.PluginProvider; 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 private static final String CONFIGURATION = "configuration"; 74 75 public final class BadArgs extends Exception { 76 77 static final long serialVersionUID = 8765093759964640721L; 78 79 private BadArgs(String key, Object... args) { 80 super(bundleHelper.getMessage(key, args)); 81 this.key = key; 82 this.args = args; 83 } 84 85 public BadArgs showUsage(boolean b) { 86 showUsage = b; 87 return this; 88 } 89 public final String key; 90 public final Object[] args; 91 public boolean showUsage; 92 } 93 94 public static class Option<T> { 95 96 public interface Processing<T> { 97 98 void process(T task, String opt, String arg) throws BadArgs; 99 } 100 101 final boolean hasArg; 102 final String[] aliases; 103 final Processing<T> processing; 104 105 public Option(boolean hasArg, Processing<T> processing, String... aliases) { 106 this.hasArg = hasArg; 107 this.processing = processing; 108 this.aliases = aliases; 109 } 110 111 public boolean isHidden() { 112 return false; 113 } 114 115 public boolean matches(String opt) { 116 for (String a : aliases) { 117 if (a.equals(opt)) { 118 return true; 119 } else if (opt.startsWith("--") 120 && (hasArg && opt.startsWith(a + "="))) { 121 return true; 122 } 123 } 124 return false; 125 } 126 127 public boolean ignoreRest() { 128 return false; 129 } 130 131 void process(T task, String opt, String arg) throws BadArgs { 132 processing.process(task, opt, arg); 133 } 134 } 135 136 private static class PluginOption extends Option<PluginsOptions> { 137 138 public PluginOption(boolean hasArg, 139 Processing<PluginsOptions> processing, String... aliases) { 140 super(hasArg, processing, aliases); 141 } 142 } 143 144 private static class HiddenPluginOption extends PluginOption { 145 146 public HiddenPluginOption(boolean hasArg, 147 Processing<PluginsOptions> processing, String... aliases) { 148 super(hasArg, processing, aliases); 149 } 150 151 @Override 152 public boolean isHidden() { 153 return true; 154 } 155 } 156 157 private final class PluginsOptions { 158 159 private static final String PLUGINS_PATH = "--plugins-modulepath"; 160 161 private Layer pluginsLayer = Layer.boot(); 162 private String pluginsProperties; 163 private boolean listPlugins; 164 private final Map<PluginProvider, Map<String, String>> plugins = new HashMap<>(); 165 private final Map<ImageBuilderProvider, Map<String, String>> builders = new HashMap<>(); 166 private final List<PluginOption> pluginsOptions = new ArrayList<>(); 167 168 private PluginsOptions(String pp) throws BadArgs { 169 170 if (pp != null) { 171 String[] dirs = pp.split(File.pathSeparator); 172 Path[] paths = new Path[dirs.length]; 173 int i = 0; 174 for (String dir : dirs) { 175 paths[i++] = Paths.get(dir); 176 } 177 178 pluginsLayer = createPluginsLayer(paths); 179 } 180 181 Map<String, List<String>> seen = new HashMap<>(); 182 for (PluginProvider prov : ImagePluginProviderRepository.getPluginProviders(pluginsLayer)) { 183 if (prov instanceof CmdPluginProvider) { 184 CmdPluginProvider provider = (CmdPluginProvider) prov; 185 if (provider.getToolOption() != null) { 186 for (Entry<String, List<String>> entry : seen.entrySet()) { 187 if (entry.getKey().equals(provider.getToolOption()) 188 || entry.getValue().contains(provider.getToolOption())) { 189 throw new BadArgs("err.plugin.mutiple.options", 190 provider.getToolOption()); 191 } 192 } 193 List<String> optional = new ArrayList<>(); 194 seen.put(provider.getToolOption(), optional); 195 PluginOption option 196 = new PluginOption(provider.getToolArgument() != null, 197 (task, opt, arg) -> { 198 Map<String, String> m = plugins.get(provider); 199 if (m == null) { 200 m = new HashMap<>(); 201 plugins.put(provider, m); 202 } 203 m.put(CmdPluginProvider.TOOL_ARGUMENT_PROPERTY, arg); 204 }, 205 "--" + provider.getToolOption()); 206 pluginsOptions.add(option); 207 if (provider.getAdditionalOptions() != null) { 208 for (String other : provider.getAdditionalOptions().keySet()) { 209 optional.add(other); 210 PluginOption otherOption = new PluginOption(true, 211 (task, opt, arg) -> { 212 Map<String, String> m = plugins.get(provider); 213 if (m == null) { 214 m = new HashMap<>(); 215 plugins.put(provider, m); 216 } 217 m.put(other, arg); 218 }, 219 "--" + other); 220 pluginsOptions.add(otherOption); 221 } 222 } 223 // On/Off enabled by default plugin 224 // Command line option can override it 225 boolean edefault = false; 226 if (provider instanceof OnOffResourcePluginProvider) { 227 edefault = ((OnOffResourcePluginProvider) provider).isEnabledByDefault(); 228 } else { 229 if (provider instanceof OnOffImageFilePluginProvider) { 230 edefault = ((OnOffImageFilePluginProvider) provider).isEnabledByDefault(); 231 } 232 } 233 if (edefault) { 234 Map<String, String> m = new HashMap<>(); 235 m.put(CmdPluginProvider.TOOL_ARGUMENT_PROPERTY, 236 ImagePluginConfiguration.ON_ARGUMENT); 237 plugins.put(provider, m); 238 } 239 } 240 } 241 } 242 pluginsOptions.add(new HiddenPluginOption(true, 243 (task, opt, arg) -> { 244 pluginsProperties = arg; 245 }, 246 "--plugins-configuration")); 247 pluginsOptions.add(new HiddenPluginOption(false, 248 (task, opt, arg) -> { 249 listPlugins = true; 250 }, 251 "--list-plugins")); 252 253 //Image Builder options 254 for (ImageBuilderProvider provider 255 : ImagePluginProviderRepository.getImageBuilderProviders(pluginsLayer)) { 256 Map<String, String> options = provider.getOptions(); 257 if (options != null && !options.isEmpty()) { 258 for (Entry<String, String> o : options.entrySet()) { 259 PluginOption option 260 = new PluginOption(provider.hasArgument(o.getKey()), 261 (task, opt, arg) -> { 262 Map<String, String> m = builders.get(provider); 263 if (m == null) { 264 m = new HashMap<>(); 265 builders.put(provider, m); 266 } 267 m.put(o.getKey(), arg); 268 }, 269 "--" + o.getKey()); 270 pluginsOptions.add(option); 271 } 272 } 273 } 274 } 275 276 private PluginOption getOption(String name) throws BadArgs { 277 for (PluginOption o : pluginsOptions) { 278 if (o.matches(name)) { 279 return o; 280 } 281 } 282 return null; 283 } 284 285 private Properties getPluginsProperties() throws IOException { 286 Properties props = new Properties(); 287 if (pluginsProperties != null) { 288 try (FileInputStream stream 289 = new FileInputStream(pluginsProperties);) { 290 props.load(stream); 291 } catch (FileNotFoundException ex) { 292 throw new IOException(bundleHelper.getMessage("err.path.not.valid", pluginsProperties)); 293 } 294 } 295 for (Entry<PluginProvider, Map<String, String>> entry : plugins.entrySet()) { 296 PluginProvider provider = entry.getKey(); 297 ImagePluginConfiguration.addPluginProperty(props, provider); 298 if (entry.getValue() != null) { 299 for (Entry<String, String> opts : entry.getValue().entrySet()) { 300 if (opts.getValue() != null) { 301 props.setProperty(provider.getName() + "." 302 + opts.getKey(), opts.getValue()); 303 } 304 } 305 } 306 } 307 for (Entry<ImageBuilderProvider, Map<String, String>> provs : builders.entrySet()) { 308 ImageBuilderProvider provider = provs.getKey(); 309 for (Entry<String, String> entry : provs.getValue().entrySet()) { 310 props.setProperty(provider.getName() + "." 311 + entry.getKey(), entry.getValue() == null ? "" 312 : entry.getValue()); 313 } 314 } 315 return props; 316 } 317 } 318 319 public static class HiddenOption<T> extends Option<T> { 320 321 public HiddenOption(boolean hasArg, Processing<T> processing, 322 String... aliases) { 323 super(hasArg, processing, aliases); 324 } 325 326 @Override 327 public boolean isHidden() { 328 return true; 329 } 330 } 331 332 private static final class ResourceBundleHelper { 333 334 private final ResourceBundle bundle; 335 private final ResourceBundle pluginBundle; 336 337 ResourceBundleHelper(String path) { 338 Locale locale = Locale.getDefault(); 339 try { 340 bundle = ResourceBundle.getBundle(path, locale); 341 pluginBundle = ResourceBundle.getBundle("jdk.tools.jlink.resources.plugins", locale); 342 } catch (MissingResourceException e) { 343 throw new InternalError("Cannot find jlink resource bundle for locale " + locale); 344 } 345 } 346 347 String getMessage(String key, Object... args) { 348 String val; 349 try { 350 val = bundle.getString(key); 351 } catch (MissingResourceException e) { 352 // XXX OK, check in plugin bundle 353 val = pluginBundle.getString(key); 354 } 355 return MessageFormat.format(val, args); 356 } 357 358 } 359 360 public final class OptionsHelper<T> { 361 362 private final List<Option<T>> options; 363 private String[] expandedCommand; 364 private String[] command; 365 private String defaults; 366 367 OptionsHelper(List<Option<T>> options) { 368 this.options = options; 369 } 370 371 private boolean hasArgument(String optionName) throws BadArgs { 372 Option<?> opt = getOption(optionName); 373 if (opt == null) { 374 opt = pluginOptions.getOption(optionName); 375 if (opt == null) { 376 throw new BadArgs("err.unknown.option", optionName). 377 showUsage(true); 378 } 379 } 380 return opt.hasArg; 381 } 382 383 private String[] handleDefaults(String[] args) throws BadArgs { 384 String[] ret = args; 385 List<String> defArgs = null; 386 387 List<String> override = new ArrayList<>(); 388 boolean expanded = false; 389 for (int i = 0; i < args.length; i++) { 390 if (args[i].equals("--" + CONFIGURATION)) { 391 i++; 392 String path = args[i]; 393 Properties p = new Properties(); 394 try (FileInputStream fs = new FileInputStream(path)) { 395 p.load(fs); 396 } catch (IOException ex) { 397 throw new RuntimeException(ex); 398 } 399 defaults = p.getProperty(DEFAULTS_PROPERTY); 400 if (defaults == null) { 401 throw new BadArgs("err.config.defaults", 402 DEFAULTS_PROPERTY); 403 } 404 try { 405 defArgs = parseDefaults(defaults); 406 } catch (Exception ex) { 407 throw new BadArgs("err.config.defaults", defaults); 408 } 409 expanded = true; 410 } else { 411 override.add(args[i]); 412 } 413 } 414 if (defArgs != null) { 415 List<String> output = new ArrayList<>(); 416 for (int i = 0; i < defArgs.size(); i++) { 417 String arg = defArgs.get(i); 418 if (arg.charAt(0) == '-') { 419 output.add(arg); 420 boolean hasArgument = hasArgument(arg); 421 String a = null; 422 if (hasArgument) { 423 i++; 424 a = defArgs.get(i); 425 } 426 int overIndex = override.indexOf(arg); 427 if (overIndex >= 0) { 428 if (hasArgument) { 429 a = override.get(overIndex + 1); 430 override.remove(a); 431 } 432 override.remove(arg); 433 } 434 if (hasArgument) { 435 if (a == null) { 436 throw newBadArgs("err.unknown.option", arg). 437 showUsage(true); 438 } 439 output.add(a); 440 } 441 } else { 442 throw newBadArgs("err.unknown.option", arg). 443 showUsage(true); 444 } 445 } 446 //Add remaining 447 output.addAll(override); 448 ret = new String[output.size()]; 449 output.toArray(ret); 450 } 451 if (expanded) { 452 expandedCommand = ret; 453 } 454 return ret; 455 } 456 457 public List<String> handleOptions(T task, String[] args) throws BadArgs { 458 command = args; 459 // Handle defaults. 460 args = handleDefaults(args); 461 462 // First extract plugins path if any 463 String pp = null; 464 List<String> filteredArgs = new ArrayList<>(); 465 for (int i = 0; i < args.length; i++) { 466 if (args[i].equals(PluginsOptions.PLUGINS_PATH)) { 467 if (i == args.length - 1) { 468 throw new BadArgs("err.no.plugins.path").showUsage(true); 469 } else { 470 warning("warn.thirdparty.plugins.enabled"); 471 log.println(bundleHelper.getMessage("warn.thirdparty.plugins")); 472 i += 1; 473 String arg = args[i]; 474 if (!arg.isEmpty() && arg.charAt(0) == '-') { 475 throw new BadArgs("err.no.plugins.path").showUsage(true); 476 } 477 pp = args[i]; 478 } 479 } else { 480 filteredArgs.add(args[i]); 481 } 482 } 483 String[] arr = new String[filteredArgs.size()]; 484 args = filteredArgs.toArray(arr); 485 486 // Unit tests can call Task multiple time in same JVM. 487 pluginOptions = new PluginsOptions(pp); 488 489 List<String> rest = new ArrayList<>(); 490 // process options 491 for (int i = 0; i < args.length; i++) { 492 if (!args[i].isEmpty() && args[i].charAt(0) == '-') { 493 String name = args[i]; 494 PluginOption pluginOption = null; 495 Option<T> option = getOption(name); 496 if (option == null) { 497 pluginOption = pluginOptions.getOption(name); 498 if (pluginOption == null) { 499 500 throw new BadArgs("err.unknown.option", name). 501 showUsage(true); 502 } 503 } 504 Option<?> opt = pluginOption == null ? option : pluginOption; 505 String param = null; 506 if (opt.hasArg) { 507 if (name.startsWith("--") && name.indexOf('=') > 0) { 508 param = name.substring(name.indexOf('=') + 1, 509 name.length()); 510 } else if (i + 1 < args.length) { 511 param = args[++i]; 512 } 513 if (param == null || param.isEmpty() 514 || param.charAt(0) == '-') { 515 throw new BadArgs("err.missing.arg", name). 516 showUsage(true); 517 } 518 } 519 if (pluginOption != null) { 520 pluginOption.process(pluginOptions, name, param); 521 } else { 522 option.process(task, name, param); 523 } 524 if (opt.ignoreRest()) { 525 i = args.length; 526 } 527 } else { 528 rest.add(args[i]); 529 } 530 } 531 return rest; 532 } 533 534 private Option<T> getOption(String name) { 535 for (Option<T> o : options) { 536 if (o.matches(name)) { 537 return o; 538 } 539 } 540 return null; 541 } 542 543 public void showHelp(String progName, String pluginsHeader, 544 boolean showsImageBuilder) { 545 log.println(bundleHelper.getMessage("main.usage", progName)); 546 // First configuration 547 log.println(bundleHelper.getMessage("main.opt." + CONFIGURATION)); 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 log.println(bundleHelper.getMessage("main.plugins-modulepath") 557 + ". " + bundleHelper.getMessage("warn.prefix") + " " 558 + bundleHelper.getMessage("warn.thirdparty.plugins")); 559 log.println(bundleHelper.getMessage("main.command.files")); 560 561 log.println("\n" + pluginsHeader); 562 for (PluginProvider prov : ImagePluginProviderRepository. 563 getPluginProviders(pluginOptions.pluginsLayer)) { 564 if (showsPlugin(prov, showsImageBuilder)) { 565 CmdPluginProvider provider = (CmdPluginProvider) prov; 566 567 if (provider.getToolOption() != null) { 568 StringBuilder line = new StringBuilder(); 569 line.append(" --").append(provider.getToolOption()); 570 if (provider.getToolArgument() != null) { 571 line.append(" ").append(provider.getToolArgument()); 572 } 573 line.append("\n ").append(provider.getDescription()); 574 if (provider.getAdditionalOptions() != null) { 575 line.append("\n").append(bundleHelper. 576 getMessage("main.plugin.additional.options")). 577 append(": "); 578 for (Entry<String, String> entry : provider.getAdditionalOptions().entrySet()) { 579 line.append(" --").append(entry.getKey()).append(" "). 580 append(entry.getValue()); 581 } 582 } 583 log.println(line.toString() + "\n"); 584 } 585 } 586 } 587 if (showsImageBuilder) { 588 log.println(bundleHelper.getMessage("main.image.builders")); 589 for (ImageBuilderProvider prov 590 : ImagePluginProviderRepository.getImageBuilderProviders(getPluginsLayer())) { 591 log.println("\n" + bundleHelper.getMessage("main.image.builder.name") 592 + ": " + prov.getName()); 593 log.println(bundleHelper.getMessage("main.image.builder.description") 594 + ": " + prov.getDescription()); 595 logBuilderOptions(prov.getOptions()); 596 } 597 } 598 } 599 600 public void showPlugins(PrintWriter log, boolean showsImageBuilder) { 601 for (PluginProvider prov : ImagePluginProviderRepository.getPluginProviders(getPluginsLayer())) { 602 if (showsPlugin(prov, showsImageBuilder)) { 603 CmdPluginProvider fact = (CmdPluginProvider) prov; 604 log.println("\n" + bundleHelper.getMessage("main.plugin.name") 605 + ": " + fact.getName()); 606 Integer[] range = ImagePluginConfiguration.getRange(fact); 607 String cat = range == null ? fact.getCategory() : fact.getCategory() 608 + ". " + bundleHelper.getMessage("main.plugin.range.from") 609 + " " + range[0] + " " + bundleHelper. 610 getMessage("main.plugin.range.to") + " " 611 + range[1] + "."; 612 log.println(bundleHelper.getMessage("main.plugin.category") 613 + ": " + cat); 614 log.println(bundleHelper.getMessage("main.plugin.description") 615 + ": " + fact.getDescription()); 616 log.println(bundleHelper.getMessage("main.plugin.argument") 617 + ": " + (fact.getToolArgument() == null 618 ? bundleHelper.getMessage("main.plugin.no.value") 619 : fact.getToolArgument())); 620 String additionalOptions = bundleHelper.getMessage("main.plugin.no.value"); 621 if (fact.getAdditionalOptions() != null) { 622 StringBuilder builder = new StringBuilder(); 623 for (Entry<String, String> entry : fact.getAdditionalOptions().entrySet()) { 624 builder.append("--" + entry.getKey()).append(" "). 625 append(entry.getValue()); 626 } 627 additionalOptions = builder.toString(); 628 } 629 log.println(bundleHelper.getMessage("main.plugin.additional.options") 630 + ": " + additionalOptions); 631 String option = fact.getToolOption(); 632 if (option != null) { 633 log.println(bundleHelper.getMessage("main.plugin.option") 634 + ": --" + option); 635 } 636 } 637 } 638 if (showsImageBuilder) { 639 for (ImageBuilderProvider prov 640 : ImagePluginProviderRepository.getImageBuilderProviders(getPluginsLayer())) { 641 log.println("\n" + bundleHelper.getMessage("main.image.builder.name") 642 + ": " + prov.getName()); 643 log.println(bundleHelper.getMessage("main.image.builder.description") 644 + ": " + prov.getDescription()); 645 logBuilderOptions(prov.getOptions()); 646 } 647 } 648 } 649 650 private void logBuilderOptions(Map<String, String> options) { 651 if (options != null && !options.isEmpty()) { 652 for (Entry<String, String> opt : options.entrySet()) { 653 log.println(" --" + opt.getKey() + " " + opt.getValue() + "\n"); 654 } 655 } 656 } 657 658 public boolean listPlugins() { 659 return pluginOptions.listPlugins; 660 } 661 662 String[] getExpandedCommand() { 663 return expandedCommand; 664 } 665 666 String[] getInputCommand() { 667 return command; 668 } 669 670 String getDefaults() { 671 return defaults; 672 } 673 674 String getPluginsConfig() throws IOException { 675 String ret = null; 676 if (pluginOptions.pluginsProperties != null) { 677 Properties props = new Properties(); 678 try (FileInputStream fis 679 = new FileInputStream(pluginOptions.pluginsProperties)) { 680 props.load(fis); 681 } catch (FileNotFoundException ex) { 682 throw new IOException(bundleHelper.getMessage("err.path.not.valid", 683 pluginOptions.pluginsProperties)); 684 } 685 StringBuilder sb = new StringBuilder(); 686 for (String str : props.stringPropertyNames()) { 687 sb.append(str).append(" = ").append(props.getProperty(str)). 688 append("\n"); 689 } 690 ret = sb.toString(); 691 } 692 return ret; 693 } 694 695 Layer getPluginsLayer() { 696 return pluginOptions.pluginsLayer; 697 } 698 } 699 700 private PluginsOptions 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 Properties getPluginsProperties() throws IOException { 749 return pluginOptions.getPluginsProperties(); 750 } 751 752 public void showVersion(boolean full) { 753 log.println(version(full ? "full" : "release")); 754 } 755 756 public String version(String key) { 757 return System.getProperty("java.version"); 758 } 759 760 // public for testing purpose 761 public static List<String> parseDefaults(String defaults) throws Exception { 762 List<String> arguments = new ArrayList<>(); 763 while (!defaults.isEmpty()) { 764 int start = defaults.indexOf("--"); 765 if (start < 0) { 766 throw new Exception("Invalid defaults " + defaults); 767 } 768 defaults = defaults.substring(start); 769 start = 0; 770 int end = defaults.indexOf(" "); 771 String remaining; 772 773 if (end < 0) { 774 arguments.add(defaults); 775 remaining = ""; 776 } else { 777 String option = defaults.substring(start, end); 778 arguments.add(option); 779 defaults = defaults.substring(end); 780 int nextOption = defaults.indexOf("--"); 781 int argEnd = nextOption < 0 ? defaults.length() : nextOption; 782 String arg = defaults.substring(0, argEnd); 783 arg = arg.replaceAll(" ", ""); 784 if (!arg.isEmpty()) { 785 arguments.add(arg); 786 } 787 remaining = defaults.substring(argEnd); 788 } 789 790 defaults = remaining; 791 } 792 return arguments; 793 } 794 795 private static void checkExportedPackages(Set<ModuleReference> modules) { 796 Map<String, ModuleReference> packageToModule = new HashMap<>(); 797 for (ModuleReference module : modules) { 798 for (String pack: module.descriptor().packages()) { 799 ModuleReference other = packageToModule.putIfAbsent(pack, module); 800 if (other != null) { 801 String module1 = module.descriptor().name(); 802 String module2 = other.descriptor().name(); 803 throw new ResolutionException("Modules " 804 + module1 + " and " + module2 + " both contain package " + pack); 805 } 806 } 807 } 808 } 809 810 static Layer createPluginsLayer(Path[] paths) { 811 ModuleFinder finder = ModuleFinder.of(paths); 812 813 // jmods are located at link-time 814 if (finder instanceof ConfigurableModuleFinder) 815 ((ConfigurableModuleFinder)finder).configurePhase(Phase.LINK_TIME); 816 817 Configuration cf 818 = Configuration.resolve(ModuleFinder.empty(), Layer.boot(), finder); 819 cf = cf.bind(); 820 checkExportedPackages(cf.modules()); 821 // The creation of this classloader is done outside privileged block in purpose 822 // If a security manager is set, then permission must be granted to jlink 823 // codebase to create a classloader. This is the expected behavior. 824 ClassLoader cl = new ModuleClassLoader(cf); 825 return Layer.create(cf, mn -> cl); 826 } 827 828 // Display all plugins or resource only. 829 private static boolean showsPlugin(PluginProvider prov, boolean showsImageBuilder) { 830 return (prov instanceof CmdPluginProvider && showsImageBuilder) 831 || (prov instanceof CmdResourcePluginProvider); 832 } 833 }