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