1 /* 2 * Copyright (c) 2014, 2018, 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.packager.internal; 27 28 import jdk.packager.internal.bundlers.BundleParams; 29 import jdk.packager.internal.builders.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 * StandardBundlerParams 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 public class StandardBundlerParam<T> extends BundlerParamInfo<T> { 72 73 private static final ResourceBundle I18N = ResourceBundle.getBundle( 74 "jdk.packager.internal.resources.StandardBundlerParam"); 75 private static final String JAVABASEJMOD = "java.base.jmod"; 76 77 public StandardBundlerParam(String name, String description, String id, 78 Class<T> valueType, 79 Function<Map<String, ? super Object>, T> defaultValueFunction, 80 BiFunction<String, Map<String, ? super Object>, T> stringConverter) 81 { 82 this.name = name; 83 this.description = description; 84 this.id = id; 85 this.valueType = valueType; 86 this.defaultValueFunction = defaultValueFunction; 87 this.stringConverter = stringConverter; 88 } 89 90 public static final StandardBundlerParam<RelativeFileSet> APP_RESOURCES = 91 new StandardBundlerParam<>( 92 I18N.getString("param.app-resources.name"), 93 I18N.getString("param.app-resource.description"), 94 BundleParams.PARAM_APP_RESOURCES, 95 RelativeFileSet.class, 96 null, // no default. Required parameter 97 null // no string translation, 98 // tool must provide complex type 99 ); 100 101 @SuppressWarnings("unchecked") 102 public static final 103 StandardBundlerParam<List<RelativeFileSet>> APP_RESOURCES_LIST = 104 new StandardBundlerParam<>( 105 I18N.getString("param.app-resources-list.name"), 106 I18N.getString("param.app-resource-list.description"), 107 BundleParams.PARAM_APP_RESOURCES + "List", 108 (Class<List<RelativeFileSet>>) (Object) List.class, 109 // Default is appResources, as a single item list 110 p -> new ArrayList<>(Collections.singletonList( 111 APP_RESOURCES.fetchFrom(p))), 112 StandardBundlerParam::createAppResourcesListFromString 113 ); 114 115 @SuppressWarnings("unchecked") 116 public static final StandardBundlerParam<String> SOURCE_DIR = 117 new StandardBundlerParam<>( 118 I18N.getString("param.source-dir.name"), 119 I18N.getString("param.source-dir.description"), 120 Arguments.CLIOptions.INPUT.getId(), 121 String.class, 122 p -> null, 123 (s, p) -> { 124 String value = String.valueOf(s); 125 if (value.charAt(value.length() - 1) == 126 File.separatorChar) { 127 return value.substring(0, value.length() - 1); 128 } 129 else { 130 return value; 131 } 132 } 133 ); 134 135 public static final StandardBundlerParam<List<File>> SOURCE_FILES = 136 new StandardBundlerParam<>( 137 I18N.getString("param.source-files.name"), 138 I18N.getString("param.source-files.description"), 139 Arguments.CLIOptions.FILES.getId(), 140 (Class<List<File>>) (Object) List.class, 141 p -> null, 142 (s, p) -> null 143 ); 144 145 // note that each bundler is likely to replace this one with 146 // their own converter 147 public static final StandardBundlerParam<RelativeFileSet> MAIN_JAR = 148 new StandardBundlerParam<>( 149 I18N.getString("param.main-jar.name"), 150 I18N.getString("param.main-jar.description"), 151 Arguments.CLIOptions.MAIN_JAR.getId(), 152 RelativeFileSet.class, 153 params -> { 154 extractMainClassInfoFromAppResources(params); 155 return (RelativeFileSet) params.get("mainJar"); 156 }, 157 (s, p) -> getMainJar(s, p) 158 ); 159 160 // TODO: test CLASSPATH jar manifest Attributet 161 public static final StandardBundlerParam<String> CLASSPATH = 162 new StandardBundlerParam<>( 163 I18N.getString("param.classpath.name"), 164 I18N.getString("param.classpath.description"), 165 "classpath", 166 String.class, 167 params -> { 168 extractMainClassInfoFromAppResources(params); 169 String cp = (String) params.get("classpath"); 170 return cp == null ? "" : cp; 171 }, 172 (s, p) -> s.replace(File.pathSeparator, " ") 173 ); 174 175 public static final StandardBundlerParam<String> MAIN_CLASS = 176 new StandardBundlerParam<>( 177 I18N.getString("param.main-class.name"), 178 I18N.getString("param.main-class.description"), 179 Arguments.CLIOptions.APPCLASS.getId(), 180 String.class, 181 params -> { 182 if (Arguments.CREATE_JRE_INSTALLER.fetchFrom(params)) { 183 return null; 184 } 185 extractMainClassInfoFromAppResources(params); 186 String s = (String) params.get( 187 BundleParams.PARAM_APPLICATION_CLASS); 188 if (s == null) { 189 s = JLinkBundlerHelper.getMainClass(params); 190 } 191 return s; 192 }, 193 (s, p) -> s 194 ); 195 196 public static final StandardBundlerParam<String> APP_NAME = 197 new StandardBundlerParam<>( 198 I18N.getString("param.app-name.name"), 199 I18N.getString("param.app-name.description"), 200 Arguments.CLIOptions.NAME.getId(), 201 String.class, 202 params -> { 203 String s = MAIN_CLASS.fetchFrom(params); 204 if (s == null) return null; 205 206 int idx = s.lastIndexOf("."); 207 if (idx >= 0) { 208 return s.substring(idx+1); 209 } 210 return s; 211 }, 212 (s, p) -> s 213 ); 214 215 private static Pattern TO_FS_NAME = Pattern.compile("\\s|[\\\\/?:*<>|]"); 216 // keep out invalid/undesireable filename characters 217 218 public static final StandardBundlerParam<String> APP_FS_NAME = 219 new StandardBundlerParam<>( 220 I18N.getString("param.app-fs-name.name"), 221 I18N.getString("param.app-fs-name.description"), 222 "name.fs", 223 String.class, 224 params -> TO_FS_NAME.matcher( 225 APP_NAME.fetchFrom(params)).replaceAll(""), 226 (s, p) -> s 227 ); 228 229 public static final StandardBundlerParam<File> ICON = 230 new StandardBundlerParam<>( 231 I18N.getString("param.icon-file.name"), 232 I18N.getString("param.icon-file.description"), 233 Arguments.CLIOptions.ICON.getId(), 234 File.class, 235 params -> null, 236 (s, p) -> new File(s) 237 ); 238 239 public static final StandardBundlerParam<String> VENDOR = 240 new StandardBundlerParam<>( 241 I18N.getString("param.vendor.name"), 242 I18N.getString("param.vendor.description"), 243 Arguments.CLIOptions.VENDOR.getId(), 244 String.class, 245 params -> I18N.getString("param.vendor.default"), 246 (s, p) -> s 247 ); 248 249 public static final StandardBundlerParam<String> CATEGORY = 250 new StandardBundlerParam<>( 251 I18N.getString("param.category.name"), 252 I18N.getString("param.category.description"), 253 Arguments.CLIOptions.CATEGORY.getId(), 254 String.class, 255 params -> I18N.getString("param.category.default"), 256 (s, p) -> s 257 ); 258 259 public static final StandardBundlerParam<String> DESCRIPTION = 260 new StandardBundlerParam<>( 261 I18N.getString("param.description.name"), 262 I18N.getString("param.description.description"), 263 Arguments.CLIOptions.DESCRIPTION.getId(), 264 String.class, 265 params -> params.containsKey(APP_NAME.getID()) 266 ? APP_NAME.fetchFrom(params) 267 : I18N.getString("param.description.default"), 268 (s, p) -> s 269 ); 270 271 public static final StandardBundlerParam<String> COPYRIGHT = 272 new StandardBundlerParam<>( 273 I18N.getString("param.copyright.name"), 274 I18N.getString("param.copyright.description"), 275 Arguments.CLIOptions.COPYRIGHT.getId(), 276 String.class, 277 params -> MessageFormat.format(I18N.getString( 278 "param.copyright.default"), new Date()), 279 (s, p) -> s 280 ); 281 282 @SuppressWarnings("unchecked") 283 public static final StandardBundlerParam<List<String>> ARGUMENTS = 284 new StandardBundlerParam<>( 285 I18N.getString("param.arguments.name"), 286 I18N.getString("param.arguments.description"), 287 Arguments.CLIOptions.ARGUMENTS.getId(), 288 (Class<List<String>>) (Object) List.class, 289 params -> Collections.emptyList(), 290 (s, p) -> splitStringWithEscapes(s) 291 ); 292 293 @SuppressWarnings("unchecked") 294 public static final StandardBundlerParam<List<String>> JVM_OPTIONS = 295 new StandardBundlerParam<>( 296 I18N.getString("param.jvm-options.name"), 297 I18N.getString("param.jvm-options.description"), 298 Arguments.CLIOptions.JVM_ARGS.getId(), 299 (Class<List<String>>) (Object) List.class, 300 params -> Collections.emptyList(), 301 (s, p) -> Arrays.asList(s.split("\n\n")) 302 ); 303 304 @SuppressWarnings("unchecked") 305 public static final 306 StandardBundlerParam<Map<String, String>> JVM_PROPERTIES = 307 new StandardBundlerParam<>( 308 I18N.getString("param.jvm-system-properties.name"), 309 I18N.getString("param.jvm-system-properties.description"), 310 "jvmProperties", 311 (Class<Map<String, String>>) (Object) Map.class, 312 params -> Collections.emptyMap(), 313 (s, params) -> { 314 Map<String, String> map = new HashMap<>(); 315 try { 316 Properties p = new Properties(); 317 p.load(new StringReader(s)); 318 for (Map.Entry<Object, 319 Object> entry : p.entrySet()) { 320 map.put((String)entry.getKey(), 321 (String)entry.getValue()); 322 } 323 } catch (IOException e) { 324 e.printStackTrace(); 325 } 326 return map; 327 } 328 ); 329 330 public static final StandardBundlerParam<String> TITLE = 331 new StandardBundlerParam<>( 332 I18N.getString("param.title.name"), 333 I18N.getString("param.title.description"), 334 BundleParams.PARAM_TITLE, 335 String.class, 336 APP_NAME::fetchFrom, 337 (s, p) -> s 338 ); 339 340 // note that each bundler is likely to replace this one with 341 // their own converter 342 public static final StandardBundlerParam<String> VERSION = 343 new StandardBundlerParam<>( 344 I18N.getString("param.version.name"), 345 I18N.getString("param.version.description"), 346 Arguments.CLIOptions.VERSION.getId(), 347 String.class, 348 params -> I18N.getString("param.version.default"), 349 (s, p) -> s 350 ); 351 352 @SuppressWarnings("unchecked") 353 public static final StandardBundlerParam<List<String>> LICENSE_FILE = 354 new StandardBundlerParam<>( 355 I18N.getString("param.license-file.name"), 356 I18N.getString("param.license-file.description"), 357 Arguments.CLIOptions.LICENSE_FILE.getId(), 358 (Class<List<String>>)(Object)List.class, 359 params -> Collections.<String>emptyList(), 360 (s, p) -> Arrays.asList(s.split(",")) 361 ); 362 363 public static final StandardBundlerParam<File> BUILD_ROOT = 364 new StandardBundlerParam<>( 365 I18N.getString("param.build-root.name"), 366 I18N.getString("param.build-root.description"), 367 Arguments.CLIOptions.BUILD_ROOT.getId(), 368 File.class, 369 params -> { 370 try { 371 return Files.createTempDirectory( 372 "jdk.packager").toFile(); 373 } catch (IOException ioe) { 374 return null; 375 } 376 }, 377 (s, p) -> new File(s) 378 ); 379 380 public static final StandardBundlerParam<String> IDENTIFIER = 381 new StandardBundlerParam<>( 382 I18N.getString("param.identifier.name"), 383 I18N.getString("param.identifier.description"), 384 Arguments.CLIOptions.IDENTIFIER.getId(), 385 String.class, 386 params -> { 387 String s = MAIN_CLASS.fetchFrom(params); 388 if (s == null) return null; 389 390 int idx = s.lastIndexOf("."); 391 if (idx >= 1) { 392 return s.substring(0, idx); 393 } 394 return s; 395 }, 396 (s, p) -> s 397 ); 398 399 public static final StandardBundlerParam<String> PREFERENCES_ID = 400 new StandardBundlerParam<>( 401 I18N.getString("param.preferences-id.name"), 402 I18N.getString("param.preferences-id.description"), 403 "preferencesID", 404 String.class, 405 p -> Optional.ofNullable(IDENTIFIER.fetchFrom(p)). 406 orElse("").replace('.', '/'), 407 (s, p) -> s 408 ); 409 410 public static final StandardBundlerParam<Boolean> VERBOSE = 411 new StandardBundlerParam<>( 412 I18N.getString("param.verbose.name"), 413 I18N.getString("param.verbose.description"), 414 Arguments.CLIOptions.VERBOSE.getId(), 415 Boolean.class, 416 params -> false, 417 // valueOf(null) is false, and we actually do want null 418 (s, p) -> (s == null || "null".equalsIgnoreCase(s)) ? 419 true : Boolean.valueOf(s) 420 ); 421 422 public static final StandardBundlerParam<File> DROP_IN_RESOURCES_ROOT = 423 new StandardBundlerParam<>( 424 I18N.getString("param.drop-in-resources-root.name"), 425 I18N.getString("param.drop-in-resources-root.description"), 426 "dropinResourcesRoot", 427 File.class, 428 params -> new File("."), 429 (s, p) -> new File(s) 430 ); 431 432 public static final BundlerParamInfo<String> INSTALL_DIR = 433 new StandardBundlerParam<>( 434 I18N.getString("param.install-dir.name"), 435 I18N.getString("param.install-dir.description"), 436 Arguments.CLIOptions.INSTALL_DIR.getId(), 437 String.class, 438 params -> null, 439 (s, p) -> s 440 ); 441 442 public static final StandardBundlerParam<File> PREDEFINED_APP_IMAGE = 443 new StandardBundlerParam<>( 444 I18N.getString("param.predefined-app-image.name"), 445 I18N.getString("param.predefined-app-image.description"), 446 Arguments.CLIOptions.PREDEFINED_APP_IMAGE.getId(), 447 File.class, 448 params -> null, 449 (s, p) -> new File(s)); 450 451 public static final StandardBundlerParam<File> PREDEFINED_RUNTIME_IMAGE = 452 new StandardBundlerParam<>( 453 I18N.getString("param.predefined-runtime-image.name"), 454 I18N.getString("param.predefined-runtime-image.description"), 455 Arguments.CLIOptions.PREDEFINED_RUNTIME_IMAGE.getId(), 456 File.class, 457 params -> null, 458 (s, p) -> new File(s)); 459 460 @SuppressWarnings("unchecked") 461 public static final StandardBundlerParam<List<Map<String, ? super Object>>> SECONDARY_LAUNCHERS = 462 new StandardBundlerParam<>( 463 I18N.getString("param.secondary-launchers.name"), 464 I18N.getString("param.secondary-launchers.description"), 465 Arguments.CLIOptions.SECONDARY_LAUNCHER.getId(), 466 (Class<List<Map<String, ? super Object>>>) (Object) 467 List.class, 468 params -> new ArrayList<>(1), 469 // valueOf(null) is false, and we actually do want null 470 (s, p) -> null 471 ); 472 473 @SuppressWarnings("unchecked") 474 public static final StandardBundlerParam 475 <List<Map<String, ? super Object>>> FILE_ASSOCIATIONS = 476 new StandardBundlerParam<>( 477 I18N.getString("param.file-associations.name"), 478 I18N.getString("param.file-associations.description"), 479 Arguments.CLIOptions.FILE_ASSOCIATIONS.getId(), 480 (Class<List<Map<String, ? super Object>>>) (Object) 481 List.class, 482 params -> new ArrayList<>(1), 483 // valueOf(null) is false, and we actually do want null 484 (s, p) -> null 485 ); 486 487 @SuppressWarnings("unchecked") 488 public static final StandardBundlerParam<List<String>> FA_EXTENSIONS = 489 new StandardBundlerParam<>( 490 I18N.getString("param.fa-extension.name"), 491 I18N.getString("param.fa-extension.description"), 492 "fileAssociation.extension", 493 (Class<List<String>>) (Object) List.class, 494 params -> null, // null means not matched to an extension 495 (s, p) -> Arrays.asList(s.split("(,|\\s)+")) 496 ); 497 498 @SuppressWarnings("unchecked") 499 public static final StandardBundlerParam<List<String>> FA_CONTENT_TYPE = 500 new StandardBundlerParam<>( 501 I18N.getString("param.fa-content-type.name"), 502 I18N.getString("param.fa-content-type.description"), 503 "fileAssociation.contentType", 504 (Class<List<String>>) (Object) List.class, 505 params -> null, 506 // null means not matched to a content/mime type 507 (s, p) -> Arrays.asList(s.split("(,|\\s)+")) 508 ); 509 510 public static final StandardBundlerParam<String> FA_DESCRIPTION = 511 new StandardBundlerParam<>( 512 I18N.getString("param.fa-description.name"), 513 I18N.getString("param.fa-description.description"), 514 "fileAssociation.description", 515 String.class, 516 params -> APP_NAME.fetchFrom(params) + " File", 517 null 518 ); 519 520 public static final StandardBundlerParam<File> FA_ICON = 521 new StandardBundlerParam<>( 522 I18N.getString("param.fa-icon.name"), 523 I18N.getString("param.fa-icon.description"), 524 "fileAssociation.icon", 525 File.class, 526 ICON::fetchFrom, 527 (s, p) -> new File(s) 528 ); 529 530 @SuppressWarnings("unchecked") 531 public static final BundlerParamInfo<List<Path>> MODULE_PATH = 532 new StandardBundlerParam<>( 533 I18N.getString("param.module-path.name"), 534 I18N.getString("param.module-path.description"), 535 Arguments.CLIOptions.MODULE_PATH.getId(), 536 (Class<List<Path>>) (Object)List.class, 537 p -> { return getDefaultModulePath(); }, 538 (s, p) -> { 539 List<Path> modulePath = Arrays.asList(s 540 .split(File.pathSeparator)).stream() 541 .map(ss -> new File(ss).toPath()) 542 .collect(Collectors.toList()); 543 Path javaBasePath = null; 544 if (modulePath != null) { 545 javaBasePath = JLinkBundlerHelper 546 .findPathOfModule(modulePath, JAVABASEJMOD); 547 } 548 else { 549 modulePath = new ArrayList(); 550 } 551 552 // Add the default JDK module path to the module path. 553 if (javaBasePath == null) { 554 List<Path> jdkModulePath = getDefaultModulePath(); 555 556 if (jdkModulePath != null) { 557 modulePath.addAll(jdkModulePath); 558 javaBasePath = 559 JLinkBundlerHelper.findPathOfModule( 560 modulePath, JAVABASEJMOD); 561 } 562 } 563 564 if (javaBasePath == null || 565 !Files.exists(javaBasePath)) { 566 jdk.packager.internal.Log.info( 567 String.format(I18N.getString( 568 "warning.no.jdk.modules.found"))); 569 } 570 571 return modulePath; 572 }); 573 574 @SuppressWarnings("unchecked") 575 public static final BundlerParamInfo<String> MODULE = 576 new StandardBundlerParam<>( 577 I18N.getString("param.main.module.name"), 578 I18N.getString("param.main.module.description"), 579 Arguments.CLIOptions.MODULE.getId(), 580 String.class, 581 p -> null, 582 (s, p) -> { 583 return String.valueOf(s); 584 }); 585 586 @SuppressWarnings("unchecked") 587 public static final BundlerParamInfo<Set<String>> ADD_MODULES = 588 new StandardBundlerParam<>( 589 I18N.getString("param.add-modules.name"), 590 I18N.getString("param.add-modules.description"), 591 Arguments.CLIOptions.ADD_MODULES.getId(), 592 (Class<Set<String>>) (Object) Set.class, 593 p -> new LinkedHashSet(), 594 (s, p) -> new LinkedHashSet<>(Arrays.asList(s.split(","))) 595 ); 596 597 @SuppressWarnings("unchecked") 598 public static final BundlerParamInfo<Set<String>> LIMIT_MODULES = 599 new StandardBundlerParam<>( 600 I18N.getString("param.limit-modules.name"), 601 I18N.getString("param.limit-modules.description"), 602 Arguments.CLIOptions.LIMIT_MODULES.getId(), 603 (Class<Set<String>>) (Object) Set.class, 604 p -> new LinkedHashSet(), 605 (s, p) -> new LinkedHashSet<>(Arrays.asList(s.split(","))) 606 ); 607 608 @SuppressWarnings("unchecked") 609 public static final BundlerParamInfo<Boolean> STRIP_NATIVE_COMMANDS = 610 new StandardBundlerParam<>( 611 I18N.getString("param.strip-executables.name"), 612 I18N.getString("param.strip-executables.description"), 613 Arguments.CLIOptions.STRIP_NATIVE_COMMANDS.getId(), 614 Boolean.class, 615 p -> Boolean.FALSE, 616 (s, p) -> Boolean.valueOf(s) 617 ); 618 619 public static final BundlerParamInfo<Boolean> SINGLETON = 620 new StandardBundlerParam<> ( 621 I18N.getString("param.singleton.name"), 622 I18N.getString("param.singleton.description"), 623 Arguments.CLIOptions.SINGLETON.getId(), 624 Boolean.class, 625 params -> Boolean.FALSE, 626 (s, p) -> Boolean.valueOf(s) 627 ); 628 629 public static File getPredefinedAppImage(Map<String, ? super Object> p) { 630 File applicationImage = null; 631 if (PREDEFINED_APP_IMAGE.fetchFrom(p) != null) { 632 applicationImage = PREDEFINED_APP_IMAGE.fetchFrom(p); 633 Log.debug("Using App Image from " + applicationImage); 634 if (!applicationImage.exists()) { 635 throw new RuntimeException( 636 MessageFormat.format(I18N.getString( 637 "message.app-image-dir-does-not-exist"), 638 PREDEFINED_APP_IMAGE.getID(), 639 applicationImage.toString())); 640 } 641 } 642 return applicationImage; 643 } 644 645 public static void copyPredefinedRuntimeImage( 646 Map<String, ? super Object> p, 647 AbstractAppImageBuilder appBuilder) 648 throws IOException , ConfigException { 649 File image = PREDEFINED_RUNTIME_IMAGE.fetchFrom(p); 650 if (!image.exists()) { 651 throw new ConfigException( 652 MessageFormat.format(I18N.getString( 653 "message.runtime-image-dir-does-not-exist"), 654 PREDEFINED_RUNTIME_IMAGE.getID(), 655 image.toString()), 656 MessageFormat.format(I18N.getString( 657 "message.runtime-image-dir-does-not-exist.advice"), 658 PREDEFINED_RUNTIME_IMAGE.getID())); 659 } 660 IOUtils.copyRecursive(image.toPath(), appBuilder.getRoot()); 661 appBuilder.prepareApplicationFiles(); 662 } 663 664 public static void extractMainClassInfoFromAppResources( 665 Map<String, ? super Object> params) { 666 boolean hasMainClass = params.containsKey(MAIN_CLASS.getID()); 667 boolean hasMainJar = params.containsKey(MAIN_JAR.getID()); 668 boolean hasMainJarClassPath = params.containsKey(CLASSPATH.getID()); 669 boolean hasModule = params.containsKey(MODULE.getID()); 670 boolean jreInstaller = 671 params.containsKey(Arguments.CREATE_JRE_INSTALLER.getID()); 672 673 if (hasMainClass && hasMainJar && hasMainJarClassPath || hasModule || 674 jreInstaller) { 675 return; 676 } 677 678 // it's a pair. 679 // The [0] is the srcdir [1] is the file relative to sourcedir 680 List<String[]> filesToCheck = new ArrayList<>(); 681 682 if (hasMainJar) { 683 RelativeFileSet rfs = MAIN_JAR.fetchFrom(params); 684 for (String s : rfs.getIncludedFiles()) { 685 filesToCheck.add( 686 new String[] {rfs.getBaseDirectory().toString(), s}); 687 } 688 } else if (hasMainJarClassPath) { 689 for (String s : CLASSPATH.fetchFrom(params).split("\\s+")) { 690 if (APP_RESOURCES.fetchFrom(params) != null) { 691 filesToCheck.add( 692 new String[] {APP_RESOURCES.fetchFrom(params) 693 .getBaseDirectory().toString(), s}); 694 } 695 } 696 } else { 697 List<RelativeFileSet> rfsl = APP_RESOURCES_LIST.fetchFrom(params); 698 if (rfsl == null || rfsl.isEmpty()) { 699 return; 700 } 701 for (RelativeFileSet rfs : rfsl) { 702 if (rfs == null) continue; 703 704 for (String s : rfs.getIncludedFiles()) { 705 filesToCheck.add( 706 new String[]{rfs.getBaseDirectory().toString(), s}); 707 } 708 } 709 } 710 711 // presume the set iterates in-order 712 for (String[] fnames : filesToCheck) { 713 try { 714 // only sniff jars 715 if (!fnames[1].toLowerCase().endsWith(".jar")) continue; 716 717 File file = new File(fnames[0], fnames[1]); 718 // that actually exist 719 if (!file.exists()) continue; 720 721 try (JarFile jf = new JarFile(file)) { 722 Manifest m = jf.getManifest(); 723 Attributes attrs = (m != null) ? 724 m.getMainAttributes() : null; 725 726 if (attrs != null) { 727 if (!hasMainJar) { 728 if (fnames[0] == null) { 729 fnames[0] = file.getParentFile().toString(); 730 } 731 params.put(MAIN_JAR.getID(), new RelativeFileSet( 732 new File(fnames[0]), 733 new LinkedHashSet<>(Collections 734 .singletonList(file)))); 735 } 736 if (!hasMainJarClassPath) { 737 String cp = 738 attrs.getValue(Attributes.Name.CLASS_PATH); 739 params.put(CLASSPATH.getID(), 740 cp == null ? "" : cp); 741 } 742 break; 743 } 744 } 745 } catch (IOException ignore) { 746 ignore.printStackTrace(); 747 } 748 } 749 } 750 751 public static void validateMainClassInfoFromAppResources( 752 Map<String, ? super Object> params) throws ConfigException { 753 boolean hasMainClass = params.containsKey(MAIN_CLASS.getID()); 754 boolean hasMainJar = params.containsKey(MAIN_JAR.getID()); 755 boolean hasMainJarClassPath = params.containsKey(CLASSPATH.getID()); 756 boolean hasModule = params.containsKey(MODULE.getID()); 757 boolean hasAppImage = params.containsKey(PREDEFINED_APP_IMAGE.getID()); 758 boolean jreInstaller = 759 params.containsKey(Arguments.CREATE_JRE_INSTALLER.getID()); 760 761 if (hasMainClass && hasMainJar && hasMainJarClassPath || 762 hasModule || jreInstaller || hasAppImage) { 763 return; 764 } 765 766 extractMainClassInfoFromAppResources(params); 767 768 if (!params.containsKey(MAIN_CLASS.getID())) { 769 if (hasMainJar) { 770 throw new ConfigException( 771 MessageFormat.format(I18N.getString( 772 "error.no-main-class-with-main-jar"), 773 MAIN_JAR.fetchFrom(params)), 774 MessageFormat.format(I18N.getString( 775 "error.no-main-class-with-main-jar.advice"), 776 MAIN_JAR.fetchFrom(params))); 777 } else if (hasMainJarClassPath) { 778 throw new ConfigException( 779 I18N.getString("error.no-main-class-with-classpath"), 780 I18N.getString( 781 "error.no-main-class-with-classpath.advice")); 782 } else { 783 throw new ConfigException( 784 I18N.getString("error.no-main-class"), 785 I18N.getString("error.no-main-class.advice")); 786 } 787 } 788 } 789 790 791 private static List<String> splitStringWithEscapes(String s) { 792 List<String> l = new ArrayList<>(); 793 StringBuilder current = new StringBuilder(); 794 boolean quoted = false; 795 boolean escaped = false; 796 for (char c : s.toCharArray()) { 797 if (escaped) { 798 current.append(c); 799 } else if ('"' == c) { 800 quoted = !quoted; 801 } else if (!quoted && Character.isWhitespace(c)) { 802 l.add(current.toString()); 803 current = new StringBuilder(); 804 } else { 805 current.append(c); 806 } 807 } 808 l.add(current.toString()); 809 return l; 810 } 811 812 private static List<RelativeFileSet> 813 createAppResourcesListFromString(String s, 814 Map<String, ? super Object> objectObjectMap) { 815 List<RelativeFileSet> result = new ArrayList<>(); 816 for (String path : s.split("[:;]")) { 817 File f = new File(path); 818 if (f.getName().equals("*") || path.endsWith("/") || 819 path.endsWith("\\")) { 820 if (f.getName().equals("*")) { 821 f = f.getParentFile(); 822 } 823 Set<File> theFiles = new HashSet<>(); 824 try { 825 Files.walk(f.toPath()) 826 .filter(Files::isRegularFile) 827 .forEach(p -> theFiles.add(p.toFile())); 828 } catch (IOException e) { 829 e.printStackTrace(); 830 } 831 result.add(new RelativeFileSet(f, theFiles)); 832 } else { 833 result.add(new RelativeFileSet(f.getParentFile(), 834 Collections.singleton(f))); 835 } 836 } 837 return result; 838 } 839 840 private static RelativeFileSet getMainJar( 841 String moduleName, Map<String, ? super Object> params) { 842 for (RelativeFileSet rfs : APP_RESOURCES_LIST.fetchFrom(params)) { 843 File appResourcesRoot = rfs.getBaseDirectory(); 844 File mainJarFile = new File(appResourcesRoot, moduleName); 845 846 if (mainJarFile.exists()) { 847 return new RelativeFileSet(appResourcesRoot, 848 new LinkedHashSet<>(Collections.singletonList( 849 mainJarFile))); 850 } 851 else { 852 List<Path> modulePath = MODULE_PATH.fetchFrom(params); 853 Path modularJarPath = JLinkBundlerHelper.findPathOfModule( 854 modulePath, moduleName); 855 856 if (modularJarPath != null && Files.exists(modularJarPath)) { 857 return new RelativeFileSet(appResourcesRoot, 858 new LinkedHashSet<>(Collections.singletonList( 859 modularJarPath.toFile()))); 860 } 861 } 862 } 863 864 throw new IllegalArgumentException( 865 new ConfigException(MessageFormat.format(I18N.getString( 866 "error.main-jar-does-not-exist"), 867 moduleName), I18N.getString( 868 "error.main-jar-does-not-exist.advice"))); 869 } 870 871 public static List<Path> getDefaultModulePath() { 872 List<Path> result = new ArrayList(); 873 Path jdkModulePath = Paths.get( 874 System.getProperty("java.home"), "jmods").toAbsolutePath(); 875 876 if (jdkModulePath != null && Files.exists(jdkModulePath)) { 877 result.add(jdkModulePath); 878 } 879 else { 880 // On a developer build the JDK Home isn't where we expect it 881 // relative to the jmods directory. Do some extra 882 // processing to find it. 883 Map<String, String> env = System.getenv(); 884 885 if (env.containsKey("JDK_HOME")) { 886 jdkModulePath = Paths.get(env.get("JDK_HOME"), 887 ".." + File.separator + "images" 888 + File.separator + "jmods").toAbsolutePath(); 889 890 if (jdkModulePath != null && Files.exists(jdkModulePath)) { 891 result.add(jdkModulePath); 892 } 893 } 894 } 895 896 return result; 897 } 898 }