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<File> PREDEFINED_RUNTIME_IMAGE = 171 new StandardBundlerParam<>( 172 Arguments.CLIOptions.PREDEFINED_RUNTIME_IMAGE.getId(), 173 File.class, 174 params -> null, 175 (s, p) -> new File(s) 176 ); 177 178 static final StandardBundlerParam<String> APP_NAME = 179 new StandardBundlerParam<>( 180 Arguments.CLIOptions.NAME.getId(), 181 String.class, 182 params -> { 183 String s = MAIN_CLASS.fetchFrom(params); 184 if (s != null) { 185 int idx = s.lastIndexOf("."); 186 if (idx >= 0) { 187 return s.substring(idx+1); 188 } 189 return s; 190 } else if (isRuntimeInstaller(params)) { 191 File f = PREDEFINED_RUNTIME_IMAGE.fetchFrom(params); 192 if (f != null) { 193 return f.getName(); 194 } 195 } 196 return null; 197 }, 198 (s, p) -> s 199 ); 200 201 static final StandardBundlerParam<File> ICON = 202 new StandardBundlerParam<>( 203 Arguments.CLIOptions.ICON.getId(), 204 File.class, 205 params -> null, 206 (s, p) -> new File(s) 207 ); 208 209 static final StandardBundlerParam<String> VENDOR = 210 new StandardBundlerParam<>( 211 Arguments.CLIOptions.VENDOR.getId(), 212 String.class, 213 params -> I18N.getString("param.vendor.default"), 214 (s, p) -> s 215 ); 216 217 static final StandardBundlerParam<String> DESCRIPTION = 218 new StandardBundlerParam<>( 219 Arguments.CLIOptions.DESCRIPTION.getId(), 220 String.class, 221 params -> params.containsKey(APP_NAME.getID()) 222 ? APP_NAME.fetchFrom(params) 223 : I18N.getString("param.description.default"), 224 (s, p) -> s 225 ); 226 227 static final StandardBundlerParam<String> COPYRIGHT = 228 new StandardBundlerParam<>( 229 Arguments.CLIOptions.COPYRIGHT.getId(), 230 String.class, 231 params -> MessageFormat.format(I18N.getString( 232 "param.copyright.default"), new Date()), 233 (s, p) -> s 234 ); 235 236 @SuppressWarnings("unchecked") 237 static final StandardBundlerParam<List<String>> ARGUMENTS = 238 new StandardBundlerParam<>( 239 Arguments.CLIOptions.ARGUMENTS.getId(), 240 (Class<List<String>>) (Object) List.class, 241 params -> Collections.emptyList(), 242 (s, p) -> splitStringWithEscapes(s) 243 ); 244 245 @SuppressWarnings("unchecked") 246 static final StandardBundlerParam<List<String>> JAVA_OPTIONS = 247 new StandardBundlerParam<>( 248 Arguments.CLIOptions.JAVA_OPTIONS.getId(), 249 (Class<List<String>>) (Object) List.class, 250 params -> Collections.emptyList(), 251 (s, p) -> Arrays.asList(s.split("\n\n")) 252 ); 253 254 // note that each bundler is likely to replace this one with 255 // their own converter 256 static final StandardBundlerParam<String> VERSION = 257 new StandardBundlerParam<>( 258 Arguments.CLIOptions.VERSION.getId(), 259 String.class, 260 params -> I18N.getString("param.version.default"), 261 (s, p) -> s 262 ); 263 264 @SuppressWarnings("unchecked") 265 public static final StandardBundlerParam<String> LICENSE_FILE = 266 new StandardBundlerParam<>( 267 Arguments.CLIOptions.LICENSE_FILE.getId(), 268 String.class, 269 params -> null, 270 (s, p) -> s 271 ); 272 273 static final StandardBundlerParam<File> TEMP_ROOT = 274 new StandardBundlerParam<>( 275 Arguments.CLIOptions.TEMP_ROOT.getId(), 276 File.class, 277 params -> { 278 try { 279 return Files.createTempDirectory( 280 "jdk.jpackage").toFile(); 281 } catch (IOException ioe) { 282 return null; 283 } 284 }, 285 (s, p) -> new File(s) 286 ); 287 288 public static final StandardBundlerParam<File> CONFIG_ROOT = 289 new StandardBundlerParam<>( 290 "configRoot", 291 File.class, 292 params -> { 293 File root = 294 new File(TEMP_ROOT.fetchFrom(params), "config"); 295 root.mkdirs(); 296 return root; 297 }, 298 (s, p) -> null 299 ); 300 301 static final StandardBundlerParam<String> IDENTIFIER = 302 new StandardBundlerParam<>( 303 Arguments.CLIOptions.IDENTIFIER.getId(), 304 String.class, 305 params -> { 306 String s = MAIN_CLASS.fetchFrom(params); 307 if (s == null) return null; 308 309 int idx = s.lastIndexOf("."); 310 if (idx >= 1) { 311 return s.substring(0, idx); 312 } 313 return s; 314 }, 315 (s, p) -> s 316 ); 317 318 static final StandardBundlerParam<Boolean> VERBOSE = 319 new StandardBundlerParam<>( 320 Arguments.CLIOptions.VERBOSE.getId(), 321 Boolean.class, 322 params -> false, 323 // valueOf(null) is false, and we actually do want null 324 (s, p) -> (s == null || "null".equalsIgnoreCase(s)) ? 325 true : Boolean.valueOf(s) 326 ); 327 328 static final StandardBundlerParam<File> RESOURCE_DIR = 329 new StandardBundlerParam<>( 330 Arguments.CLIOptions.RESOURCE_DIR.getId(), 331 File.class, 332 params -> null, 333 (s, p) -> new File(s) 334 ); 335 336 static final BundlerParamInfo<String> INSTALL_DIR = 337 new StandardBundlerParam<>( 338 Arguments.CLIOptions.INSTALL_DIR.getId(), 339 String.class, 340 params -> null, 341 (s, p) -> s 342 ); 343 344 static final StandardBundlerParam<File> PREDEFINED_APP_IMAGE = 345 new StandardBundlerParam<>( 346 Arguments.CLIOptions.PREDEFINED_APP_IMAGE.getId(), 347 File.class, 348 params -> null, 349 (s, p) -> new File(s)); 350 351 @SuppressWarnings("unchecked") 352 static final StandardBundlerParam<List<Map<String, ? super Object>>> ADD_LAUNCHERS = 353 new StandardBundlerParam<>( 354 Arguments.CLIOptions.ADD_LAUNCHER.getId(), 355 (Class<List<Map<String, ? super Object>>>) (Object) 356 List.class, 357 params -> new ArrayList<>(1), 358 // valueOf(null) is false, and we actually do want null 359 (s, p) -> null 360 ); 361 362 @SuppressWarnings("unchecked") 363 static final StandardBundlerParam 364 <List<Map<String, ? super Object>>> FILE_ASSOCIATIONS = 365 new StandardBundlerParam<>( 366 Arguments.CLIOptions.FILE_ASSOCIATIONS.getId(), 367 (Class<List<Map<String, ? super Object>>>) (Object) 368 List.class, 369 params -> new ArrayList<>(1), 370 // valueOf(null) is false, and we actually do want null 371 (s, p) -> null 372 ); 373 374 @SuppressWarnings("unchecked") 375 static final StandardBundlerParam<List<String>> FA_EXTENSIONS = 376 new StandardBundlerParam<>( 377 "fileAssociation.extension", 378 (Class<List<String>>) (Object) List.class, 379 params -> null, // null means not matched to an extension 380 (s, p) -> Arrays.asList(s.split("(,|\\s)+")) 381 ); 382 383 @SuppressWarnings("unchecked") 384 static final StandardBundlerParam<List<String>> FA_CONTENT_TYPE = 385 new StandardBundlerParam<>( 386 "fileAssociation.contentType", 387 (Class<List<String>>) (Object) List.class, 388 params -> null, 389 // null means not matched to a content/mime type 390 (s, p) -> Arrays.asList(s.split("(,|\\s)+")) 391 ); 392 393 static final StandardBundlerParam<String> FA_DESCRIPTION = 394 new StandardBundlerParam<>( 395 "fileAssociation.description", 396 String.class, 397 params -> APP_NAME.fetchFrom(params) + " File", 398 null 399 ); 400 401 static final StandardBundlerParam<File> FA_ICON = 402 new StandardBundlerParam<>( 403 "fileAssociation.icon", 404 File.class, 405 ICON::fetchFrom, 406 (s, p) -> new File(s) 407 ); 408 409 @SuppressWarnings("unchecked") 410 static final BundlerParamInfo<List<Path>> MODULE_PATH = 411 new StandardBundlerParam<>( 412 Arguments.CLIOptions.MODULE_PATH.getId(), 413 (Class<List<Path>>) (Object)List.class, 414 p -> { return getDefaultModulePath(); }, 415 (s, p) -> { 416 List<Path> modulePath = Arrays.asList(s 417 .split(File.pathSeparator)).stream() 418 .map(ss -> new File(ss).toPath()) 419 .collect(Collectors.toList()); 420 Path javaBasePath = null; 421 if (modulePath != null) { 422 javaBasePath = JLinkBundlerHelper 423 .findPathOfModule(modulePath, JAVABASEJMOD); 424 } else { 425 modulePath = new ArrayList<Path>(); 426 } 427 428 // Add the default JDK module path to the module path. 429 if (javaBasePath == null) { 430 List<Path> jdkModulePath = getDefaultModulePath(); 431 432 if (jdkModulePath != null) { 433 modulePath.addAll(jdkModulePath); 434 javaBasePath = 435 JLinkBundlerHelper.findPathOfModule( 436 modulePath, JAVABASEJMOD); 437 } 438 } 439 440 if (javaBasePath == null || 441 !Files.exists(javaBasePath)) { 442 Log.error(String.format(I18N.getString( 443 "warning.no.jdk.modules.found"))); 444 } 445 446 return modulePath; 447 }); 448 449 static final BundlerParamInfo<String> MODULE = 450 new StandardBundlerParam<>( 451 Arguments.CLIOptions.MODULE.getId(), 452 String.class, 453 p -> null, 454 (s, p) -> { 455 return String.valueOf(s); 456 }); 457 458 @SuppressWarnings("unchecked") 459 static final BundlerParamInfo<Set<String>> ADD_MODULES = 460 new StandardBundlerParam<>( 461 Arguments.CLIOptions.ADD_MODULES.getId(), 462 (Class<Set<String>>) (Object) Set.class, 463 p -> new LinkedHashSet<String>(), 464 (s, p) -> new LinkedHashSet<>(Arrays.asList(s.split(","))) 465 ); 466 467 @SuppressWarnings("unchecked") 468 static final BundlerParamInfo<Set<String>> LIMIT_MODULES = 469 new StandardBundlerParam<>( 470 "limit-modules", 471 (Class<Set<String>>) (Object) Set.class, 472 p -> new LinkedHashSet<String>(), 473 (s, p) -> new LinkedHashSet<>(Arrays.asList(s.split(","))) 474 ); 475 476 static boolean isRuntimeInstaller(Map<String, ? super Object> p) { 477 if (p.containsKey(MODULE.getID()) || 478 p.containsKey(MAIN_JAR.getID()) || 479 p.containsKey(PREDEFINED_APP_IMAGE.getID())) { 480 return false; // we are building or are given an application 481 } 482 // runtime installer requires --runtime-image, if this is false 483 // here then we should have thrown error validating args. 484 return p.containsKey(PREDEFINED_RUNTIME_IMAGE.getID()); 485 } 486 487 static File getPredefinedAppImage(Map<String, ? super Object> p) { 488 File applicationImage = null; 489 if (PREDEFINED_APP_IMAGE.fetchFrom(p) != null) { 490 applicationImage = PREDEFINED_APP_IMAGE.fetchFrom(p); 491 Log.debug("Using App Image from " + applicationImage); 492 if (!applicationImage.exists()) { 493 throw new RuntimeException( 494 MessageFormat.format(I18N.getString( 495 "message.app-image-dir-does-not-exist"), 496 PREDEFINED_APP_IMAGE.getID(), 497 applicationImage.toString())); 498 } 499 } 500 return applicationImage; 501 } 502 503 static void copyPredefinedRuntimeImage( 504 Map<String, ? super Object> p, 505 AbstractAppImageBuilder appBuilder) 506 throws IOException , ConfigException { 507 File image = PREDEFINED_RUNTIME_IMAGE.fetchFrom(p); 508 if (!image.exists()) { 509 throw new ConfigException( 510 MessageFormat.format(I18N.getString( 511 "message.runtime-image-dir-does-not-exist"), 512 PREDEFINED_RUNTIME_IMAGE.getID(), 513 image.toString()), 514 MessageFormat.format(I18N.getString( 515 "message.runtime-image-dir-does-not-exist.advice"), 516 PREDEFINED_RUNTIME_IMAGE.getID())); 517 } 518 // copy whole runtime, need to skip jmods and src.zip 519 final List<String> excludes = Arrays.asList("jmods", "src.zip"); 520 IOUtils.copyRecursive(image.toPath(), appBuilder.getRoot(), excludes); 521 522 // if module-path given - copy modules to appDir/mods 523 List<Path> modulePath = 524 StandardBundlerParam.MODULE_PATH.fetchFrom(p); 525 List<Path> defaultModulePath = getDefaultModulePath(); 526 Path dest = appBuilder.getAppModsDir(); 527 528 if (dest != null) { 529 for (Path mp : modulePath) { 530 if (!defaultModulePath.contains(mp)) { 531 Files.createDirectories(dest); 532 IOUtils.copyRecursive(mp, dest); 533 } 534 } 535 } 536 537 appBuilder.prepareApplicationFiles(); 538 } 539 540 static void extractMainClassInfoFromAppResources( 541 Map<String, ? super Object> params) { 542 boolean hasMainClass = params.containsKey(MAIN_CLASS.getID()); 543 boolean hasMainJar = params.containsKey(MAIN_JAR.getID()); 544 boolean hasMainJarClassPath = params.containsKey(CLASSPATH.getID()); 545 boolean hasModule = params.containsKey(MODULE.getID()); 546 547 if (hasMainClass && hasMainJar && hasMainJarClassPath || hasModule || 548 isRuntimeInstaller(params)) { 549 return; 550 } 551 552 // it's a pair. 553 // The [0] is the srcdir [1] is the file relative to sourcedir 554 List<String[]> filesToCheck = new ArrayList<>(); 555 556 if (hasMainJar) { 557 RelativeFileSet rfs = MAIN_JAR.fetchFrom(params); 558 for (String s : rfs.getIncludedFiles()) { 559 filesToCheck.add( 560 new String[] {rfs.getBaseDirectory().toString(), s}); 561 } 562 } else if (hasMainJarClassPath) { 563 for (String s : CLASSPATH.fetchFrom(params).split("\\s+")) { 564 if (APP_RESOURCES.fetchFrom(params) != null) { 565 filesToCheck.add( 566 new String[] {APP_RESOURCES.fetchFrom(params) 567 .getBaseDirectory().toString(), s}); 568 } 569 } 570 } else { 571 List<RelativeFileSet> rfsl = APP_RESOURCES_LIST.fetchFrom(params); 572 if (rfsl == null || rfsl.isEmpty()) { 573 return; 574 } 575 for (RelativeFileSet rfs : rfsl) { 576 if (rfs == null) continue; 577 578 for (String s : rfs.getIncludedFiles()) { 579 filesToCheck.add( 580 new String[]{rfs.getBaseDirectory().toString(), s}); 581 } 582 } 583 } 584 585 // presume the set iterates in-order 586 for (String[] fnames : filesToCheck) { 587 try { 588 // only sniff jars 589 if (!fnames[1].toLowerCase().endsWith(".jar")) continue; 590 591 File file = new File(fnames[0], fnames[1]); 592 // that actually exist 593 if (!file.exists()) continue; 594 595 try (JarFile jf = new JarFile(file)) { 596 Manifest m = jf.getManifest(); 597 Attributes attrs = (m != null) ? 598 m.getMainAttributes() : null; 599 600 if (attrs != null) { 601 if (!hasMainJar) { 602 if (fnames[0] == null) { 603 fnames[0] = file.getParentFile().toString(); 604 } 605 params.put(MAIN_JAR.getID(), new RelativeFileSet( 606 new File(fnames[0]), 607 new LinkedHashSet<>(Collections 608 .singletonList(file)))); 609 } 610 if (!hasMainJarClassPath) { 611 String cp = 612 attrs.getValue(Attributes.Name.CLASS_PATH); 613 params.put(CLASSPATH.getID(), 614 cp == null ? "" : cp); 615 } 616 break; 617 } 618 } 619 } catch (IOException ignore) { 620 ignore.printStackTrace(); 621 } 622 } 623 } 624 625 static void validateMainClassInfoFromAppResources( 626 Map<String, ? super Object> params) throws ConfigException { 627 boolean hasMainClass = params.containsKey(MAIN_CLASS.getID()); 628 boolean hasMainJar = params.containsKey(MAIN_JAR.getID()); 629 boolean hasMainJarClassPath = params.containsKey(CLASSPATH.getID()); 630 boolean hasModule = params.containsKey(MODULE.getID()); 631 boolean hasAppImage = params.containsKey(PREDEFINED_APP_IMAGE.getID()); 632 633 if (hasMainClass && hasMainJar && hasMainJarClassPath || 634 hasModule || hasAppImage || isRuntimeInstaller(params)) { 635 return; 636 } 637 638 extractMainClassInfoFromAppResources(params); 639 640 if (!params.containsKey(MAIN_CLASS.getID())) { 641 if (hasMainJar) { 642 throw new ConfigException( 643 MessageFormat.format(I18N.getString( 644 "error.no-main-class-with-main-jar"), 645 MAIN_JAR.fetchFrom(params)), 646 MessageFormat.format(I18N.getString( 647 "error.no-main-class-with-main-jar.advice"), 648 MAIN_JAR.fetchFrom(params))); 649 } else { 650 throw new ConfigException( 651 I18N.getString("error.no-main-class"), 652 I18N.getString("error.no-main-class.advice")); 653 } 654 } 655 } 656 657 private static List<String> splitStringWithEscapes(String s) { 658 List<String> l = new ArrayList<>(); 659 StringBuilder current = new StringBuilder(); 660 boolean quoted = false; 661 boolean escaped = false; 662 for (char c : s.toCharArray()) { 663 if (escaped) { 664 current.append(c); 665 } else if ('"' == c) { 666 quoted = !quoted; 667 } else if (!quoted && Character.isWhitespace(c)) { 668 l.add(current.toString()); 669 current = new StringBuilder(); 670 } else { 671 current.append(c); 672 } 673 } 674 l.add(current.toString()); 675 return l; 676 } 677 678 private static List<RelativeFileSet> 679 createAppResourcesListFromString(String s, 680 Map<String, ? super Object> objectObjectMap) { 681 List<RelativeFileSet> result = new ArrayList<>(); 682 for (String path : s.split("[:;]")) { 683 File f = new File(path); 684 if (f.getName().equals("*") || path.endsWith("/") || 685 path.endsWith("\\")) { 686 if (f.getName().equals("*")) { 687 f = f.getParentFile(); 688 } 689 Set<File> theFiles = new HashSet<>(); 690 try { 691 Files.walk(f.toPath()) 692 .filter(Files::isRegularFile) 693 .forEach(p -> theFiles.add(p.toFile())); 694 } catch (IOException e) { 695 e.printStackTrace(); 696 } 697 result.add(new RelativeFileSet(f, theFiles)); 698 } else { 699 result.add(new RelativeFileSet(f.getParentFile(), 700 Collections.singleton(f))); 701 } 702 } 703 return result; 704 } 705 706 private static RelativeFileSet getMainJar( 707 String mainJarValue, Map<String, ? super Object> params) { 708 for (RelativeFileSet rfs : APP_RESOURCES_LIST.fetchFrom(params)) { 709 File appResourcesRoot = rfs.getBaseDirectory(); 710 File mainJarFile = new File(appResourcesRoot, mainJarValue); 711 712 if (mainJarFile.exists()) { 713 return new RelativeFileSet(appResourcesRoot, 714 new LinkedHashSet<>(Collections.singletonList( 715 mainJarFile))); 716 } 717 mainJarFile = new File(mainJarValue); 718 if (mainJarFile.exists()) { 719 // absolute path for main-jar may fail is not legal 720 // below contains explicit error message. 721 } else { 722 List<Path> modulePath = MODULE_PATH.fetchFrom(params); 723 modulePath.removeAll(getDefaultModulePath()); 724 if (!modulePath.isEmpty()) { 725 Path modularJarPath = JLinkBundlerHelper.findPathOfModule( 726 modulePath, mainJarValue); 727 if (modularJarPath != null && 728 Files.exists(modularJarPath)) { 729 return new RelativeFileSet(appResourcesRoot, 730 new LinkedHashSet<>(Collections.singletonList( 731 modularJarPath.toFile()))); 732 } 733 } 734 } 735 } 736 737 throw new IllegalArgumentException( 738 new ConfigException(MessageFormat.format(I18N.getString( 739 "error.main-jar-does-not-exist"), 740 mainJarValue), I18N.getString( 741 "error.main-jar-does-not-exist.advice"))); 742 } 743 744 static List<Path> getDefaultModulePath() { 745 List<Path> result = new ArrayList<Path>(); 746 Path jdkModulePath = Paths.get( 747 System.getProperty("java.home"), "jmods").toAbsolutePath(); 748 749 if (jdkModulePath != null && Files.exists(jdkModulePath)) { 750 result.add(jdkModulePath); 751 } 752 else { 753 // On a developer build the JDK Home isn't where we expect it 754 // relative to the jmods directory. Do some extra 755 // processing to find it. 756 Map<String, String> env = System.getenv(); 757 758 if (env.containsKey("JDK_HOME")) { 759 jdkModulePath = Paths.get(env.get("JDK_HOME"), 760 ".." + File.separator + "images" 761 + File.separator + "jmods").toAbsolutePath(); 762 763 if (jdkModulePath != null && Files.exists(jdkModulePath)) { 764 result.add(jdkModulePath); 765 } 766 } 767 } 768 769 return result; 770 } 771 }