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