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.jpackager.internal; 27 28 import jdk.jpackager.internal.bundlers.BundleParams; 29 import jdk.jpackager.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.jpackager.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 public static final StandardBundlerParam<String> SOURCE_DIR = 116 new StandardBundlerParam<>( 117 I18N.getString("param.source-dir.name"), 118 I18N.getString("param.source-dir.description"), 119 Arguments.CLIOptions.INPUT.getId(), 120 String.class, 121 p -> null, 122 (s, p) -> { 123 String value = String.valueOf(s); 124 if (value.charAt(value.length() - 1) == 125 File.separatorChar) { 126 return value.substring(0, value.length() - 1); 127 } 128 else { 129 return value; 130 } 131 } 132 ); 133 134 @SuppressWarnings("unchecked") 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.jpackager").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<Boolean> FORCE = 423 new StandardBundlerParam<>( 424 I18N.getString("param.force.name"), 425 I18N.getString("param.force.description"), 426 Arguments.CLIOptions.FORCE.getId(), 427 Boolean.class, 428 params -> false, 429 // valueOf(null) is false, and we actually do want null 430 (s, p) -> (s == null || "null".equalsIgnoreCase(s)) ? 431 true : Boolean.valueOf(s) 432 ); 433 434 public static final StandardBundlerParam<File> DROP_IN_RESOURCES_ROOT = 435 new StandardBundlerParam<>( 436 I18N.getString("param.drop-in-resources-root.name"), 437 I18N.getString("param.drop-in-resources-root.description"), 438 "dropinResourcesRoot", 439 File.class, 440 params -> new File("."), 441 (s, p) -> new File(s) 442 ); 443 444 public static final BundlerParamInfo<String> INSTALL_DIR = 445 new StandardBundlerParam<>( 446 I18N.getString("param.install-dir.name"), 447 I18N.getString("param.install-dir.description"), 448 Arguments.CLIOptions.INSTALL_DIR.getId(), 449 String.class, 450 params -> null, 451 (s, p) -> s 452 ); 453 454 public static final StandardBundlerParam<File> PREDEFINED_APP_IMAGE = 455 new StandardBundlerParam<>( 456 I18N.getString("param.predefined-app-image.name"), 457 I18N.getString("param.predefined-app-image.description"), 458 Arguments.CLIOptions.PREDEFINED_APP_IMAGE.getId(), 459 File.class, 460 params -> null, 461 (s, p) -> new File(s)); 462 463 public static final StandardBundlerParam<File> PREDEFINED_RUNTIME_IMAGE = 464 new StandardBundlerParam<>( 465 I18N.getString("param.predefined-runtime-image.name"), 466 I18N.getString("param.predefined-runtime-image.description"), 467 Arguments.CLIOptions.PREDEFINED_RUNTIME_IMAGE.getId(), 468 File.class, 469 params -> null, 470 (s, p) -> new File(s)); 471 472 @SuppressWarnings("unchecked") 473 public static final StandardBundlerParam<List<Map<String, ? super Object>>> SECONDARY_LAUNCHERS = 474 new StandardBundlerParam<>( 475 I18N.getString("param.secondary-launchers.name"), 476 I18N.getString("param.secondary-launchers.description"), 477 Arguments.CLIOptions.SECONDARY_LAUNCHER.getId(), 478 (Class<List<Map<String, ? super Object>>>) (Object) 479 List.class, 480 params -> new ArrayList<>(1), 481 // valueOf(null) is false, and we actually do want null 482 (s, p) -> null 483 ); 484 485 @SuppressWarnings("unchecked") 486 public static final StandardBundlerParam 487 <List<Map<String, ? super Object>>> FILE_ASSOCIATIONS = 488 new StandardBundlerParam<>( 489 I18N.getString("param.file-associations.name"), 490 I18N.getString("param.file-associations.description"), 491 Arguments.CLIOptions.FILE_ASSOCIATIONS.getId(), 492 (Class<List<Map<String, ? super Object>>>) (Object) 493 List.class, 494 params -> new ArrayList<>(1), 495 // valueOf(null) is false, and we actually do want null 496 (s, p) -> null 497 ); 498 499 @SuppressWarnings("unchecked") 500 public static final StandardBundlerParam<List<String>> FA_EXTENSIONS = 501 new StandardBundlerParam<>( 502 I18N.getString("param.fa-extension.name"), 503 I18N.getString("param.fa-extension.description"), 504 "fileAssociation.extension", 505 (Class<List<String>>) (Object) List.class, 506 params -> null, // null means not matched to an extension 507 (s, p) -> Arrays.asList(s.split("(,|\\s)+")) 508 ); 509 510 @SuppressWarnings("unchecked") 511 public static final StandardBundlerParam<List<String>> FA_CONTENT_TYPE = 512 new StandardBundlerParam<>( 513 I18N.getString("param.fa-content-type.name"), 514 I18N.getString("param.fa-content-type.description"), 515 "fileAssociation.contentType", 516 (Class<List<String>>) (Object) List.class, 517 params -> null, 518 // null means not matched to a content/mime type 519 (s, p) -> Arrays.asList(s.split("(,|\\s)+")) 520 ); 521 522 public static final StandardBundlerParam<String> FA_DESCRIPTION = 523 new StandardBundlerParam<>( 524 I18N.getString("param.fa-description.name"), 525 I18N.getString("param.fa-description.description"), 526 "fileAssociation.description", 527 String.class, 528 params -> APP_NAME.fetchFrom(params) + " File", 529 null 530 ); 531 532 public static final StandardBundlerParam<File> FA_ICON = 533 new StandardBundlerParam<>( 534 I18N.getString("param.fa-icon.name"), 535 I18N.getString("param.fa-icon.description"), 536 "fileAssociation.icon", 537 File.class, 538 ICON::fetchFrom, 539 (s, p) -> new File(s) 540 ); 541 542 @SuppressWarnings("unchecked") 543 public static final BundlerParamInfo<List<Path>> MODULE_PATH = 544 new StandardBundlerParam<>( 545 I18N.getString("param.module-path.name"), 546 I18N.getString("param.module-path.description"), 547 Arguments.CLIOptions.MODULE_PATH.getId(), 548 (Class<List<Path>>) (Object)List.class, 549 p -> { return getDefaultModulePath(); }, 550 (s, p) -> { 551 List<Path> modulePath = Arrays.asList(s 552 .split(File.pathSeparator)).stream() 553 .map(ss -> new File(ss).toPath()) 554 .collect(Collectors.toList()); 555 Path javaBasePath = null; 556 if (modulePath != null) { 557 javaBasePath = JLinkBundlerHelper 558 .findPathOfModule(modulePath, JAVABASEJMOD); 559 } else { 560 modulePath = new ArrayList<Path>(); 561 } 562 563 // Add the default JDK module path to the module path. 564 if (javaBasePath == null) { 565 List<Path> jdkModulePath = getDefaultModulePath(); 566 567 if (jdkModulePath != null) { 568 modulePath.addAll(jdkModulePath); 569 javaBasePath = 570 JLinkBundlerHelper.findPathOfModule( 571 modulePath, JAVABASEJMOD); 572 } 573 } 574 575 if (javaBasePath == null || 576 !Files.exists(javaBasePath)) { 577 Log.error(String.format(I18N.getString( 578 "warning.no.jdk.modules.found"))); 579 } 580 581 return modulePath; 582 }); 583 584 public static final BundlerParamInfo<String> MODULE = 585 new StandardBundlerParam<>( 586 I18N.getString("param.main.module.name"), 587 I18N.getString("param.main.module.description"), 588 Arguments.CLIOptions.MODULE.getId(), 589 String.class, 590 p -> null, 591 (s, p) -> { 592 return String.valueOf(s); 593 }); 594 595 @SuppressWarnings("unchecked") 596 public static final BundlerParamInfo<Set<String>> ADD_MODULES = 597 new StandardBundlerParam<>( 598 I18N.getString("param.add-modules.name"), 599 I18N.getString("param.add-modules.description"), 600 Arguments.CLIOptions.ADD_MODULES.getId(), 601 (Class<Set<String>>) (Object) Set.class, 602 p -> new LinkedHashSet<String>(), 603 (s, p) -> new LinkedHashSet<>(Arrays.asList(s.split(","))) 604 ); 605 606 @SuppressWarnings("unchecked") 607 public static final BundlerParamInfo<Set<String>> LIMIT_MODULES = 608 new StandardBundlerParam<>( 609 I18N.getString("param.limit-modules.name"), 610 I18N.getString("param.limit-modules.description"), 611 Arguments.CLIOptions.LIMIT_MODULES.getId(), 612 (Class<Set<String>>) (Object) Set.class, 613 p -> new LinkedHashSet<String>(), 614 (s, p) -> new LinkedHashSet<>(Arrays.asList(s.split(","))) 615 ); 616 617 public static final BundlerParamInfo<Boolean> STRIP_NATIVE_COMMANDS = 618 new StandardBundlerParam<>( 619 I18N.getString("param.strip-executables.name"), 620 I18N.getString("param.strip-executables.description"), 621 Arguments.CLIOptions.STRIP_NATIVE_COMMANDS.getId(), 622 Boolean.class, 623 p -> Boolean.FALSE, 624 (s, p) -> Boolean.valueOf(s) 625 ); 626 627 public static final BundlerParamInfo<Boolean> SINGLETON = 628 new StandardBundlerParam<> ( 629 I18N.getString("param.singleton.name"), 630 I18N.getString("param.singleton.description"), 631 Arguments.CLIOptions.SINGLETON.getId(), 632 Boolean.class, 633 params -> Boolean.FALSE, 634 (s, p) -> Boolean.valueOf(s) 635 ); 636 637 public static File getPredefinedAppImage(Map<String, ? super Object> p) { 638 File applicationImage = null; 639 if (PREDEFINED_APP_IMAGE.fetchFrom(p) != null) { 640 applicationImage = PREDEFINED_APP_IMAGE.fetchFrom(p); 641 Log.debug("Using App Image from " + applicationImage); 642 if (!applicationImage.exists()) { 643 throw new RuntimeException( 644 MessageFormat.format(I18N.getString( 645 "message.app-image-dir-does-not-exist"), 646 PREDEFINED_APP_IMAGE.getID(), 647 applicationImage.toString())); 648 } 649 } 650 return applicationImage; 651 } 652 653 public static void copyPredefinedRuntimeImage( 654 Map<String, ? super Object> p, 655 AbstractAppImageBuilder appBuilder) 656 throws IOException , ConfigException { 657 File image = PREDEFINED_RUNTIME_IMAGE.fetchFrom(p); 658 if (!image.exists()) { 659 throw new ConfigException( 660 MessageFormat.format(I18N.getString( 661 "message.runtime-image-dir-does-not-exist"), 662 PREDEFINED_RUNTIME_IMAGE.getID(), 663 image.toString()), 664 MessageFormat.format(I18N.getString( 665 "message.runtime-image-dir-does-not-exist.advice"), 666 PREDEFINED_RUNTIME_IMAGE.getID())); 667 } 668 IOUtils.copyRecursive(image.toPath(), appBuilder.getRoot()); 669 appBuilder.prepareApplicationFiles(); 670 } 671 672 public static void extractMainClassInfoFromAppResources( 673 Map<String, ? super Object> params) { 674 boolean hasMainClass = params.containsKey(MAIN_CLASS.getID()); 675 boolean hasMainJar = params.containsKey(MAIN_JAR.getID()); 676 boolean hasMainJarClassPath = params.containsKey(CLASSPATH.getID()); 677 boolean hasModule = params.containsKey(MODULE.getID()); 678 boolean jreInstaller = 679 params.containsKey(Arguments.CREATE_JRE_INSTALLER.getID()); 680 681 if (hasMainClass && hasMainJar && hasMainJarClassPath || hasModule || 682 jreInstaller) { 683 return; 684 } 685 686 // it's a pair. 687 // The [0] is the srcdir [1] is the file relative to sourcedir 688 List<String[]> filesToCheck = new ArrayList<>(); 689 690 if (hasMainJar) { 691 RelativeFileSet rfs = MAIN_JAR.fetchFrom(params); 692 for (String s : rfs.getIncludedFiles()) { 693 filesToCheck.add( 694 new String[] {rfs.getBaseDirectory().toString(), s}); 695 } 696 } else if (hasMainJarClassPath) { 697 for (String s : CLASSPATH.fetchFrom(params).split("\\s+")) { 698 if (APP_RESOURCES.fetchFrom(params) != null) { 699 filesToCheck.add( 700 new String[] {APP_RESOURCES.fetchFrom(params) 701 .getBaseDirectory().toString(), s}); 702 } 703 } 704 } else { 705 List<RelativeFileSet> rfsl = APP_RESOURCES_LIST.fetchFrom(params); 706 if (rfsl == null || rfsl.isEmpty()) { 707 return; 708 } 709 for (RelativeFileSet rfs : rfsl) { 710 if (rfs == null) continue; 711 712 for (String s : rfs.getIncludedFiles()) { 713 filesToCheck.add( 714 new String[]{rfs.getBaseDirectory().toString(), s}); 715 } 716 } 717 } 718 719 // presume the set iterates in-order 720 for (String[] fnames : filesToCheck) { 721 try { 722 // only sniff jars 723 if (!fnames[1].toLowerCase().endsWith(".jar")) continue; 724 725 File file = new File(fnames[0], fnames[1]); 726 // that actually exist 727 if (!file.exists()) continue; 728 729 try (JarFile jf = new JarFile(file)) { 730 Manifest m = jf.getManifest(); 731 Attributes attrs = (m != null) ? 732 m.getMainAttributes() : null; 733 734 if (attrs != null) { 735 if (!hasMainJar) { 736 if (fnames[0] == null) { 737 fnames[0] = file.getParentFile().toString(); 738 } 739 params.put(MAIN_JAR.getID(), new RelativeFileSet( 740 new File(fnames[0]), 741 new LinkedHashSet<>(Collections 742 .singletonList(file)))); 743 } 744 if (!hasMainJarClassPath) { 745 String cp = 746 attrs.getValue(Attributes.Name.CLASS_PATH); 747 params.put(CLASSPATH.getID(), 748 cp == null ? "" : cp); 749 } 750 break; 751 } 752 } 753 } catch (IOException ignore) { 754 ignore.printStackTrace(); 755 } 756 } 757 } 758 759 public static void validateMainClassInfoFromAppResources( 760 Map<String, ? super Object> params) throws ConfigException { 761 boolean hasMainClass = params.containsKey(MAIN_CLASS.getID()); 762 boolean hasMainJar = params.containsKey(MAIN_JAR.getID()); 763 boolean hasMainJarClassPath = params.containsKey(CLASSPATH.getID()); 764 boolean hasModule = params.containsKey(MODULE.getID()); 765 boolean hasAppImage = params.containsKey(PREDEFINED_APP_IMAGE.getID()); 766 boolean jreInstaller = 767 params.containsKey(Arguments.CREATE_JRE_INSTALLER.getID()); 768 769 if (hasMainClass && hasMainJar && hasMainJarClassPath || 770 hasModule || jreInstaller || hasAppImage) { 771 return; 772 } 773 774 extractMainClassInfoFromAppResources(params); 775 776 if (!params.containsKey(MAIN_CLASS.getID())) { 777 if (hasMainJar) { 778 throw new ConfigException( 779 MessageFormat.format(I18N.getString( 780 "error.no-main-class-with-main-jar"), 781 MAIN_JAR.fetchFrom(params)), 782 MessageFormat.format(I18N.getString( 783 "error.no-main-class-with-main-jar.advice"), 784 MAIN_JAR.fetchFrom(params))); 785 } else if (hasMainJarClassPath) { 786 throw new ConfigException( 787 I18N.getString("error.no-main-class-with-classpath"), 788 I18N.getString( 789 "error.no-main-class-with-classpath.advice")); 790 } else { 791 throw new ConfigException( 792 I18N.getString("error.no-main-class"), 793 I18N.getString("error.no-main-class.advice")); 794 } 795 } 796 } 797 798 799 private static List<String> splitStringWithEscapes(String s) { 800 List<String> l = new ArrayList<>(); 801 StringBuilder current = new StringBuilder(); 802 boolean quoted = false; 803 boolean escaped = false; 804 for (char c : s.toCharArray()) { 805 if (escaped) { 806 current.append(c); 807 } else if ('"' == c) { 808 quoted = !quoted; 809 } else if (!quoted && Character.isWhitespace(c)) { 810 l.add(current.toString()); 811 current = new StringBuilder(); 812 } else { 813 current.append(c); 814 } 815 } 816 l.add(current.toString()); 817 return l; 818 } 819 820 private static List<RelativeFileSet> 821 createAppResourcesListFromString(String s, 822 Map<String, ? super Object> objectObjectMap) { 823 List<RelativeFileSet> result = new ArrayList<>(); 824 for (String path : s.split("[:;]")) { 825 File f = new File(path); 826 if (f.getName().equals("*") || path.endsWith("/") || 827 path.endsWith("\\")) { 828 if (f.getName().equals("*")) { 829 f = f.getParentFile(); 830 } 831 Set<File> theFiles = new HashSet<>(); 832 try { 833 Files.walk(f.toPath()) 834 .filter(Files::isRegularFile) 835 .forEach(p -> theFiles.add(p.toFile())); 836 } catch (IOException e) { 837 e.printStackTrace(); 838 } 839 result.add(new RelativeFileSet(f, theFiles)); 840 } else { 841 result.add(new RelativeFileSet(f.getParentFile(), 842 Collections.singleton(f))); 843 } 844 } 845 return result; 846 } 847 848 private static RelativeFileSet getMainJar( 849 String moduleName, Map<String, ? super Object> params) { 850 for (RelativeFileSet rfs : APP_RESOURCES_LIST.fetchFrom(params)) { 851 File appResourcesRoot = rfs.getBaseDirectory(); 852 File mainJarFile = new File(appResourcesRoot, moduleName); 853 854 if (mainJarFile.exists()) { 855 return new RelativeFileSet(appResourcesRoot, 856 new LinkedHashSet<>(Collections.singletonList( 857 mainJarFile))); 858 } 859 else { 860 List<Path> modulePath = MODULE_PATH.fetchFrom(params); 861 Path modularJarPath = JLinkBundlerHelper.findPathOfModule( 862 modulePath, moduleName); 863 864 if (modularJarPath != null && Files.exists(modularJarPath)) { 865 return new RelativeFileSet(appResourcesRoot, 866 new LinkedHashSet<>(Collections.singletonList( 867 modularJarPath.toFile()))); 868 } 869 } 870 } 871 872 throw new IllegalArgumentException( 873 new ConfigException(MessageFormat.format(I18N.getString( 874 "error.main-jar-does-not-exist"), 875 moduleName), I18N.getString( 876 "error.main-jar-does-not-exist.advice"))); 877 } 878 879 public static List<Path> getDefaultModulePath() { 880 List<Path> result = new ArrayList<Path>(); 881 Path jdkModulePath = Paths.get( 882 System.getProperty("java.home"), "jmods").toAbsolutePath(); 883 884 if (jdkModulePath != null && Files.exists(jdkModulePath)) { 885 result.add(jdkModulePath); 886 } 887 else { 888 // On a developer build the JDK Home isn't where we expect it 889 // relative to the jmods directory. Do some extra 890 // processing to find it. 891 Map<String, String> env = System.getenv(); 892 893 if (env.containsKey("JDK_HOME")) { 894 jdkModulePath = Paths.get(env.get("JDK_HOME"), 895 ".." + File.separator + "images" 896 + File.separator + "jmods").toAbsolutePath(); 897 898 if (jdkModulePath != null && Files.exists(jdkModulePath)) { 899 result.add(jdkModulePath); 900 } 901 } 902 } 903 904 return result; 905 } 906 }