|
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 } |