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