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