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 // check that we really add _another_ value to the list 282 setOptionValue("file-associations", args); 283 }), 284 285 SECONDARY_LAUNCHER ("secondary-launcher", 286 OptionCategories.PROPERTY, () -> { 287 context().secondaryLaunchers.add( 288 new SecondaryLauncherArguments(popArg())); 289 }), 290 291 BUILD_ROOT ("build-root", OptionCategories.PROPERTY), 292 293 INSTALL_DIR ("install-dir", OptionCategories.PROPERTY), 294 295 ECHO_MODE ("echo-mode", OptionCategories.PROPERTY, () -> { 296 echo = true; 297 setOptionValue("echo-mode", true); 298 }), 299 300 PREDEFINED_APP_IMAGE ("app-image", OptionCategories.PROPERTY), 301 302 PREDEFINED_RUNTIME_IMAGE ("runtime-image", OptionCategories.PROPERTY), 303 304 MAIN_JAR ("main-jar", "j", OptionCategories.PROPERTY, () -> { 305 context().mainJarPath = popArg(); 306 context().hasMainJar = true; 307 setOptionValue("main-jar", context().mainJarPath); 308 }), 309 310 MODULE ("module", "m", OptionCategories.MODULAR, () -> { 311 context().hasMainModule = true; 312 setOptionValue("module", popArg()); 313 }), 314 315 ADD_MODULES ("add-modules", OptionCategories.MODULAR), 316 317 MODULE_PATH ("module-path", "p", OptionCategories.MODULAR), 318 319 LIMIT_MODULES ("limit-modules", OptionCategories.MODULAR), 320 321 MAC_SIGN ("mac-sign", "s", OptionCategories.PLATFORM_MAC, () -> { 322 setOptionValue("mac-sign", true); 323 }), 324 325 MAC_BUNDLE_NAME ("mac-bundle-name", OptionCategories.PLATFORM_MAC), 326 327 MAC_BUNDLE_IDENTIFIER("mac-bundle-identifier", 328 OptionCategories.PLATFORM_MAC), 329 330 MAC_APP_STORE_CATEGORY ("mac-app-store-category", 331 OptionCategories.PLATFORM_MAC), 332 333 MAC_BUNDLE_SIGNING_PREFIX ("mac-bundle-signing-prefix", 334 OptionCategories.PLATFORM_MAC), 335 336 MAC_SIGNING_KEY_NAME ("mac-signing-key-user-name", 337 OptionCategories.PLATFORM_MAC), 338 339 MAC_SIGNING_KEYCHAIN ("mac-signing-keychain", 340 OptionCategories.PLATFORM_MAC), 341 342 MAC_APP_STORE_ENTITLEMENTS ("mac-app-store-entitlements", 343 OptionCategories.PLATFORM_MAC), 344 345 WIN_MENU_HINT ("win-menu", OptionCategories.PLATFORM_WIN, () -> { 346 setOptionValue("win-menu", true); 347 }), 348 349 WIN_MENU_GROUP ("win-menu-group", OptionCategories.PLATFORM_WIN), 350 351 WIN_SHORTCUT_HINT ("win-shortcut", OptionCategories.PLATFORM_WIN, () -> { 352 setOptionValue("win-shortcut", true); 353 }), 354 355 WIN_PER_USER_INSTALLATION ("win-per-user-install", 356 OptionCategories.PLATFORM_WIN, () -> { 357 setOptionValue("win-per-user-install", false); 358 }), 359 360 WIN_DIR_CHOOSER ("win-dir-chooser", OptionCategories.PLATFORM_WIN, () -> { 361 setOptionValue("win-dir-chooser", true); 362 }), 363 364 WIN_REGISTRY_NAME ("win-registry-name", OptionCategories.PLATFORM_WIN), 365 366 WIN_MSI_UPGRADE_UUID ("win-upgrade-uuid", OptionCategories.PLATFORM_WIN), 367 368 LINUX_BUNDLE_NAME ("linux-bundle-name", OptionCategories.PLATFORM_LINUX), 369 370 LINUX_DEB_MAINTAINER ("linux-deb-maintainer", 371 OptionCategories.PLATFORM_LINUX), 372 373 LINUX_RPM_LICENSE_TYPE ("linux-rpm-license-type", 374 OptionCategories.PLATFORM_LINUX), 375 376 LINUX_PACKAGE_DEPENDENCIES ("linux-package-deps", 377 OptionCategories.PLATFORM_LINUX); 378 379 private final String id; 380 private final String shortId; 381 private final OptionCategories category; 382 private final ArgAction action; 383 private static Arguments argContext; 384 385 private CLIOptions(String id, OptionCategories category) { 386 this(id, null, category, null); 387 } 388 389 private CLIOptions(String id, String shortId, 390 OptionCategories category) { 391 this(id, shortId, category, null); 392 } 393 394 private CLIOptions(String id, OptionCategories category, ArgAction action) { 395 this(id, null, category, action); 396 } 397 398 private CLIOptions(String id, String shortId, 399 OptionCategories category, ArgAction action) { 400 this.id = id; 401 this.shortId = shortId; 402 this.action = action; 403 this.category = category; 404 } 405 406 public static void setContext(Arguments context) { 407 argContext = context; 408 } 409 410 private static Arguments context() { 411 if (argContext != null) { 412 return argContext; 413 } else { 414 throw new RuntimeException("Argument context is not set."); 415 } 416 } 417 418 public String getId() { 419 return this.id; 420 } 421 422 public String getIdWithPrefix() { 423 String prefix = isMode() ? "create-" : "--"; 424 return prefix + this.id; 425 } 426 427 public String getShortIdWithPrefix() { 428 return this.shortId == null ? null : "-" + this.shortId; 429 } 430 431 public void execute() { 432 if (action != null) { 433 action.execute(); 434 } else { 435 defaultAction(); 436 } 437 } 438 439 public boolean isMode() { 440 return category == OptionCategories.MODE; 441 } 442 443 public OptionCategories getCategory() { 444 return category; 445 } 446 447 private void defaultAction() { 448 context().deployParams.addBundleArgument(id, popArg()); 449 } 450 451 private static void setOptionValue(String option, Object value) { 452 context().deployParams.addBundleArgument(option, value); 453 } 454 455 private static String popArg() { 456 nextArg(); 457 return (context().pos >= context().argList.size()) ? 458 "" : context().argList.get(context().pos); 459 } 460 461 private static String getArg() { 462 return (context().pos >= context().argList.size()) ? 463 "" : context().argList.get(context().pos); 464 } 465 466 private static void nextArg() { 467 context().pos++; 468 } 469 470 private static void prevArg() { 471 context().pos--; 472 } 473 474 private static boolean hasNextArg() { 475 return context().pos < context().argList.size(); 476 } 477 } 478 479 public enum OptionCategories { 480 MODE, 481 MODULAR, 482 PROPERTY, 483 PLATFORM_MAC, 484 PLATFORM_WIN, 485 PLATFORM_LINUX; 486 } 487 488 private void initArgumentList(String[] args) { 489 argList = new ArrayList<>(Arrays.asList(args)); 490 pos = 0; 491 492 deployParams = new DeployParams(); 493 bundleType = Bundler.BundleType.NONE; 494 495 allOptions = new ArrayList<>(); 496 497 secondaryLaunchers = new ArrayList<>(); 498 } 499 500 public boolean processArguments() throws Exception { 501 try { 502 503 // init context of arguments 504 CLIOptions.setContext(this); 505 506 // parse cmd line 507 String arg; 508 CLIOptions option; 509 for (; CLIOptions.hasNextArg(); CLIOptions.nextArg()) { 510 arg = CLIOptions.getArg(); 511 // check if it's a CLI option 512 if ((option = toCLIOption(arg)) != null) { 513 allOptions.add(option); 514 option.execute(); 515 } else { 516 Log.info("Illegal argument ["+arg+"]"); 517 } 518 } 519 520 if (allOptions.isEmpty() || !allOptions.get(0).isMode()) { 521 // first argument should always be a mode. 522 Log.info("ERROR: Mode is not specified"); 523 return false; 524 } 525 526 if (!hasMainJar && !hasMainModule && !hasMainClass && !jreInstaller) { 527 Log.info("ERROR: Main jar or main class or main module must be specified."); 528 } else if (!hasMainModule && !hasMainClass) { 529 // try to get main-class from manifest 530 String mainClass = getMainClassFromManifest(); 531 if (mainClass != null) { 532 CLIOptions.setOptionValue(CLIOptions.APPCLASS.getId(), mainClass); 533 } 534 } 535 536 // display warning for arguments that are not supported 537 // for current configuration. 538 539 validateArguments(); 540 541 addResources(deployParams, input, files); 542 543 deployParams.setBundleType(bundleType); 544 545 List<Map<String, ? super Object>> launchersAsMap = new ArrayList<>(); 546 547 for (SecondaryLauncherArguments sl : secondaryLaunchers) { 548 launchersAsMap.add(sl.getLauncherMap()); 549 } 550 551 deployParams.addBundleArgument( 552 StandardBundlerParam.SECONDARY_LAUNCHERS.getID(), 553 launchersAsMap); 554 555 // at this point deployParams should be already configured 556 557 deployParams.validate(); 558 559 BundleParams bp = deployParams.getBundleParams(); 560 561 generateBundle(bp.getBundleParamsAsMap()); 562 } catch (Exception e) { 563 if (verbose) { 564 throw e; 565 } else { 566 System.err.println(e.getMessage()); 567 if (e.getCause() != null && e.getCause() != e) { 568 System.err.println(e.getCause().getMessage()); 569 } 570 System.exit(-1); 571 } 572 } 573 return true; 574 } 575 576 private void validateArguments() { 577 CLIOptions mode = allOptions.get(0); 578 for (CLIOptions option : allOptions) { 579 if(!ValidOptions.checkIfSupported(mode, option)) { 580 System.out.println("WARNING: argument [" + option.getId() + "] is not " 581 + "supported for current configuration."); 582 } 583 } 584 } 585 586 private List<jdk.packager.internal.Bundler> getPlatformBundlers() { 587 588 if (platformBundlers != null) { 589 return platformBundlers; 590 } 591 592 platformBundlers = new ArrayList<>(); 593 for (jdk.packager.internal.Bundler bundler : 594 Bundlers.createBundlersInstance().getBundlers( 595 bundleType.toString())) { 596 if (hasTargetFormat && deployParams.getTargetFormat() != null && 597 !deployParams.getTargetFormat().equalsIgnoreCase( 598 bundler.getID())) { 599 continue; 600 } 601 if (bundler.supported()) { 602 platformBundlers.add(bundler); 603 } 604 } 605 606 return platformBundlers; 607 } 608 609 private void generateBundle(Map<String,? super Object> params) throws PackagerException { 610 for (jdk.packager.internal.Bundler bundler : getPlatformBundlers()) { 611 Map<String, ? super Object> localParams = new HashMap<>(params); 612 try { 613 if (bundler.validate(localParams)) { 614 File result = bundler.execute(localParams, deployParams.outdir); 615 bundler.cleanup(localParams); 616 if (result == null) { 617 throw new PackagerException("MSG_BundlerFailed", 618 bundler.getID(), bundler.getName()); 619 } 620 } 621 } catch (UnsupportedPlatformException e) { 622 Log.debug(MessageFormat.format( 623 I18N.getString("MSG_BundlerPlatformException"), 624 bundler.getName())); 625 } catch (ConfigException e) { 626 Log.debug(e); 627 if (e.getAdvice() != null) { 628 Log.info(MessageFormat.format( 629 I18N.getString("MSG_BundlerConfigException"), 630 bundler.getName(), e.getMessage(), e.getAdvice())); 631 } else { 632 Log.info(MessageFormat.format( 633 I18N.getString("MSG_BundlerConfigExceptionNoAdvice"), 634 bundler.getName(), e.getMessage())); 635 } 636 } catch (RuntimeException re) { 637 Log.info(MessageFormat.format( 638 I18N.getString("MSG_BundlerRuntimeException"), 639 bundler.getName(), re.toString())); 640 Log.debug(re); 641 } 642 } 643 } 644 645 private void addResources(CommonParams commonParams, 646 String inputdir, List<String> inputfiles) { 647 648 if (inputdir == null || inputdir.isEmpty()) { 649 return; 650 } 651 652 File baseDir = new File(inputdir); 653 654 if (!baseDir.isDirectory()) { 655 Log.info("Unable to add resources: \"-srcdir\" is not a directory."); 656 return; 657 } 658 659 List<String> fileNames; 660 if (inputfiles != null) { 661 fileNames = inputfiles; 662 } else { 663 // "-files" is omitted, all files in input cdir (which 664 // is a mandatory argument in this case) will be packaged. 665 fileNames = new ArrayList<>(); 666 try (Stream<Path> files = Files.list(baseDir.toPath())) { 667 files.forEach(file -> fileNames.add(file.getFileName().toString())); 668 } catch (IOException e) { 669 Log.info("Unable to add resources: " + e.getMessage()); 670 } 671 } 672 fileNames.forEach(file -> commonParams.addResource(baseDir, file)); 673 setClasspath(fileNames); 674 } 675 676 private void setClasspath(List<String> inputfiles) { 677 String classpath = ""; 678 for (String file : inputfiles) { 679 if (file.endsWith(".jar")) { 680 classpath += file; 681 classpath += File.pathSeparator; 682 } 683 } 684 deployParams.addBundleArgument( 685 StandardBundlerParam.CLASSPATH.getID(), classpath); 686 } 687 688 public static boolean isCLIOption(String arg) { 689 return toCLIOption(arg) != null; 690 } 691 692 public static CLIOptions toCLIOption(String arg) { 693 CLIOptions option; 694 if ((option = argIds.get(arg)) == null) { 695 option = argShortIds.get(arg); 696 } 697 return option; 698 } 699 700 static Map<String, String> getArgumentMap(String inputString) { 701 Map<String, String> map = new HashMap<>(); 702 List<String> list = getArgumentList(inputString); 703 for (String pair : list) { 704 int equals = pair.indexOf("="); 705 if (equals != -1) { 706 String key = pair.substring(0, equals); 707 String value = pair.substring(equals+1, pair.length()); 708 map.put(key, value); 709 } 710 } 711 return map; 712 } 713 714 static Map<String, String> getPropertiesFromFile(String filename) { 715 Map<String, String> map = new HashMap<>(); 716 // load properties file 717 File file = new File(filename); 718 Properties properties = new Properties(); 719 try (FileInputStream in = new FileInputStream(file)) { 720 properties.load(in); 721 in.close(); 722 } catch (IOException e) { 723 Log.info("Exception: " + e.getMessage()); 724 } 725 726 for (final String name: properties.stringPropertyNames()) { 727 map.put(name, properties.getProperty(name)); 728 } 729 730 return map; 731 } 732 733 static List<String> getArgumentList(String inputString) { 734 List<String> list = new ArrayList<>(); 735 if (inputString == null || inputString.isEmpty()) { 736 return list; 737 } 738 739 // The "pattern" regexp attempts to abide to the rule that 740 // strings are delimited by whitespace unless surrounded by 741 // quotes, then it is anything (including spaces) in the quotes. 742 Matcher m = pattern.matcher(inputString); 743 while (m.find()) { 744 String s = inputString.substring(m.start(), m.end()).trim(); 745 // Ensure we do not have an empty string. trim() will take care of 746 // whitespace only strings. The regex preserves quotes and escaped 747 // chars so we need to clean them before adding to the List 748 if (!s.isEmpty()) { 749 list.add(unquoteIfNeeded(s)); 750 } 751 } 752 return list; 753 } 754 755 private static String unquoteIfNeeded(String in) { 756 if (in == null) { 757 return null; 758 } 759 760 if (in.isEmpty()) { 761 return ""; 762 } 763 764 // Use code points to preserve non-ASCII chars 765 StringBuilder sb = new StringBuilder(); 766 int codeLen = in.codePointCount(0, in.length()); 767 int quoteChar = -1; 768 for (int i = 0; i < codeLen; i++) { 769 int code = in.codePointAt(i); 770 if (code == '"' || code == '\'') { 771 // If quote is escaped make sure to copy it 772 if (i > 0 && in.codePointAt(i - 1) == '\\') { 773 sb.deleteCharAt(sb.length() - 1); 774 sb.appendCodePoint(code); 775 continue; 776 } 777 if (quoteChar != -1) { 778 if (code == quoteChar) { 779 // close quote, skip char 780 quoteChar = -1; 781 } else { 782 sb.appendCodePoint(code); 783 } 784 } else { 785 // opening quote, skip char 786 quoteChar = code; 787 } 788 } else { 789 sb.appendCodePoint(code); 790 } 791 } 792 return sb.toString(); 793 } 794 795 private String getMainClassFromManifest() { 796 if (mainJarPath == null || 797 input == null ) { 798 return null; 799 } 800 801 JarFile jf; 802 try { 803 File file = new File(input, mainJarPath); 804 if (!file.exists()) { 805 return null; 806 } 807 jf = new JarFile(file); 808 Manifest m = jf.getManifest(); 809 Attributes attrs = (m != null) ? m.getMainAttributes() : null; 810 if (attrs != null) { 811 return attrs.getValue(Attributes.Name.MAIN_CLASS); 812 } 813 } catch (IOException ignore) {} 814 return null; 815 } 816 817 public static boolean echoMode() { 818 return echo; 819 } 820 821 public static boolean verbose() { 822 return verbose; 823 } 824 825 public static boolean isJreInstaller() { 826 return jreInstaller; 827 } 828 }