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