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