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