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