1 /*
2  * Copyright (c) 2018, 2020, 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.incubator.jpackage.internal;
26 
27 import java.io.File;
28 import java.io.FileInputStream;
29 import java.io.IOException;
30 import java.nio.file.Files;
31 import java.nio.file.Path;
32 import java.nio.file.Paths;
33 import java.text.MessageFormat;
34 import java.util.ArrayList;
35 import java.util.Arrays;
36 import java.util.Collection;
37 import java.util.EnumSet;
38 import java.util.HashMap;
39 import java.util.HashSet;
40 import java.util.List;
41 import java.util.Map;
42 import java.util.Set;
43 import java.util.Properties;
44 import java.util.ResourceBundle;
45 import java.util.jar.Attributes;
46 import java.util.jar.JarFile;
47 import java.util.jar.Manifest;
48 import java.util.stream.Stream;
49 import java.util.regex.Matcher;
50 import java.util.regex.Pattern;
51 
52 /**
53  * Arguments
54  *
55  * This class encapsulates and processes the command line arguments,
56  * in effect, implementing all the work of jpackage tool.
57  *
58  * The primary entry point, processArguments():
59  * Processes and validates command line arguments, constructing DeployParams.
60  * Validates the DeployParams, and generate the BundleParams.
61  * Generates List of Bundlers from BundleParams valid for this platform.
62  * Executes each Bundler in the list.
63  */
64 public class Arguments {
65     private static final ResourceBundle I18N = ResourceBundle.getBundle(
66             "jdk.incubator.jpackage.internal.resources.MainResources");
67 
68     private static final String FA_EXTENSIONS = "extension";
69     private static final String FA_CONTENT_TYPE = "mime-type";
70     private static final String FA_DESCRIPTION = "description";
71     private static final String FA_ICON = "icon";
72 
73     // Mac specific file association keys
74     // String
75     public static final String MAC_CFBUNDLETYPEROLE = "mac.CFBundleTypeRole";
76     public static final String MAC_LSHANDLERRANK = "mac.LSHandlerRank";
77     public static final String MAC_NSSTORETYPEKEY = "mac.NSPersistentStoreTypeKey";
78     public static final String MAC_NSDOCUMENTCLASS = "mac.NSDocumentClass";
79     // Boolean
80     public static final String MAC_LSTYPEISPACKAGE = "mac.LSTypeIsPackage";
81     public static final String MAC_LSDOCINPLACE = "mac.LSSupportsOpeningDocumentsInPlace";
82     public static final String MAC_UIDOCBROWSER = "mac.UISupportsDocumentBrowser";
83      // Array of strings
84     public static final String MAC_NSEXPORTABLETYPES = "mac.NSExportableTypes";
85     public static final String MAC_UTTYPECONFORMSTO = "mac.UTTypeConformsTo";
86 
87     // regexp for parsing args (for example, for additional launchers)
88     private static Pattern pattern = Pattern.compile(
89           "(?:(?:([\"'])(?:\\\\\\1|.)*?(?:\\1|$))|(?:\\\\[\"'\\s]|[^\\s]))++");
90 
91     private DeployParams deployParams = null;
92 
93     private int pos = 0;
94     private List<String> argList = null;
95 
96     private List<CLIOptions> allOptions = null;
97 
98     private String input = null;
99     private String output = null;
100 
101     private boolean hasMainJar = false;
102     private boolean hasMainClass = false;
103     private boolean hasMainModule = false;
104     public boolean userProvidedBuildRoot = false;
105 
106     private String buildRoot = null;
107     private String mainJarPath = null;
108 
109     private static boolean runtimeInstaller = false;
110 
111     private List<AddLauncherArguments> addLaunchers = null;
112 
113     private static Map<String, CLIOptions> argIds = new HashMap<>();
114     private static Map<String, CLIOptions> argShortIds = new HashMap<>();
115 
116     static {
117         // init maps for parsing arguments
118         (EnumSet.allOf(CLIOptions.class)).forEach(option -> {
119             argIds.put(option.getIdWithPrefix(), option);
120             if (option.getShortIdWithPrefix() != null) {
121                 argShortIds.put(option.getShortIdWithPrefix(), option);
122             }
123         });
124     }
125 
Arguments(String[] args)126     public Arguments(String[] args) {
127         argList = new ArrayList<String>(args.length);
128         for (String arg : args) {
129             argList.add(arg);
130         }
131         Log.verbose ("\njpackage argument list: \n" + argList + "\n");
132         pos = 0;
133 
134         deployParams = new DeployParams();
135 
136         allOptions = new ArrayList<>();
137 
138         addLaunchers = new ArrayList<>();
139 
140         output = Paths.get("").toAbsolutePath().toString();
141         deployParams.setOutput(new File(output));
142     }
143 
144     // CLIOptions is public for DeployParamsTest
145     public enum CLIOptions {
146         PACKAGE_TYPE("type", "t", OptionCategories.PROPERTY, () -> {
147             context().deployParams.setTargetFormat(popArg());
148         }),
149 
150         INPUT ("input", "i", OptionCategories.PROPERTY, () -> {
151             context().input = popArg();
152             setOptionValue("input", context().input);
153         }),
154 
155         OUTPUT ("dest", "d", OptionCategories.PROPERTY, () -> {
156             context().output = popArg();
157             context().deployParams.setOutput(new File(context().output));
158         }),
159 
160         DESCRIPTION ("description", OptionCategories.PROPERTY),
161 
162         VENDOR ("vendor", OptionCategories.PROPERTY),
163 
164         APPCLASS ("main-class", OptionCategories.PROPERTY, () -> {
165             context().hasMainClass = true;
166             setOptionValue("main-class", popArg());
167         }),
168 
169         NAME ("name", "n", OptionCategories.PROPERTY),
170 
171         VERBOSE ("verbose", OptionCategories.PROPERTY, () -> {
172             setOptionValue("verbose", true);
173             Log.setVerbose();
174         }),
175 
176         RESOURCE_DIR("resource-dir",
177                 OptionCategories.PROPERTY, () -> {
178             String resourceDir = popArg();
179             setOptionValue("resource-dir", resourceDir);
180         }),
181 
182         ARGUMENTS ("arguments", OptionCategories.PROPERTY, () -> {
183             List<String> arguments = getArgumentList(popArg());
184             setOptionValue("arguments", arguments);
185         }),
186 
187         JLINK_OPTIONS ("jlink-options", OptionCategories.PROPERTY, () -> {
188             List<String> options = getArgumentList(popArg());
189             setOptionValue("jlink-options", options);
190         }),
191 
192         ICON ("icon", OptionCategories.PROPERTY),
193 
194         COPYRIGHT ("copyright", OptionCategories.PROPERTY),
195 
196         LICENSE_FILE ("license-file", OptionCategories.PROPERTY),
197 
198         VERSION ("app-version", OptionCategories.PROPERTY),
199 
200         RELEASE ("linux-app-release", OptionCategories.PROPERTY),
201 
202         JAVA_OPTIONS ("java-options", OptionCategories.PROPERTY, () -> {
203             List<String> args = getArgumentList(popArg());
204             args.forEach(a -> setOptionValue("java-options", a));
205         }),
206 
207         FILE_ASSOCIATIONS ("file-associations",
208                 OptionCategories.PROPERTY, () -> {
209             Map<String, ? super Object> args = new HashMap<>();
210 
211             // load .properties file
212             Map<String, String> initialMap = getPropertiesFromFile(popArg());
213 
214             putUnlessNull(args, StandardBundlerParam.FA_EXTENSIONS.getID(),
215                     initialMap.get(FA_EXTENSIONS));
216 
217             putUnlessNull(args, StandardBundlerParam.FA_CONTENT_TYPE.getID(),
218                     initialMap.get(FA_CONTENT_TYPE));
219 
220             putUnlessNull(args, StandardBundlerParam.FA_DESCRIPTION.getID(),
221                     initialMap.get(FA_DESCRIPTION));
222 
223             putUnlessNull(args, StandardBundlerParam.FA_ICON.getID(),
224                     initialMap.get(FA_ICON));
225 
226             // Mac extended file association arguments
227             putUnlessNull(args, MAC_CFBUNDLETYPEROLE,
228                     initialMap.get(MAC_CFBUNDLETYPEROLE));
229 
230             putUnlessNull(args, MAC_LSHANDLERRANK,
231                     initialMap.get(MAC_LSHANDLERRANK));
232 
233             putUnlessNull(args, MAC_NSSTORETYPEKEY,
234                     initialMap.get(MAC_NSSTORETYPEKEY));
235 
236             putUnlessNull(args, MAC_NSDOCUMENTCLASS,
237                     initialMap.get(MAC_NSDOCUMENTCLASS));
238 
239             putUnlessNull(args, MAC_LSTYPEISPACKAGE,
240                     initialMap.get(MAC_LSTYPEISPACKAGE));
241 
242             putUnlessNull(args, MAC_LSDOCINPLACE,
243                     initialMap.get(MAC_LSDOCINPLACE));
244 
245             putUnlessNull(args, MAC_UIDOCBROWSER,
246                     initialMap.get(MAC_UIDOCBROWSER));
247 
248             putUnlessNull(args, MAC_NSEXPORTABLETYPES,
249                     initialMap.get(MAC_NSEXPORTABLETYPES));
250 
251             putUnlessNull(args, MAC_UTTYPECONFORMSTO,
252                     initialMap.get(MAC_UTTYPECONFORMSTO));
253 
254             ArrayList<Map<String, ? super Object>> associationList =
255                 new ArrayList<Map<String, ? super Object>>();
256 
257             associationList.add(args);
258 
259             // check that we really add _another_ value to the list
260             setOptionValue("file-associations", associationList);
261 
262         }),
263 
264         ADD_LAUNCHER ("add-launcher",
265                     OptionCategories.PROPERTY, () -> {
266             String spec = popArg();
267             String name = null;
268             String filename = spec;
269             if (spec.contains("=")) {
270                 String[] values = spec.split("=", 2);
271                 name = values[0];
272                 filename = values[1];
273             }
274             context().addLaunchers.add(
275                 new AddLauncherArguments(name, filename));
276         }),
277 
278         TEMP_ROOT ("temp", OptionCategories.PROPERTY, () -> {
279             context().buildRoot = popArg();
280             context().userProvidedBuildRoot = true;
281             setOptionValue("temp", context().buildRoot);
282         }),
283 
284         INSTALL_DIR ("install-dir", OptionCategories.PROPERTY),
285 
286         PREDEFINED_APP_IMAGE ("app-image", OptionCategories.PROPERTY),
287 
288         PREDEFINED_RUNTIME_IMAGE ("runtime-image", OptionCategories.PROPERTY),
289 
290         MAIN_JAR ("main-jar",  OptionCategories.PROPERTY, () -> {
291             context().mainJarPath = popArg();
292             context().hasMainJar = true;
293             setOptionValue("main-jar", context().mainJarPath);
294         }),
295 
296         MODULE ("module", "m", OptionCategories.MODULAR, () -> {
297             context().hasMainModule = true;
298             setOptionValue("module", popArg());
299         }),
300 
301         ADD_MODULES ("add-modules", OptionCategories.MODULAR),
302 
303         MODULE_PATH ("module-path", "p", OptionCategories.MODULAR),
304 
305         BIND_SERVICES ("bind-services", OptionCategories.PROPERTY, () -> {
306             showDeprecation("bind-services");
307             setOptionValue("bind-services", true);
308         }),
309 
310         MAC_SIGN ("mac-sign", "s", OptionCategories.PLATFORM_MAC, () -> {
311             setOptionValue("mac-sign", true);
312         }),
313 
314         MAC_BUNDLE_NAME ("mac-package-name", OptionCategories.PLATFORM_MAC),
315 
316         MAC_BUNDLE_IDENTIFIER("mac-package-identifier",
317                     OptionCategories.PLATFORM_MAC),
318 
319         MAC_BUNDLE_SIGNING_PREFIX ("mac-package-signing-prefix",
320                     OptionCategories.PLATFORM_MAC),
321 
322         MAC_SIGNING_KEY_NAME ("mac-signing-key-user-name",
323                     OptionCategories.PLATFORM_MAC),
324 
325         MAC_SIGNING_KEYCHAIN ("mac-signing-keychain",
326                     OptionCategories.PLATFORM_MAC),
327 
328         WIN_MENU_HINT ("win-menu", OptionCategories.PLATFORM_WIN, () -> {
329             setOptionValue("win-menu", true);
330         }),
331 
332         WIN_MENU_GROUP ("win-menu-group", OptionCategories.PLATFORM_WIN),
333 
334         WIN_SHORTCUT_HINT ("win-shortcut",
335                 OptionCategories.PLATFORM_WIN, () -> {
336             setOptionValue("win-shortcut", true);
337         }),
338 
339         WIN_PER_USER_INSTALLATION ("win-per-user-install",
340                 OptionCategories.PLATFORM_WIN, () -> {
341             setOptionValue("win-per-user-install", false);
342         }),
343 
344         WIN_DIR_CHOOSER ("win-dir-chooser",
345                 OptionCategories.PLATFORM_WIN, () -> {
346             setOptionValue("win-dir-chooser", true);
347         }),
348 
349         WIN_UPGRADE_UUID ("win-upgrade-uuid",
350                 OptionCategories.PLATFORM_WIN),
351 
352         WIN_CONSOLE_HINT ("win-console", OptionCategories.PLATFORM_WIN, () -> {
353             setOptionValue("win-console", true);
354         }),
355 
356         LINUX_BUNDLE_NAME ("linux-package-name",
357                 OptionCategories.PLATFORM_LINUX),
358 
359         LINUX_DEB_MAINTAINER ("linux-deb-maintainer",
360                 OptionCategories.PLATFORM_LINUX),
361 
362         LINUX_CATEGORY ("linux-app-category",
363                 OptionCategories.PLATFORM_LINUX),
364 
365         LINUX_RPM_LICENSE_TYPE ("linux-rpm-license-type",
366                 OptionCategories.PLATFORM_LINUX),
367 
368         LINUX_PACKAGE_DEPENDENCIES ("linux-package-deps",
369                 OptionCategories.PLATFORM_LINUX),
370 
371         LINUX_SHORTCUT_HINT ("linux-shortcut",
372                 OptionCategories.PLATFORM_LINUX, () -> {
373             setOptionValue("linux-shortcut", true);
374         }),
375 
376         LINUX_MENU_GROUP ("linux-menu-group", OptionCategories.PLATFORM_LINUX);
377 
378         private final String id;
379         private final String shortId;
380         private final OptionCategories category;
381         private final Runnable action;
382         private static Arguments argContext;
383 
CLIOptions(String id, OptionCategories category)384         private CLIOptions(String id, OptionCategories category) {
385             this(id, null, category, null);
386         }
387 
CLIOptions(String id, String shortId, OptionCategories category)388         private CLIOptions(String id, String shortId,
389                            OptionCategories category) {
390             this(id, shortId, category, null);
391         }
392 
CLIOptions(String id, OptionCategories category, Runnable action)393         private CLIOptions(String id,
394                 OptionCategories category, Runnable action) {
395             this(id, null, category, action);
396         }
397 
CLIOptions(String id, String shortId, OptionCategories category, Runnable action)398         private CLIOptions(String id, String shortId,
399                            OptionCategories category, Runnable action) {
400             this.id = id;
401             this.shortId = shortId;
402             this.action = action;
403             this.category = category;
404         }
405 
setContext(Arguments context)406         static void setContext(Arguments context) {
407             argContext = context;
408         }
409 
context()410         public static Arguments context() {
411             if (argContext != null) {
412                 return argContext;
413             } else {
414                 throw new RuntimeException("Argument context is not set.");
415             }
416         }
417 
getId()418         public String getId() {
419             return this.id;
420         }
421 
getIdWithPrefix()422         String getIdWithPrefix() {
423             return "--" + this.id;
424         }
425 
getShortIdWithPrefix()426         String getShortIdWithPrefix() {
427             return this.shortId == null ? null : "-" + this.shortId;
428         }
429 
execute()430         void execute() {
431             if (action != null) {
432                 action.run();
433             } else {
434                 defaultAction();
435             }
436         }
437 
defaultAction()438         private void defaultAction() {
439             context().deployParams.addBundleArgument(id, popArg());
440         }
441 
setOptionValue(String option, Object value)442         private static void setOptionValue(String option, Object value) {
443             context().deployParams.addBundleArgument(option, value);
444         }
445 
popArg()446         private static String popArg() {
447             nextArg();
448             return (context().pos >= context().argList.size()) ?
449                             "" : context().argList.get(context().pos);
450         }
451 
getArg()452         private static String getArg() {
453             return (context().pos >= context().argList.size()) ?
454                         "" : context().argList.get(context().pos);
455         }
456 
nextArg()457         private static void nextArg() {
458             context().pos++;
459         }
460 
hasNextArg()461         private static boolean hasNextArg() {
462             return context().pos < context().argList.size();
463         }
464     }
465 
466     enum OptionCategories {
467         MODULAR,
468         PROPERTY,
469         PLATFORM_MAC,
470         PLATFORM_WIN,
471         PLATFORM_LINUX;
472     }
473 
processArguments()474     public boolean processArguments() {
475         try {
476 
477             // init context of arguments
478             CLIOptions.setContext(this);
479 
480             // parse cmd line
481             String arg;
482             CLIOptions option;
483             for (; CLIOptions.hasNextArg(); CLIOptions.nextArg()) {
484                 arg = CLIOptions.getArg();
485                 if ((option = toCLIOption(arg)) != null) {
486                     // found a CLI option
487                     allOptions.add(option);
488                     option.execute();
489                 } else {
490                     throw new PackagerException("ERR_InvalidOption", arg);
491                 }
492             }
493 
494             if (hasMainJar && !hasMainClass) {
495                 // try to get main-class from manifest
496                 String mainClass = getMainClassFromManifest();
497                 if (mainClass != null) {
498                     CLIOptions.setOptionValue(
499                             CLIOptions.APPCLASS.getId(), mainClass);
500                 }
501             }
502 
503             // display error for arguments that are not supported
504             // for current configuration.
505 
506             validateArguments();
507 
508             List<Map<String, ? super Object>> launchersAsMap =
509                     new ArrayList<>();
510 
511             for (AddLauncherArguments sl : addLaunchers) {
512                 launchersAsMap.add(sl.getLauncherMap());
513             }
514 
515             deployParams.addBundleArgument(
516                     StandardBundlerParam.ADD_LAUNCHERS.getID(),
517                     launchersAsMap);
518 
519             // at this point deployParams should be already configured
520 
521             deployParams.validate();
522 
523             BundleParams bp = deployParams.getBundleParams();
524 
525             // validate name(s)
526             ArrayList<String> usedNames = new ArrayList<String>();
527             usedNames.add(bp.getName()); // add main app name
528 
529             for (AddLauncherArguments sl : addLaunchers) {
530                 Map<String, ? super Object> slMap = sl.getLauncherMap();
531                 String slName =
532                         (String) slMap.get(Arguments.CLIOptions.NAME.getId());
533                 if (slName == null) {
534                     throw new PackagerException("ERR_NoAddLauncherName");
535                 }
536                 // same rules apply to additional launcher names as app name
537                 DeployParams.validateName(slName, false);
538                 for (String usedName : usedNames) {
539                     if (slName.equals(usedName)) {
540                         throw new PackagerException("ERR_NoUniqueName");
541                     }
542                 }
543                 usedNames.add(slName);
544             }
545             if (runtimeInstaller && bp.getName() == null) {
546                 throw new PackagerException("ERR_NoJreInstallerName");
547             }
548 
549             generateBundle(bp.getBundleParamsAsMap());
550             return true;
551         } catch (Exception e) {
552             if (Log.isVerbose()) {
553                 Log.verbose(e);
554             } else {
555                 String msg1 = e.getMessage();
556                 Log.error(msg1);
557                 if (e.getCause() != null && e.getCause() != e) {
558                     String msg2 = e.getCause().getMessage();
559                     if (msg2 != null && !msg1.contains(msg2)) {
560                         Log.error(msg2);
561                     }
562                 }
563             }
564             return false;
565         }
566     }
567 
validateArguments()568     private void validateArguments() throws PackagerException {
569         String type = deployParams.getTargetFormat();
570         String ptype = (type != null) ? type : "default";
571         boolean imageOnly = deployParams.isTargetAppImage();
572         boolean hasAppImage = allOptions.contains(
573                 CLIOptions.PREDEFINED_APP_IMAGE);
574         boolean hasRuntime = allOptions.contains(
575                 CLIOptions.PREDEFINED_RUNTIME_IMAGE);
576         boolean installerOnly = !imageOnly && hasAppImage;
577         runtimeInstaller = !imageOnly && hasRuntime && !hasAppImage &&
578                 !hasMainModule && !hasMainJar;
579 
580         for (CLIOptions option : allOptions) {
581             if (!ValidOptions.checkIfSupported(option)) {
582                 // includes option valid only on different platform
583                 throw new PackagerException("ERR_UnsupportedOption",
584                         option.getIdWithPrefix());
585             }
586             if (imageOnly) {
587                 if (!ValidOptions.checkIfImageSupported(option)) {
588                     throw new PackagerException("ERR_InvalidTypeOption",
589                         option.getIdWithPrefix(), type);
590                 }
591             } else if (installerOnly || runtimeInstaller) {
592                 if (!ValidOptions.checkIfInstallerSupported(option)) {
593                     if (runtimeInstaller) {
594                         throw new PackagerException("ERR_NoInstallerEntryPoint",
595                             option.getIdWithPrefix());
596                     } else {
597                         throw new PackagerException("ERR_InvalidTypeOption",
598                             option.getIdWithPrefix(), ptype);
599                    }
600                 }
601             }
602         }
603         if (hasRuntime) {
604             if (hasAppImage) {
605                 // note --runtime-image is only for image or runtime installer.
606                 throw new PackagerException("ERR_MutuallyExclusiveOptions",
607                         CLIOptions.PREDEFINED_RUNTIME_IMAGE.getIdWithPrefix(),
608                         CLIOptions.PREDEFINED_APP_IMAGE.getIdWithPrefix());
609             }
610             if (allOptions.contains(CLIOptions.ADD_MODULES)) {
611                 throw new PackagerException("ERR_MutuallyExclusiveOptions",
612                         CLIOptions.PREDEFINED_RUNTIME_IMAGE.getIdWithPrefix(),
613                         CLIOptions.ADD_MODULES.getIdWithPrefix());
614             }
615             if (allOptions.contains(CLIOptions.BIND_SERVICES)) {
616                 throw new PackagerException("ERR_MutuallyExclusiveOptions",
617                         CLIOptions.PREDEFINED_RUNTIME_IMAGE.getIdWithPrefix(),
618                         CLIOptions.BIND_SERVICES.getIdWithPrefix());
619             }
620             if (allOptions.contains(CLIOptions.JLINK_OPTIONS)) {
621                 throw new PackagerException("ERR_MutuallyExclusiveOptions",
622                         CLIOptions.PREDEFINED_RUNTIME_IMAGE.getIdWithPrefix(),
623                         CLIOptions.JLINK_OPTIONS.getIdWithPrefix());
624             }
625         }
626         if (hasMainJar && hasMainModule) {
627             throw new PackagerException("ERR_BothMainJarAndModule");
628         }
629         if (imageOnly && !hasMainJar && !hasMainModule) {
630             throw new PackagerException("ERR_NoEntryPoint");
631         }
632     }
633 
getPlatformBundler()634     private jdk.incubator.jpackage.internal.Bundler getPlatformBundler() {
635         boolean appImage = deployParams.isTargetAppImage();
636         String type = deployParams.getTargetFormat();
637         String bundleType = (appImage ?  "IMAGE" : "INSTALLER");
638 
639         for (jdk.incubator.jpackage.internal.Bundler bundler :
640                 Bundlers.createBundlersInstance().getBundlers(bundleType)) {
641             if (type == null) {
642                  if (bundler.isDefault()
643                          && bundler.supported(runtimeInstaller)) {
644                      return bundler;
645                  }
646             } else {
647                  if ((appImage || type.equalsIgnoreCase(bundler.getID()))
648                          && bundler.supported(runtimeInstaller)) {
649                      return bundler;
650                  }
651             }
652         }
653         return null;
654     }
655 
generateBundle(Map<String,? super Object> params)656     private void generateBundle(Map<String,? super Object> params)
657             throws PackagerException {
658 
659         boolean bundleCreated = false;
660 
661         // the temp dir needs to be fetched from the params early,
662         // to prevent each copy of the params (such as may be used for
663         // additional launchers) from generating a separate temp dir when
664         // the default is used (the default is a new temp directory)
665         // The bundler.cleanup() below would not otherwise be able to
666         // clean these extra (and unneeded) temp directories.
667         StandardBundlerParam.TEMP_ROOT.fetchFrom(params);
668 
669         // determine what bundler to run
670         jdk.incubator.jpackage.internal.Bundler bundler = getPlatformBundler();
671 
672         if (bundler == null) {
673             throw new PackagerException("ERR_InvalidInstallerType",
674                       deployParams.getTargetFormat());
675         }
676 
677         Map<String, ? super Object> localParams = new HashMap<>(params);
678         try {
679             bundler.validate(localParams);
680             File result = bundler.execute(localParams, deployParams.outdir);
681             if (result == null) {
682                 throw new PackagerException("MSG_BundlerFailed",
683                         bundler.getID(), bundler.getName());
684             }
685             Log.verbose(MessageFormat.format(
686                     I18N.getString("message.bundle-created"),
687                     bundler.getName()));
688         } catch (ConfigException e) {
689             Log.verbose(e);
690             if (e.getAdvice() != null)  {
691                 throw new PackagerException(e, "MSG_BundlerConfigException",
692                         bundler.getName(), e.getMessage(), e.getAdvice());
693             } else {
694                 throw new PackagerException(e,
695                        "MSG_BundlerConfigExceptionNoAdvice",
696                         bundler.getName(), e.getMessage());
697             }
698         } catch (RuntimeException re) {
699             Log.verbose(re);
700             throw new PackagerException(re, "MSG_BundlerRuntimeException",
701                     bundler.getName(), re.toString());
702         } finally {
703             if (userProvidedBuildRoot) {
704                 Log.verbose(MessageFormat.format(
705                         I18N.getString("message.debug-working-directory"),
706                         (new File(buildRoot)).getAbsolutePath()));
707             } else {
708                 // always clean up the temporary directory created
709                 // when --temp option not used.
710                 bundler.cleanup(localParams);
711             }
712         }
713     }
714 
toCLIOption(String arg)715     static CLIOptions toCLIOption(String arg) {
716         CLIOptions option;
717         if ((option = argIds.get(arg)) == null) {
718             option = argShortIds.get(arg);
719         }
720         return option;
721     }
722 
getPropertiesFromFile(String filename)723     static Map<String, String> getPropertiesFromFile(String filename) {
724         Map<String, String> map = new HashMap<>();
725         // load properties file
726         File file = new File(filename);
727         Properties properties = new Properties();
728         try (FileInputStream in = new FileInputStream(file)) {
729             properties.load(in);
730         } catch (IOException e) {
731             Log.error("Exception: " + e.getMessage());
732         }
733 
734         for (final String name: properties.stringPropertyNames()) {
735             map.put(name, properties.getProperty(name));
736         }
737 
738         return map;
739     }
740 
getArgumentList(String inputString)741     static List<String> getArgumentList(String inputString) {
742         List<String> list = new ArrayList<>();
743         if (inputString == null || inputString.isEmpty()) {
744              return list;
745         }
746 
747         // The "pattern" regexp attempts to abide to the rule that
748         // strings are delimited by whitespace unless surrounded by
749         // quotes, then it is anything (including spaces) in the quotes.
750         Matcher m = pattern.matcher(inputString);
751         while (m.find()) {
752             String s = inputString.substring(m.start(), m.end()).trim();
753             // Ensure we do not have an empty string. trim() will take care of
754             // whitespace only strings. The regex preserves quotes and escaped
755             // chars so we need to clean them before adding to the List
756             if (!s.isEmpty()) {
757                 list.add(unquoteIfNeeded(s));
758             }
759         }
760         return list;
761     }
762 
putUnlessNull(Map<String, ? super Object> params, String param, Object value)763     static void putUnlessNull(Map<String, ? super Object> params,
764             String param, Object value) {
765         if (value != null) {
766             params.put(param, value);
767         }
768     }
769 
unquoteIfNeeded(String in)770     private static String unquoteIfNeeded(String in) {
771         if (in == null) {
772             return null;
773         }
774 
775         if (in.isEmpty()) {
776             return "";
777         }
778 
779         // Use code points to preserve non-ASCII chars
780         StringBuilder sb = new StringBuilder();
781         int codeLen = in.codePointCount(0, in.length());
782         int quoteChar = -1;
783         for (int i = 0; i < codeLen; i++) {
784             int code = in.codePointAt(i);
785             if (code == '"' || code == '\'') {
786                 // If quote is escaped make sure to copy it
787                 if (i > 0 && in.codePointAt(i - 1) == '\\') {
788                     sb.deleteCharAt(sb.length() - 1);
789                     sb.appendCodePoint(code);
790                     continue;
791                 }
792                 if (quoteChar != -1) {
793                     if (code == quoteChar) {
794                         // close quote, skip char
795                         quoteChar = -1;
796                     } else {
797                         sb.appendCodePoint(code);
798                     }
799                 } else {
800                     // opening quote, skip char
801                     quoteChar = code;
802                 }
803             } else {
804                 sb.appendCodePoint(code);
805             }
806         }
807         return sb.toString();
808     }
809 
getMainClassFromManifest()810     private String getMainClassFromManifest() {
811         if (mainJarPath == null ||
812             input == null ) {
813             return null;
814         }
815 
816         JarFile jf;
817         try {
818             File file = new File(input, mainJarPath);
819             if (!file.exists()) {
820                 return null;
821             }
822             jf = new JarFile(file);
823             Manifest m = jf.getManifest();
824             Attributes attrs = (m != null) ? m.getMainAttributes() : null;
825             if (attrs != null) {
826                 return attrs.getValue(Attributes.Name.MAIN_CLASS);
827             }
828         } catch (IOException ignore) {}
829         return null;
830     }
831 
showDeprecation(String option)832     private static void showDeprecation(String option) {
833         Log.error(MessageFormat.format(I18N.getString("warning.deprecation"),
834                 option));
835     }
836 }
837