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 String spec = popArg(); 267 String name = null; 268 String filename = spec; 269 if (spec.contains("=")) { 270 String[] values = spec.split("=", 2); 271 name = values[0]; 272 filename = values[1]; 273 } 274 context().addLaunchers.add( 275 new AddLauncherArguments(name, filename)); 276 }), 277 278 TEMP_ROOT ("temp-root", OptionCategories.PROPERTY, () -> { 279 context().buildRoot = popArg(); 280 context().userProvidedBuildRoot = true; 281 setOptionValue("temp-root", context().buildRoot); 282 }), 283 284 INSTALL_DIR ("install-dir", OptionCategories.PROPERTY), 285 286 PREDEFINED_APP_IMAGE ("app-image", OptionCategories.PROPERTY, ()-> { 287 setOptionValue("app-image", popArg()); 288 context().hasAppImage = true; 289 }), 290 291 PREDEFINED_RUNTIME_IMAGE ("runtime-image", OptionCategories.PROPERTY), 292 293 MAIN_JAR ("main-jar", OptionCategories.PROPERTY, () -> { 294 context().mainJarPath = popArg(); 295 context().hasMainJar = true; 296 setOptionValue("main-jar", context().mainJarPath); 297 }), 298 299 MODULE ("module", "m", OptionCategories.MODULAR, () -> { 300 context().hasMainModule = true; 301 setOptionValue("module", popArg()); 302 }), 303 304 ADD_MODULES ("add-modules", OptionCategories.MODULAR), 305 306 MODULE_PATH ("module-path", "p", OptionCategories.MODULAR), 307 308 MAC_SIGN ("mac-sign", "s", OptionCategories.PLATFORM_MAC, () -> { 309 setOptionValue("mac-sign", true); 310 }), 311 312 MAC_BUNDLE_NAME ("mac-bundle-name", OptionCategories.PLATFORM_MAC), 313 314 MAC_BUNDLE_IDENTIFIER("mac-bundle-identifier", 315 OptionCategories.PLATFORM_MAC), 316 317 MAC_APP_STORE_CATEGORY ("mac-app-store-category", 318 OptionCategories.PLATFORM_MAC), 319 320 MAC_BUNDLE_SIGNING_PREFIX ("mac-bundle-signing-prefix", 321 OptionCategories.PLATFORM_MAC), 322 323 MAC_SIGNING_KEY_NAME ("mac-signing-key-user-name", 324 OptionCategories.PLATFORM_MAC), 325 326 MAC_SIGNING_KEYCHAIN ("mac-signing-keychain", 327 OptionCategories.PLATFORM_MAC), 328 329 MAC_APP_STORE_ENTITLEMENTS ("mac-app-store-entitlements", 330 OptionCategories.PLATFORM_MAC), 331 332 WIN_MENU_HINT ("win-menu", OptionCategories.PLATFORM_WIN, () -> { 333 setOptionValue("win-menu", true); 334 }), 335 336 WIN_MENU_GROUP ("win-menu-group", OptionCategories.PLATFORM_WIN), 337 338 WIN_SHORTCUT_HINT ("win-shortcut", 339 OptionCategories.PLATFORM_WIN, () -> { 340 setOptionValue("win-shortcut", true); 341 }), 342 343 WIN_PER_USER_INSTALLATION ("win-per-user-install", 344 OptionCategories.PLATFORM_WIN, () -> { 345 setOptionValue("win-per-user-install", false); 346 }), 347 348 WIN_DIR_CHOOSER ("win-dir-chooser", 349 OptionCategories.PLATFORM_WIN, () -> { 350 setOptionValue("win-dir-chooser", true); 351 }), 352 353 WIN_REGISTRY_NAME ("win-registry-name", OptionCategories.PLATFORM_WIN), 354 355 WIN_UPGRADE_UUID ("win-upgrade-uuid", 356 OptionCategories.PLATFORM_WIN), 357 358 WIN_CONSOLE_HINT ("win-console", OptionCategories.PLATFORM_WIN, () -> { 359 setOptionValue("win-console", true); 360 }), 361 362 LINUX_BUNDLE_NAME ("linux-bundle-name", 363 OptionCategories.PLATFORM_LINUX), 364 365 LINUX_DEB_MAINTAINER ("linux-deb-maintainer", 366 OptionCategories.PLATFORM_LINUX), 367 368 LINUX_RPM_LICENSE_TYPE ("linux-rpm-license-type", 369 OptionCategories.PLATFORM_LINUX), 370 371 LINUX_PACKAGE_DEPENDENCIES ("linux-package-deps", 372 OptionCategories.PLATFORM_LINUX), 373 374 LINUX_MENU_GROUP ("linux-menu-group", OptionCategories.PLATFORM_LINUX); 375 376 private final String id; 377 private final String shortId; 378 private final OptionCategories category; 379 private final ArgAction action; 380 private static Arguments argContext; 381 382 private CLIOptions(String id, OptionCategories category) { 383 this(id, null, category, null); 384 } 385 386 private CLIOptions(String id, String shortId, 387 OptionCategories category) { 388 this(id, shortId, category, null); 389 } 390 391 private CLIOptions(String id, 392 OptionCategories category, ArgAction action) { 393 this(id, null, category, action); 394 } 395 396 private CLIOptions(String id, String shortId, 397 OptionCategories category, ArgAction action) { 398 this.id = id; 399 this.shortId = shortId; 400 this.action = action; 401 this.category = category; 402 } 403 404 static void setContext(Arguments context) { 405 argContext = context; 406 } 407 408 public static Arguments context() { 409 if (argContext != null) { 410 return argContext; 411 } else { 412 throw new RuntimeException("Argument context is not set."); 413 } 414 } 415 416 public String getId() { 417 return this.id; 418 } 419 420 String getIdWithPrefix() { 421 String prefix = isMode() ? "" : "--"; 422 return prefix + this.id; 423 } 424 425 String getShortIdWithPrefix() { 426 return this.shortId == null ? null : "-" + this.shortId; 427 } 428 429 void execute() { 430 if (action != null) { 431 action.execute(); 432 } else { 433 defaultAction(); 434 } 435 } 436 437 boolean isMode() { 438 return category == OptionCategories.MODE; 439 } 440 441 OptionCategories getCategory() { 442 return category; 443 } 444 445 private void defaultAction() { 446 context().deployParams.addBundleArgument(id, popArg()); 447 } 448 449 private static void setOptionValue(String option, Object value) { 450 context().deployParams.addBundleArgument(option, value); 451 } 452 453 private static String popArg() { 454 nextArg(); 455 return (context().pos >= context().argList.size()) ? 456 "" : context().argList.get(context().pos); 457 } 458 459 private static String getArg() { 460 return (context().pos >= context().argList.size()) ? 461 "" : context().argList.get(context().pos); 462 } 463 464 private static void nextArg() { 465 context().pos++; 466 } 467 468 private static void prevArg() { 469 context().pos--; 470 } 471 472 private static boolean hasNextArg() { 473 return context().pos < context().argList.size(); 474 } 475 } 476 477 enum OptionCategories { 478 MODE, 479 MODULAR, 480 PROPERTY, 481 PLATFORM_MAC, 482 PLATFORM_WIN, 483 PLATFORM_LINUX; 484 } 485 486 public boolean processArguments() throws Exception { 487 try { 488 489 // init context of arguments 490 CLIOptions.setContext(this); 491 492 // parse cmd line 493 String arg; 494 CLIOptions option; 495 for (; CLIOptions.hasNextArg(); CLIOptions.nextArg()) { 496 arg = CLIOptions.getArg(); 497 if ((option = toCLIOption(arg)) != null) { 498 // found a CLI option 499 allOptions.add(option); 500 option.execute(); 501 } else { 502 throw new PackagerException("ERR_InvalidOption", arg); 503 } 504 } 505 506 if (allOptions.isEmpty() || !allOptions.get(0).isMode()) { 507 // first argument should always be a mode. 508 throw new PackagerException("ERR_MissingMode"); 509 } 510 511 if (hasMainJar && !hasMainClass) { 512 // try to get main-class from manifest 513 String mainClass = getMainClassFromManifest(); 514 if (mainClass != null) { 515 CLIOptions.setOptionValue( 516 CLIOptions.APPCLASS.getId(), mainClass); 517 } 518 } 519 520 // display warning for arguments that are not supported 521 // for current configuration. 522 523 validateArguments(); 524 525 addResources(deployParams, input); 526 527 deployParams.setBundleType(bundleType); 528 529 List<Map<String, ? super Object>> launchersAsMap = 530 new ArrayList<>(); 531 532 for (AddLauncherArguments sl : addLaunchers) { 533 launchersAsMap.add(sl.getLauncherMap()); 534 } 535 536 deployParams.addBundleArgument( 537 StandardBundlerParam.ADD_LAUNCHERS.getID(), 538 launchersAsMap); 539 540 // at this point deployParams should be already configured 541 542 deployParams.validate(); 543 544 BundleParams bp = deployParams.getBundleParams(); 545 546 // validate name(s) 547 ArrayList<String> usedNames = new ArrayList<String>(); 548 usedNames.add(bp.getName()); // add main app name 549 550 for (AddLauncherArguments sl : addLaunchers) { 551 Map<String, ? super Object> slMap = sl.getLauncherMap(); 552 String slName = 553 (String) slMap.get(Arguments.CLIOptions.NAME.getId()); 554 if (slName == null) { 555 throw new PackagerException("ERR_NoAddLauncherName"); 556 } 557 // same rules apply to additional launcher names as app name 558 DeployParams.validateName(slName, false); 559 for (String usedName : usedNames) { 560 if (slName.equals(usedName)) { 561 throw new PackagerException("ERR_NoUniqueName"); 562 } 563 } 564 usedNames.add(slName); 565 } 566 if (runtimeInstaller && bp.getName() == null) { 567 throw new PackagerException("ERR_NoJreInstallerName"); 568 } 569 570 return generateBundle(bp.getBundleParamsAsMap()); 571 } catch (Exception e) { 572 if (Log.isVerbose()) { 573 throw e; 574 } else { 575 String msg1 = e.getMessage(); 576 Log.error(msg1); 577 if (e.getCause() != null && e.getCause() != e) { 578 String msg2 = e.getCause().getMessage(); 579 if (!msg1.contains(msg2)) { 580 Log.error(msg2); 581 } 582 } 583 return false; 584 } 585 } 586 } 587 588 private void validateArguments() throws PackagerException { 589 CLIOptions mode = allOptions.get(0); 590 boolean imageOnly = (mode == CLIOptions.CREATE_APP_IMAGE); 591 boolean hasAppImage = allOptions.contains( 592 CLIOptions.PREDEFINED_APP_IMAGE); 593 boolean hasRuntime = allOptions.contains( 594 CLIOptions.PREDEFINED_RUNTIME_IMAGE); 595 boolean installerOnly = !imageOnly && hasAppImage; 596 boolean runtimeInstall = !imageOnly && hasRuntime && !hasAppImage && 597 !hasMainModule && !hasMainJar; 598 599 for (CLIOptions option : allOptions) { 600 if (!ValidOptions.checkIfSupported(option)) { 601 // includes option valid only on different platform 602 throw new PackagerException("ERR_UnsupportedOption", 603 option.getIdWithPrefix()); 604 } 605 if (imageOnly) { 606 if (!ValidOptions.checkIfImageSupported(option)) { 607 throw new PackagerException("ERR_NotImageOption", 608 option.getIdWithPrefix()); 609 } 610 } else if (installerOnly || runtimeInstall) { 611 if (!ValidOptions.checkIfInstallerSupported(option)) { 612 String key = runtimeInstaller ? 613 "ERR_NoInstallerEntryPoint" : "ERR_NotInstallerOption"; 614 throw new PackagerException(key, option.getIdWithPrefix()); 615 } 616 } 617 } 618 if (installerOnly && hasRuntime) { 619 // note --runtime-image is only for image or runtime installer. 620 throw new PackagerException("ERR_NotInstallerOption", 621 CLIOptions.PREDEFINED_RUNTIME_IMAGE.getIdWithPrefix()); 622 } 623 if (hasMainJar && hasMainModule) { 624 throw new PackagerException("ERR_BothMainJarAndModule"); 625 } 626 if (imageOnly && !hasMainJar && !hasMainModule) { 627 throw new PackagerException("ERR_NoEntryPoint"); 628 } 629 } 630 631 private List<jdk.jpackage.internal.Bundler> getPlatformBundlers() { 632 633 if (platformBundlers != null) { 634 return platformBundlers; 635 } 636 637 platformBundlers = new ArrayList<>(); 638 for (jdk.jpackage.internal.Bundler bundler : 639 Bundlers.createBundlersInstance().getBundlers( 640 bundleType.toString())) { 641 if (hasTargetFormat && deployParams.getTargetFormat() != null && 642 !deployParams.getTargetFormat().equalsIgnoreCase( 643 bundler.getID())) { 644 continue; 645 } 646 if (bundler.supported(runtimeInstaller)) { 647 platformBundlers.add(bundler); 648 } 649 } 650 651 return platformBundlers; 652 } 653 654 private boolean generateBundle(Map<String,? super Object> params) 655 throws PackagerException { 656 657 boolean bundleCreated = false; 658 659 // the temp-root needs to be fetched from the params early, 660 // to prevent each copy of the params (such as may be used for 661 // additional launchers) from generating a separate temp-root when 662 // the default is used (the default is a new temp directory) 663 // The bundler.cleanup() below would not otherwise be able to 664 // clean these extra (and unneeded) temp directories. 665 StandardBundlerParam.TEMP_ROOT.fetchFrom(params); 666 List<jdk.jpackage.internal.Bundler> bundlers = getPlatformBundlers(); 667 if (bundlers.isEmpty()) { 668 throw new PackagerException("ERR_InvalidInstallerType", 669 deployParams.getTargetFormat()); 670 } 671 PackagerException pe = null; 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 Log.verbose(MessageFormat.format( 688 I18N.getString("message.bundle-created"), 689 bundler.getName())); 690 } catch (UnsupportedPlatformException upe) { 691 Log.debug(upe); 692 if (pe == null) { 693 pe = new PackagerException(upe, 694 "MSG_BundlerPlatformException", bundler.getName()); 695 } 696 } catch (ConfigException e) { 697 Log.debug(e); 698 if (pe == null) { 699 pe = (e.getAdvice() != null) ? 700 new PackagerException(e, 701 "MSG_BundlerConfigException", 702 bundler.getName(), e.getMessage(), e.getAdvice()) : 703 new PackagerException(e, 704 "MSG_BundlerConfigExceptionNoAdvice", 705 bundler.getName(), e.getMessage()); 706 } 707 } catch (RuntimeException re) { 708 Log.debug(re); 709 if (pe == null) { 710 pe = new PackagerException(re, 711 "MSG_BundlerRuntimeException", 712 bundler.getName(), re.toString()); 713 } 714 } finally { 715 if (userProvidedBuildRoot) { 716 Log.verbose(MessageFormat.format( 717 I18N.getString("message.debug-working-directory"), 718 (new File(buildRoot)).getAbsolutePath())); 719 } 720 } 721 } 722 if (pe != null) { 723 // throw packager exception only after trying all bundlers 724 throw pe; 725 } 726 return bundleCreated; 727 } 728 729 private void addResources(DeployParams deployParams, 730 String inputdir) throws PackagerException { 731 732 if (inputdir == null || inputdir.isEmpty()) { 733 return; 734 } 735 736 File baseDir = new File(inputdir); 737 738 if (!baseDir.isDirectory()) { 739 throw new PackagerException("ERR_InputNotDirectory", inputdir); 740 } 741 if (!baseDir.canRead()) { 742 throw new PackagerException("ERR_CannotReadInputDir", inputdir); 743 } 744 745 List<String> fileNames; 746 fileNames = new ArrayList<>(); 747 try (Stream<Path> files = Files.list(baseDir.toPath())) { 748 files.forEach(file -> fileNames.add( 749 file.getFileName().toString())); 750 } catch (IOException e) { 751 Log.error("Unable to add resources: " + e.getMessage()); 752 } 753 fileNames.forEach(file -> deployParams.addResource(baseDir, file)); 754 755 deployParams.setClasspath(); 756 } 757 758 static boolean isCLIOption(String arg) { 759 return toCLIOption(arg) != null; 760 } 761 762 static CLIOptions toCLIOption(String arg) { 763 CLIOptions option; 764 if ((option = argIds.get(arg)) == null) { 765 option = argShortIds.get(arg); 766 } 767 return option; 768 } 769 770 static Map<String, String> getArgumentMap(String inputString) { 771 Map<String, String> map = new HashMap<>(); 772 List<String> list = getArgumentList(inputString); 773 for (String pair : list) { 774 int equals = pair.indexOf("="); 775 if (equals != -1) { 776 String key = pair.substring(0, equals); 777 String value = pair.substring(equals+1, pair.length()); 778 map.put(key, value); 779 } 780 } 781 return map; 782 } 783 784 static Map<String, String> getPropertiesFromFile(String filename) { 785 Map<String, String> map = new HashMap<>(); 786 // load properties file 787 File file = new File(filename); 788 Properties properties = new Properties(); 789 try (FileInputStream in = new FileInputStream(file)) { 790 properties.load(in); 791 } catch (IOException e) { 792 Log.error("Exception: " + e.getMessage()); 793 } 794 795 for (final String name: properties.stringPropertyNames()) { 796 map.put(name, properties.getProperty(name)); 797 } 798 799 return map; 800 } 801 802 static List<String> getArgumentList(String inputString) { 803 List<String> list = new ArrayList<>(); 804 if (inputString == null || inputString.isEmpty()) { 805 return list; 806 } 807 808 // The "pattern" regexp attempts to abide to the rule that 809 // strings are delimited by whitespace unless surrounded by 810 // quotes, then it is anything (including spaces) in the quotes. 811 Matcher m = pattern.matcher(inputString); 812 while (m.find()) { 813 String s = inputString.substring(m.start(), m.end()).trim(); 814 // Ensure we do not have an empty string. trim() will take care of 815 // whitespace only strings. The regex preserves quotes and escaped 816 // chars so we need to clean them before adding to the List 817 if (!s.isEmpty()) { 818 list.add(unquoteIfNeeded(s)); 819 } 820 } 821 return list; 822 } 823 824 private static String unquoteIfNeeded(String in) { 825 if (in == null) { 826 return null; 827 } 828 829 if (in.isEmpty()) { 830 return ""; 831 } 832 833 // Use code points to preserve non-ASCII chars 834 StringBuilder sb = new StringBuilder(); 835 int codeLen = in.codePointCount(0, in.length()); 836 int quoteChar = -1; 837 for (int i = 0; i < codeLen; i++) { 838 int code = in.codePointAt(i); 839 if (code == '"' || code == '\'') { 840 // If quote is escaped make sure to copy it 841 if (i > 0 && in.codePointAt(i - 1) == '\\') { 842 sb.deleteCharAt(sb.length() - 1); 843 sb.appendCodePoint(code); 844 continue; 845 } 846 if (quoteChar != -1) { 847 if (code == quoteChar) { 848 // close quote, skip char 849 quoteChar = -1; 850 } else { 851 sb.appendCodePoint(code); 852 } 853 } else { 854 // opening quote, skip char 855 quoteChar = code; 856 } 857 } else { 858 sb.appendCodePoint(code); 859 } 860 } 861 return sb.toString(); 862 } 863 864 private String getMainClassFromManifest() { 865 if (mainJarPath == null || 866 input == null ) { 867 return null; 868 } 869 870 JarFile jf; 871 try { 872 File file = new File(input, mainJarPath); 873 if (!file.exists()) { 874 return null; 875 } 876 jf = new JarFile(file); 877 Manifest m = jf.getManifest(); 878 Attributes attrs = (m != null) ? m.getMainAttributes() : null; 879 if (attrs != null) { 880 return attrs.getValue(Attributes.Name.MAIN_CLASS); 881 } 882 } catch (IOException ignore) {} 883 return null; 884 } 885 886 }