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