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