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