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 hasModule = allOptions.contains(CLIOptions.MODULE); 593 boolean hasMainJar = allOptions.contains(CLIOptions.MAIN_JAR); 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 !hasModule && !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_NoEntryPoint" : "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 } 627 628 private List<jdk.jpackage.internal.Bundler> getPlatformBundlers() { 629 630 if (platformBundlers != null) { 631 return platformBundlers; 632 } 633 634 platformBundlers = new ArrayList<>(); 635 for (jdk.jpackage.internal.Bundler bundler : 636 Bundlers.createBundlersInstance().getBundlers( 637 bundleType.toString())) { 638 if (hasTargetFormat && deployParams.getTargetFormat() != null && 639 !deployParams.getTargetFormat().equalsIgnoreCase( 640 bundler.getID())) { 641 continue; 642 } 643 if (bundler.supported(runtimeInstaller)) { 644 platformBundlers.add(bundler); 645 } 646 } 647 648 return platformBundlers; 649 } 650 651 private boolean generateBundle(Map<String,? super Object> params) 652 throws PackagerException { 653 654 boolean bundleCreated = false; 655 656 // the temp-root needs to be fetched from the params early, 657 // to prevent each copy of the params (such as may be used for 658 // additional launchers) from generating a separate temp-root when 659 // the default is used (the default is a new temp directory) 660 // The bundler.cleanup() below would not otherwise be able to 661 // clean these extra (and unneeded) temp directories. 662 StandardBundlerParam.TEMP_ROOT.fetchFrom(params); 663 664 for (jdk.jpackage.internal.Bundler bundler : getPlatformBundlers()) { 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 } catch (UnsupportedPlatformException e) { 680 throw new PackagerException(e, 681 "MSG_BundlerPlatformException", 682 bundler.getName()); 683 } catch (ConfigException e) { 684 Log.debug(e); 685 if (e.getAdvice() != null) { 686 throw new PackagerException(e, 687 "MSG_BundlerConfigException", 688 bundler.getName(), e.getMessage(), e.getAdvice()); 689 } else { 690 throw new PackagerException(e, 691 "MSG_BundlerConfigExceptionNoAdvice", 692 bundler.getName(), e.getMessage()); 693 } 694 } catch (RuntimeException re) { 695 Log.debug(re); 696 throw new PackagerException(re, "MSG_BundlerRuntimeException", 697 bundler.getName(), re.toString()); 698 } finally { 699 if (userProvidedBuildRoot) { 700 Log.verbose(MessageFormat.format( 701 I18N.getString("message.debug-working-directory"), 702 (new File(buildRoot)).getAbsolutePath())); 703 } 704 } 705 } 706 707 return bundleCreated; 708 } 709 710 private void addResources(DeployParams deployParams, 711 String inputdir, List<String> inputfiles) { 712 713 if (inputdir == null || inputdir.isEmpty()) { 714 return; 715 } 716 717 File baseDir = new File(inputdir); 718 719 if (!baseDir.isDirectory()) { 720 Log.error( 721 "Unable to add resources: \"--input\" is not a directory."); 722 return; 723 } 724 725 List<String> fileNames; 726 if (inputfiles != null) { 727 fileNames = inputfiles; 728 } else { 729 // "-files" is omitted, all files in input cdir (which 730 // is a mandatory argument in this case) will be packaged. 731 fileNames = new ArrayList<>(); 732 try (Stream<Path> files = Files.list(baseDir.toPath())) { 733 files.forEach(file -> fileNames.add( 734 file.getFileName().toString())); 735 } catch (IOException e) { 736 Log.error("Unable to add resources: " + e.getMessage()); 737 } 738 } 739 fileNames.forEach(file -> deployParams.addResource(baseDir, file)); 740 741 deployParams.setClasspath(); 742 } 743 744 static boolean isCLIOption(String arg) { 745 return toCLIOption(arg) != null; 746 } 747 748 static CLIOptions toCLIOption(String arg) { 749 CLIOptions option; 750 if ((option = argIds.get(arg)) == null) { 751 option = argShortIds.get(arg); 752 } 753 return option; 754 } 755 756 static Map<String, String> getArgumentMap(String inputString) { 757 Map<String, String> map = new HashMap<>(); 758 List<String> list = getArgumentList(inputString); 759 for (String pair : list) { 760 int equals = pair.indexOf("="); 761 if (equals != -1) { 762 String key = pair.substring(0, equals); 763 String value = pair.substring(equals+1, pair.length()); 764 map.put(key, value); 765 } 766 } 767 return map; 768 } 769 770 static Map<String, String> getPropertiesFromFile(String filename) { 771 Map<String, String> map = new HashMap<>(); 772 // load properties file 773 File file = new File(filename); 774 Properties properties = new Properties(); 775 try (FileInputStream in = new FileInputStream(file)) { 776 properties.load(in); 777 } catch (IOException e) { 778 Log.error("Exception: " + e.getMessage()); 779 } 780 781 for (final String name: properties.stringPropertyNames()) { 782 map.put(name, properties.getProperty(name)); 783 } 784 785 return map; 786 } 787 788 static List<String> getArgumentList(String inputString) { 789 List<String> list = new ArrayList<>(); 790 if (inputString == null || inputString.isEmpty()) { 791 return list; 792 } 793 794 // The "pattern" regexp attempts to abide to the rule that 795 // strings are delimited by whitespace unless surrounded by 796 // quotes, then it is anything (including spaces) in the quotes. 797 Matcher m = pattern.matcher(inputString); 798 while (m.find()) { 799 String s = inputString.substring(m.start(), m.end()).trim(); 800 // Ensure we do not have an empty string. trim() will take care of 801 // whitespace only strings. The regex preserves quotes and escaped 802 // chars so we need to clean them before adding to the List 803 if (!s.isEmpty()) { 804 list.add(unquoteIfNeeded(s)); 805 } 806 } 807 return list; 808 } 809 810 private static String unquoteIfNeeded(String in) { 811 if (in == null) { 812 return null; 813 } 814 815 if (in.isEmpty()) { 816 return ""; 817 } 818 819 // Use code points to preserve non-ASCII chars 820 StringBuilder sb = new StringBuilder(); 821 int codeLen = in.codePointCount(0, in.length()); 822 int quoteChar = -1; 823 for (int i = 0; i < codeLen; i++) { 824 int code = in.codePointAt(i); 825 if (code == '"' || code == '\'') { 826 // If quote is escaped make sure to copy it 827 if (i > 0 && in.codePointAt(i - 1) == '\\') { 828 sb.deleteCharAt(sb.length() - 1); 829 sb.appendCodePoint(code); 830 continue; 831 } 832 if (quoteChar != -1) { 833 if (code == quoteChar) { 834 // close quote, skip char 835 quoteChar = -1; 836 } else { 837 sb.appendCodePoint(code); 838 } 839 } else { 840 // opening quote, skip char 841 quoteChar = code; 842 } 843 } else { 844 sb.appendCodePoint(code); 845 } 846 } 847 return sb.toString(); 848 } 849 850 private String getMainClassFromManifest() { 851 if (mainJarPath == null || 852 input == null ) { 853 return null; 854 } 855 856 JarFile jf; 857 try { 858 File file = new File(input, mainJarPath); 859 if (!file.exists()) { 860 return null; 861 } 862 jf = new JarFile(file); 863 Manifest m = jf.getManifest(); 864 Attributes attrs = (m != null) ? m.getMainAttributes() : null; 865 if (attrs != null) { 866 return attrs.getValue(Attributes.Name.MAIN_CLASS); 867 } 868 } catch (IOException ignore) {} 869 return null; 870 } 871 872 }