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