1 /* 2 * Copyright (c) 2014, 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 26 package jdk.jpackage.internal; 27 28 import jdk.jpackage.internal.BundleParams; 29 import jdk.jpackage.internal.AbstractAppImageBuilder; 30 31 import java.io.File; 32 import java.io.IOException; 33 import java.io.StringReader; 34 import java.nio.file.Files; 35 import java.nio.file.Path; 36 import java.nio.file.Paths; 37 import java.text.MessageFormat; 38 import java.util.ArrayList; 39 import java.util.Arrays; 40 import java.util.Collections; 41 import java.util.Date; 42 import java.util.HashMap; 43 import java.util.HashSet; 44 import java.util.LinkedHashSet; 45 import java.util.List; 46 import java.util.Map; 47 import java.util.Optional; 48 import java.util.Properties; 49 import java.util.ResourceBundle; 50 import java.util.Set; 51 import java.util.HashSet; 52 import java.util.function.BiFunction; 53 import java.util.function.Function; 54 import java.util.jar.Attributes; 55 import java.util.jar.JarFile; 56 import java.util.jar.Manifest; 57 import java.util.regex.Pattern; 58 import java.util.stream.Collectors; 59 60 /** 61 * StandardBundlerParam 62 * 63 * A parameter to a bundler. 64 * 65 * Also contains static definitions of all of the common bundler parameters. 66 * (additional platform specific and mode specific bundler parameters 67 * are defined in each of the specific bundlers) 68 * 69 * Also contains static methods that operate on maps of parameters. 70 */ 71 class StandardBundlerParam<T> extends BundlerParamInfo<T> { 72 73 private static final ResourceBundle I18N = ResourceBundle.getBundle( 74 "jdk.jpackage.internal.resources.MainResources"); 75 private static final String JAVABASEJMOD = "java.base.jmod"; 76 77 StandardBundlerParam(String id, Class<T> valueType, 78 Function<Map<String, ? super Object>, T> defaultValueFunction, 79 BiFunction<String, Map<String, ? super Object>, T> stringConverter) 80 { 81 this.id = id; 82 this.valueType = valueType; 83 this.defaultValueFunction = defaultValueFunction; 84 this.stringConverter = stringConverter; 85 } 86 87 static final StandardBundlerParam<RelativeFileSet> APP_RESOURCES = 88 new StandardBundlerParam<>( 89 BundleParams.PARAM_APP_RESOURCES, 90 RelativeFileSet.class, 91 null, // no default. Required parameter 92 null // no string translation, 93 // tool must provide complex type 94 ); 95 96 @SuppressWarnings("unchecked") 97 static final 98 StandardBundlerParam<List<RelativeFileSet>> APP_RESOURCES_LIST = 99 new StandardBundlerParam<>( 100 BundleParams.PARAM_APP_RESOURCES + "List", 101 (Class<List<RelativeFileSet>>) (Object) List.class, 102 // Default is appResources, as a single item list 103 p -> new ArrayList<>(Collections.singletonList( 104 APP_RESOURCES.fetchFrom(p))), 105 StandardBundlerParam::createAppResourcesListFromString 106 ); 107 108 static final StandardBundlerParam<String> SOURCE_DIR = 109 new StandardBundlerParam<>( 110 Arguments.CLIOptions.INPUT.getId(), 111 String.class, 112 p -> null, 113 (s, p) -> { 114 String value = String.valueOf(s); 115 if (value.charAt(value.length() - 1) == 116 File.separatorChar) { 117 return value.substring(0, value.length() - 1); 118 } 119 else { 120 return value; 121 } 122 } 123 ); 124 125 // note that each bundler is likely to replace this one with 126 // their own converter 127 static final StandardBundlerParam<RelativeFileSet> MAIN_JAR = 128 new StandardBundlerParam<>( 129 Arguments.CLIOptions.MAIN_JAR.getId(), 130 RelativeFileSet.class, 131 params -> { 132 extractMainClassInfoFromAppResources(params); 133 return (RelativeFileSet) params.get("mainJar"); 134 }, 135 (s, p) -> getMainJar(s, p) 136 ); 137 138 // TODO: test CLASSPATH jar manifest Attributet 139 static final StandardBundlerParam<String> CLASSPATH = 140 new StandardBundlerParam<>( 141 "classpath", 142 String.class, 143 params -> { 144 extractMainClassInfoFromAppResources(params); 145 String cp = (String) params.get("classpath"); 146 return cp == null ? "" : cp; 147 }, 148 (s, p) -> s.replace(File.pathSeparator, " ") 149 ); 150 151 static final StandardBundlerParam<String> MAIN_CLASS = 152 new StandardBundlerParam<>( 153 Arguments.CLIOptions.APPCLASS.getId(), 154 String.class, 155 params -> { 156 if (isRuntimeInstaller(params)) { 157 return null; 158 } 159 extractMainClassInfoFromAppResources(params); 160 String s = (String) params.get( 161 BundleParams.PARAM_APPLICATION_CLASS); 162 if (s == null) { 163 s = JLinkBundlerHelper.getMainClass(params); 164 } 165 return s; 166 }, 167 (s, p) -> s 168 ); 169 170 static final StandardBundlerParam<String> APP_NAME = 171 new StandardBundlerParam<>( 172 Arguments.CLIOptions.NAME.getId(), 173 String.class, 174 params -> { 175 String s = MAIN_CLASS.fetchFrom(params); 176 if (s == null) return null; 177 178 int idx = s.lastIndexOf("."); 179 if (idx >= 0) { 180 return s.substring(idx+1); 181 } 182 return s; 183 }, 184 (s, p) -> s 185 ); 186 187 static final StandardBundlerParam<File> ICON = 188 new StandardBundlerParam<>( 189 Arguments.CLIOptions.ICON.getId(), 190 File.class, 191 params -> null, 192 (s, p) -> new File(s) 193 ); 194 195 static final StandardBundlerParam<String> VENDOR = 196 new StandardBundlerParam<>( 197 Arguments.CLIOptions.VENDOR.getId(), 198 String.class, 199 params -> I18N.getString("param.vendor.default"), 200 (s, p) -> s 201 ); 202 203 static final StandardBundlerParam<String> DESCRIPTION = 204 new StandardBundlerParam<>( 205 Arguments.CLIOptions.DESCRIPTION.getId(), 206 String.class, 207 params -> params.containsKey(APP_NAME.getID()) 208 ? APP_NAME.fetchFrom(params) 209 : I18N.getString("param.description.default"), 210 (s, p) -> s 211 ); 212 213 static final StandardBundlerParam<String> COPYRIGHT = 214 new StandardBundlerParam<>( 215 Arguments.CLIOptions.COPYRIGHT.getId(), 216 String.class, 217 params -> MessageFormat.format(I18N.getString( 218 "param.copyright.default"), new Date()), 219 (s, p) -> s 220 ); 221 222 @SuppressWarnings("unchecked") 223 static final StandardBundlerParam<List<String>> ARGUMENTS = 224 new StandardBundlerParam<>( 225 Arguments.CLIOptions.ARGUMENTS.getId(), 226 (Class<List<String>>) (Object) List.class, 227 params -> Collections.emptyList(), 228 (s, p) -> splitStringWithEscapes(s) 229 ); 230 231 @SuppressWarnings("unchecked") 232 static final StandardBundlerParam<List<String>> JVM_OPTIONS = 233 new StandardBundlerParam<>( 234 Arguments.CLIOptions.JVM_ARGS.getId(), 235 (Class<List<String>>) (Object) List.class, 236 params -> Collections.emptyList(), 237 (s, p) -> Arrays.asList(s.split("\n\n")) 238 ); 239 240 static final StandardBundlerParam<String> TITLE = 241 new StandardBundlerParam<>( 242 BundleParams.PARAM_TITLE, 243 String.class, 244 APP_NAME::fetchFrom, 245 (s, p) -> s 246 ); 247 248 // note that each bundler is likely to replace this one with 249 // their own converter 250 static final StandardBundlerParam<String> VERSION = 251 new StandardBundlerParam<>( 252 Arguments.CLIOptions.VERSION.getId(), 253 String.class, 254 params -> I18N.getString("param.version.default"), 255 (s, p) -> s 256 ); 257 258 @SuppressWarnings("unchecked") 259 public static final StandardBundlerParam<String> LICENSE_FILE = 260 new StandardBundlerParam<>( 261 Arguments.CLIOptions.LICENSE_FILE.getId(), 262 String.class, 263 params -> null, 264 (s, p) -> s 265 ); 266 267 static final StandardBundlerParam<File> TEMP_ROOT = 268 new StandardBundlerParam<>( 269 Arguments.CLIOptions.TEMP_ROOT.getId(), 270 File.class, 271 params -> { 272 try { 273 return Files.createTempDirectory( 274 "jdk.jpackage").toFile(); 275 } catch (IOException ioe) { 276 return null; 277 } 278 }, 279 (s, p) -> new File(s) 280 ); 281 282 public static final StandardBundlerParam<File> CONFIG_ROOT = 283 new StandardBundlerParam<>( 284 "configRoot", 285 File.class, 286 params -> { 287 File root = 288 new File(TEMP_ROOT.fetchFrom(params), "config"); 289 root.mkdirs(); 290 return root; 291 }, 292 (s, p) -> null 293 ); 294 295 static final StandardBundlerParam<String> IDENTIFIER = 296 new StandardBundlerParam<>( 297 Arguments.CLIOptions.IDENTIFIER.getId(), 298 String.class, 299 params -> { 300 String s = MAIN_CLASS.fetchFrom(params); 301 if (s == null) return null; 302 303 int idx = s.lastIndexOf("."); 304 if (idx >= 1) { 305 return s.substring(0, idx); 306 } 307 return s; 308 }, 309 (s, p) -> s 310 ); 311 312 static final StandardBundlerParam<String> PREFERENCES_ID = 313 new StandardBundlerParam<>( 314 "preferencesID", 315 String.class, 316 p -> Optional.ofNullable(IDENTIFIER.fetchFrom(p)). 317 orElse("").replace('.', '/'), 318 (s, p) -> s 319 ); 320 321 static final StandardBundlerParam<Boolean> VERBOSE = 322 new StandardBundlerParam<>( 323 Arguments.CLIOptions.VERBOSE.getId(), 324 Boolean.class, 325 params -> false, 326 // valueOf(null) is false, and we actually do want null 327 (s, p) -> (s == null || "null".equalsIgnoreCase(s)) ? 328 true : Boolean.valueOf(s) 329 ); 330 331 static final StandardBundlerParam<File> RESOURCE_DIR = 332 new StandardBundlerParam<>( 333 Arguments.CLIOptions.RESOURCE_DIR.getId(), 334 File.class, 335 params -> null, 336 (s, p) -> new File(s) 337 ); 338 339 static final BundlerParamInfo<String> INSTALL_DIR = 340 new StandardBundlerParam<>( 341 Arguments.CLIOptions.INSTALL_DIR.getId(), 342 String.class, 343 params -> null, 344 (s, p) -> s 345 ); 346 347 static final StandardBundlerParam<File> PREDEFINED_APP_IMAGE = 348 new StandardBundlerParam<>( 349 Arguments.CLIOptions.PREDEFINED_APP_IMAGE.getId(), 350 File.class, 351 params -> null, 352 (s, p) -> new File(s)); 353 354 static final StandardBundlerParam<File> PREDEFINED_RUNTIME_IMAGE = 355 new StandardBundlerParam<>( 356 Arguments.CLIOptions.PREDEFINED_RUNTIME_IMAGE.getId(), 357 File.class, 358 params -> null, 359 (s, p) -> new File(s)); 360 361 @SuppressWarnings("unchecked") 362 static final StandardBundlerParam<List<Map<String, ? super Object>>> ADD_LAUNCHERS = 363 new StandardBundlerParam<>( 364 Arguments.CLIOptions.ADD_LAUNCHER.getId(), 365 (Class<List<Map<String, ? super Object>>>) (Object) 366 List.class, 367 params -> new ArrayList<>(1), 368 // valueOf(null) is false, and we actually do want null 369 (s, p) -> null 370 ); 371 372 @SuppressWarnings("unchecked") 373 static final StandardBundlerParam 374 <List<Map<String, ? super Object>>> FILE_ASSOCIATIONS = 375 new StandardBundlerParam<>( 376 Arguments.CLIOptions.FILE_ASSOCIATIONS.getId(), 377 (Class<List<Map<String, ? super Object>>>) (Object) 378 List.class, 379 params -> new ArrayList<>(1), 380 // valueOf(null) is false, and we actually do want null 381 (s, p) -> null 382 ); 383 384 @SuppressWarnings("unchecked") 385 static final StandardBundlerParam<List<String>> FA_EXTENSIONS = 386 new StandardBundlerParam<>( 387 "fileAssociation.extension", 388 (Class<List<String>>) (Object) List.class, 389 params -> null, // null means not matched to an extension 390 (s, p) -> Arrays.asList(s.split("(,|\\s)+")) 391 ); 392 393 @SuppressWarnings("unchecked") 394 static final StandardBundlerParam<List<String>> FA_CONTENT_TYPE = 395 new StandardBundlerParam<>( 396 "fileAssociation.contentType", 397 (Class<List<String>>) (Object) List.class, 398 params -> null, 399 // null means not matched to a content/mime type 400 (s, p) -> Arrays.asList(s.split("(,|\\s)+")) 401 ); 402 403 static final StandardBundlerParam<String> FA_DESCRIPTION = 404 new StandardBundlerParam<>( 405 "fileAssociation.description", 406 String.class, 407 params -> APP_NAME.fetchFrom(params) + " File", 408 null 409 ); 410 411 static final StandardBundlerParam<File> FA_ICON = 412 new StandardBundlerParam<>( 413 "fileAssociation.icon", 414 File.class, 415 ICON::fetchFrom, 416 (s, p) -> new File(s) 417 ); 418 419 @SuppressWarnings("unchecked") 420 static final BundlerParamInfo<List<Path>> MODULE_PATH = 421 new StandardBundlerParam<>( 422 Arguments.CLIOptions.MODULE_PATH.getId(), 423 (Class<List<Path>>) (Object)List.class, 424 p -> { return getDefaultModulePath(); }, 425 (s, p) -> { 426 List<Path> modulePath = Arrays.asList(s 427 .split(File.pathSeparator)).stream() 428 .map(ss -> new File(ss).toPath()) 429 .collect(Collectors.toList()); 430 Path javaBasePath = null; 431 if (modulePath != null) { 432 javaBasePath = JLinkBundlerHelper 433 .findPathOfModule(modulePath, JAVABASEJMOD); 434 } else { 435 modulePath = new ArrayList<Path>(); 436 } 437 438 // Add the default JDK module path to the module path. 439 if (javaBasePath == null) { 440 List<Path> jdkModulePath = getDefaultModulePath(); 441 442 if (jdkModulePath != null) { 443 modulePath.addAll(jdkModulePath); 444 javaBasePath = 445 JLinkBundlerHelper.findPathOfModule( 446 modulePath, JAVABASEJMOD); 447 } 448 } 449 450 if (javaBasePath == null || 451 !Files.exists(javaBasePath)) { 452 Log.error(String.format(I18N.getString( 453 "warning.no.jdk.modules.found"))); 454 } 455 456 return modulePath; 457 }); 458 459 static final BundlerParamInfo<String> MODULE = 460 new StandardBundlerParam<>( 461 Arguments.CLIOptions.MODULE.getId(), 462 String.class, 463 p -> null, 464 (s, p) -> { 465 return String.valueOf(s); 466 }); 467 468 @SuppressWarnings("unchecked") 469 static final BundlerParamInfo<Set<String>> ADD_MODULES = 470 new StandardBundlerParam<>( 471 Arguments.CLIOptions.ADD_MODULES.getId(), 472 (Class<Set<String>>) (Object) Set.class, 473 p -> new LinkedHashSet<String>(), 474 (s, p) -> new LinkedHashSet<>(Arrays.asList(s.split(","))) 475 ); 476 477 @SuppressWarnings("unchecked") 478 static final BundlerParamInfo<Set<String>> LIMIT_MODULES = 479 new StandardBundlerParam<>( 480 "limit-modules", 481 (Class<Set<String>>) (Object) Set.class, 482 p -> new LinkedHashSet<String>(), 483 (s, p) -> new LinkedHashSet<>(Arrays.asList(s.split(","))) 484 ); 485 486 static boolean isRuntimeInstaller(Map<String, ? super Object> p) { 487 if (p.containsKey(MODULE.getID()) || 488 p.containsKey(MAIN_JAR.getID()) || 489 p.containsKey(PREDEFINED_APP_IMAGE.getID())) { 490 return false; // we are building or are given an application 491 } 492 // runtime installer requires --runtime-image, if this is false 493 // here then we should have thrown error validating args. 494 return p.containsKey(PREDEFINED_RUNTIME_IMAGE.getID()); 495 } 496 497 static File getPredefinedAppImage(Map<String, ? super Object> p) { 498 File applicationImage = null; 499 if (PREDEFINED_APP_IMAGE.fetchFrom(p) != null) { 500 applicationImage = PREDEFINED_APP_IMAGE.fetchFrom(p); 501 Log.debug("Using App Image from " + applicationImage); 502 if (!applicationImage.exists()) { 503 throw new RuntimeException( 504 MessageFormat.format(I18N.getString( 505 "message.app-image-dir-does-not-exist"), 506 PREDEFINED_APP_IMAGE.getID(), 507 applicationImage.toString())); 508 } 509 } 510 return applicationImage; 511 } 512 513 static void copyPredefinedRuntimeImage( 514 Map<String, ? super Object> p, 515 AbstractAppImageBuilder appBuilder) 516 throws IOException , ConfigException { 517 File image = PREDEFINED_RUNTIME_IMAGE.fetchFrom(p); 518 if (!image.exists()) { 519 throw new ConfigException( 520 MessageFormat.format(I18N.getString( 521 "message.runtime-image-dir-does-not-exist"), 522 PREDEFINED_RUNTIME_IMAGE.getID(), 523 image.toString()), 524 MessageFormat.format(I18N.getString( 525 "message.runtime-image-dir-does-not-exist.advice"), 526 PREDEFINED_RUNTIME_IMAGE.getID())); 527 } 528 // copy whole runtime, need to skip jmods and src.zip 529 final List<String> excludes = Arrays.asList("jmods", "src.zip"); 530 IOUtils.copyRecursive(image.toPath(), appBuilder.getRoot(), excludes); 531 532 // if module-path given - copy modules to appDir/mods 533 List<Path> modulePath = 534 StandardBundlerParam.MODULE_PATH.fetchFrom(p); 535 List<Path> defaultModulePath = getDefaultModulePath(); 536 Path dest = appBuilder.getAppModsDir(); 537 538 if (dest != null) { 539 for (Path mp : modulePath) { 540 if (!defaultModulePath.contains(mp)) { 541 Files.createDirectories(dest); 542 IOUtils.copyRecursive(mp, dest); 543 } 544 } 545 } 546 547 appBuilder.prepareApplicationFiles(); 548 } 549 550 static void extractMainClassInfoFromAppResources( 551 Map<String, ? super Object> params) { 552 boolean hasMainClass = params.containsKey(MAIN_CLASS.getID()); 553 boolean hasMainJar = params.containsKey(MAIN_JAR.getID()); 554 boolean hasMainJarClassPath = params.containsKey(CLASSPATH.getID()); 555 boolean hasModule = params.containsKey(MODULE.getID()); 556 557 if (hasMainClass && hasMainJar && hasMainJarClassPath || hasModule || 558 isRuntimeInstaller(params)) { 559 return; 560 } 561 562 // it's a pair. 563 // The [0] is the srcdir [1] is the file relative to sourcedir 564 List<String[]> filesToCheck = new ArrayList<>(); 565 566 if (hasMainJar) { 567 RelativeFileSet rfs = MAIN_JAR.fetchFrom(params); 568 for (String s : rfs.getIncludedFiles()) { 569 filesToCheck.add( 570 new String[] {rfs.getBaseDirectory().toString(), s}); 571 } 572 } else if (hasMainJarClassPath) { 573 for (String s : CLASSPATH.fetchFrom(params).split("\\s+")) { 574 if (APP_RESOURCES.fetchFrom(params) != null) { 575 filesToCheck.add( 576 new String[] {APP_RESOURCES.fetchFrom(params) 577 .getBaseDirectory().toString(), s}); 578 } 579 } 580 } else { 581 List<RelativeFileSet> rfsl = APP_RESOURCES_LIST.fetchFrom(params); 582 if (rfsl == null || rfsl.isEmpty()) { 583 return; 584 } 585 for (RelativeFileSet rfs : rfsl) { 586 if (rfs == null) continue; 587 588 for (String s : rfs.getIncludedFiles()) { 589 filesToCheck.add( 590 new String[]{rfs.getBaseDirectory().toString(), s}); 591 } 592 } 593 } 594 595 // presume the set iterates in-order 596 for (String[] fnames : filesToCheck) { 597 try { 598 // only sniff jars 599 if (!fnames[1].toLowerCase().endsWith(".jar")) continue; 600 601 File file = new File(fnames[0], fnames[1]); 602 // that actually exist 603 if (!file.exists()) continue; 604 605 try (JarFile jf = new JarFile(file)) { 606 Manifest m = jf.getManifest(); 607 Attributes attrs = (m != null) ? 608 m.getMainAttributes() : null; 609 610 if (attrs != null) { 611 if (!hasMainJar) { 612 if (fnames[0] == null) { 613 fnames[0] = file.getParentFile().toString(); 614 } 615 params.put(MAIN_JAR.getID(), new RelativeFileSet( 616 new File(fnames[0]), 617 new LinkedHashSet<>(Collections 618 .singletonList(file)))); 619 } 620 if (!hasMainJarClassPath) { 621 String cp = 622 attrs.getValue(Attributes.Name.CLASS_PATH); 623 params.put(CLASSPATH.getID(), 624 cp == null ? "" : cp); 625 } 626 break; 627 } 628 } 629 } catch (IOException ignore) { 630 ignore.printStackTrace(); 631 } 632 } 633 } 634 635 static void validateMainClassInfoFromAppResources( 636 Map<String, ? super Object> params) throws ConfigException { 637 boolean hasMainClass = params.containsKey(MAIN_CLASS.getID()); 638 boolean hasMainJar = params.containsKey(MAIN_JAR.getID()); 639 boolean hasMainJarClassPath = params.containsKey(CLASSPATH.getID()); 640 boolean hasModule = params.containsKey(MODULE.getID()); 641 boolean hasAppImage = params.containsKey(PREDEFINED_APP_IMAGE.getID()); 642 643 if (hasMainClass && hasMainJar && hasMainJarClassPath || 644 hasModule || hasAppImage || isRuntimeInstaller(params)) { 645 return; 646 } 647 648 extractMainClassInfoFromAppResources(params); 649 650 if (!params.containsKey(MAIN_CLASS.getID())) { 651 if (hasMainJar) { 652 throw new ConfigException( 653 MessageFormat.format(I18N.getString( 654 "error.no-main-class-with-main-jar"), 655 MAIN_JAR.fetchFrom(params)), 656 MessageFormat.format(I18N.getString( 657 "error.no-main-class-with-main-jar.advice"), 658 MAIN_JAR.fetchFrom(params))); 659 } else { 660 throw new ConfigException( 661 I18N.getString("error.no-main-class"), 662 I18N.getString("error.no-main-class.advice")); 663 } 664 } 665 } 666 667 private static List<String> splitStringWithEscapes(String s) { 668 List<String> l = new ArrayList<>(); 669 StringBuilder current = new StringBuilder(); 670 boolean quoted = false; 671 boolean escaped = false; 672 for (char c : s.toCharArray()) { 673 if (escaped) { 674 current.append(c); 675 } else if ('"' == c) { 676 quoted = !quoted; 677 } else if (!quoted && Character.isWhitespace(c)) { 678 l.add(current.toString()); 679 current = new StringBuilder(); 680 } else { 681 current.append(c); 682 } 683 } 684 l.add(current.toString()); 685 return l; 686 } 687 688 private static List<RelativeFileSet> 689 createAppResourcesListFromString(String s, 690 Map<String, ? super Object> objectObjectMap) { 691 List<RelativeFileSet> result = new ArrayList<>(); 692 for (String path : s.split("[:;]")) { 693 File f = new File(path); 694 if (f.getName().equals("*") || path.endsWith("/") || 695 path.endsWith("\\")) { 696 if (f.getName().equals("*")) { 697 f = f.getParentFile(); 698 } 699 Set<File> theFiles = new HashSet<>(); 700 try { 701 Files.walk(f.toPath()) 702 .filter(Files::isRegularFile) 703 .forEach(p -> theFiles.add(p.toFile())); 704 } catch (IOException e) { 705 e.printStackTrace(); 706 } 707 result.add(new RelativeFileSet(f, theFiles)); 708 } else { 709 result.add(new RelativeFileSet(f.getParentFile(), 710 Collections.singleton(f))); 711 } 712 } 713 return result; 714 } 715 716 private static RelativeFileSet getMainJar( 717 String mainJarValue, Map<String, ? super Object> params) { 718 for (RelativeFileSet rfs : APP_RESOURCES_LIST.fetchFrom(params)) { 719 File appResourcesRoot = rfs.getBaseDirectory(); 720 File mainJarFile = new File(appResourcesRoot, mainJarValue); 721 722 if (mainJarFile.exists()) { 723 return new RelativeFileSet(appResourcesRoot, 724 new LinkedHashSet<>(Collections.singletonList( 725 mainJarFile))); 726 } 727 mainJarFile = new File(mainJarValue); 728 if (mainJarFile.exists()) { 729 // absolute path for main-jar may fail is only legal if 730 // path is within the appResourceRoot directory 731 try { 732 return new RelativeFileSet(appResourcesRoot, 733 new LinkedHashSet<>(Collections.singletonList( 734 mainJarFile))); 735 } catch (Exception e) { 736 // if not within, RelativeFileSet constructor throws a 737 // RuntimeException, but the IllegalArgumentException 738 // below contains a more explicit error message. 739 } 740 } else { 741 List<Path> modulePath = MODULE_PATH.fetchFrom(params); 742 modulePath.removeAll(getDefaultModulePath()); 743 if (!modulePath.isEmpty()) { 744 Path modularJarPath = JLinkBundlerHelper.findPathOfModule( 745 modulePath, mainJarValue); 746 if (modularJarPath != null && 747 Files.exists(modularJarPath)) { 748 return new RelativeFileSet(appResourcesRoot, 749 new LinkedHashSet<>(Collections.singletonList( 750 modularJarPath.toFile()))); 751 } 752 } 753 } 754 } 755 756 throw new IllegalArgumentException( 757 new ConfigException(MessageFormat.format(I18N.getString( 758 "error.main-jar-does-not-exist"), 759 mainJarValue), I18N.getString( 760 "error.main-jar-does-not-exist.advice"))); 761 } 762 763 static List<Path> getDefaultModulePath() { 764 List<Path> result = new ArrayList<Path>(); 765 Path jdkModulePath = Paths.get( 766 System.getProperty("java.home"), "jmods").toAbsolutePath(); 767 768 if (jdkModulePath != null && Files.exists(jdkModulePath)) { 769 result.add(jdkModulePath); 770 } 771 else { 772 // On a developer build the JDK Home isn't where we expect it 773 // relative to the jmods directory. Do some extra 774 // processing to find it. 775 Map<String, String> env = System.getenv(); 776 777 if (env.containsKey("JDK_HOME")) { 778 jdkModulePath = Paths.get(env.get("JDK_HOME"), 779 ".." + File.separator + "images" 780 + File.separator + "jmods").toAbsolutePath(); 781 782 if (jdkModulePath != null && Files.exists(jdkModulePath)) { 783 result.add(jdkModulePath); 784 } 785 } 786 } 787 788 return result; 789 } 790 }