1 /* 2 * Copyright (c) 2014, 2015, 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 com.oracle.tools.packager; 27 28 import com.sun.javafx.tools.packager.bundlers.BundleParams; 29 30 import java.io.File; 31 import java.io.IOException; 32 import java.io.StringReader; 33 import java.nio.file.Files; 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.function.BiFunction; 49 import java.util.function.Function; 50 import java.util.jar.Attributes; 51 import java.util.jar.JarFile; 52 import java.util.jar.Manifest; 53 import java.util.regex.Pattern; 54 55 public class StandardBundlerParam<T> extends BundlerParamInfo<T> { 56 57 public static final String MANIFEST_JAVAFX_MAIN ="JavaFX-Application-Class"; 58 public static final String MANIFEST_PRELOADER = "JavaFX-Preloader-Class"; 59 60 private static final ResourceBundle I18N = 61 ResourceBundle.getBundle(StandardBundlerParam.class.getName()); 62 63 public StandardBundlerParam(String name, String description, String id, 64 Class<T> valueType, 65 Function<Map<String, ? super Object>, T> defaultValueFunction, 66 BiFunction<String, Map<String, ? super Object>, T> stringConverter) { 67 this.name = name; 68 this.description = description; 69 this.id = id; 70 this.valueType = valueType; 71 this.defaultValueFunction = defaultValueFunction; 72 this.stringConverter = stringConverter; 73 } 74 75 public static final StandardBundlerParam<RelativeFileSet> APP_RESOURCES = 76 new StandardBundlerParam<>( 77 I18N.getString("param.app-resources.name"), 78 I18N.getString("param.app-resource.description"), 79 BundleParams.PARAM_APP_RESOURCES, 80 RelativeFileSet.class, 81 null, // no default. Required parameter 82 null // no string translation, tool must provide complex type 83 ); 84 85 @SuppressWarnings("unchecked") 86 public static final StandardBundlerParam<List<RelativeFileSet>> APP_RESOURCES_LIST = 87 new StandardBundlerParam<>( 88 I18N.getString("param.app-resources-list.name"), 89 I18N.getString("param.app-resource-list.description"), 90 BundleParams.PARAM_APP_RESOURCES + "List", 91 (Class<List<RelativeFileSet>>) (Object) List.class, 92 p -> new ArrayList<>(Collections.singletonList(APP_RESOURCES.fetchFrom(p))), // Default is appResources, as a single item list 93 StandardBundlerParam::createAppResourcesListFromString 94 ); 95 96 private static List<RelativeFileSet> createAppResourcesListFromString(String s, Map<String, ? super Object> objectObjectMap) { 97 List<RelativeFileSet> result = new ArrayList<>(); 98 for (String path : s.split("[:;]")) { 99 File f = new File(path); 100 if (f.getName().equals("*") || path.endsWith("/") || path.endsWith("\\")) { 101 if (f.getName().equals("*")) { 102 f = f.getParentFile(); 103 } 104 Set<File> theFiles = new HashSet<>(); 105 try { 106 Files.walk(f.toPath()) 107 .filter(Files::isRegularFile) 108 .forEach(p -> theFiles.add(p.toFile())); 109 } catch (IOException e) { 110 e.printStackTrace(); 111 } 112 result.add(new RelativeFileSet(f, theFiles)); 113 } else { 114 result.add(new RelativeFileSet(f.getParentFile(), Collections.singleton(f))); 115 } 116 } 117 return result; 118 } 119 120 public static final StandardBundlerParam<File> ICON = 121 new StandardBundlerParam<>( 122 I18N.getString("param.icon-file.name"), 123 I18N.getString("param.icon-file.description"), 124 BundleParams.PARAM_ICON, 125 File.class, 126 params -> null, 127 (s, p) -> new File(s) 128 ); 129 130 131 public static final StandardBundlerParam<String> MAIN_CLASS = 132 new StandardBundlerParam<>( 133 I18N.getString("param.main-class.name"), 134 I18N.getString("param.main-class.description"), 135 BundleParams.PARAM_APPLICATION_CLASS, 136 String.class, 137 params -> { 138 //FIXME sniff modules 139 extractMainClassInfoFromAppResources(params); 140 return (String) params.get(BundleParams.PARAM_APPLICATION_CLASS); 141 }, 142 (s, p) -> s 143 ); 144 145 public static final StandardBundlerParam<String> APP_NAME = 146 new StandardBundlerParam<>( 147 I18N.getString("param.app-name.name"), 148 I18N.getString("param.app-name.description"), 149 BundleParams.PARAM_NAME, 150 String.class, 151 params -> { 152 String s = MAIN_CLASS.fetchFrom(params); 153 if (s == null) return null; 154 155 int idx = s.lastIndexOf("."); 156 if (idx >= 0) { 157 return s.substring(idx+1); 158 } 159 return s; 160 }, 161 (s, p) -> s 162 ); 163 164 private static Pattern TO_FS_NAME = Pattern.compile("\\s|[\\\\/?:*<>|]"); // keep out invalid/undesireable filename characters 165 166 public static final StandardBundlerParam<String> APP_FS_NAME = 167 new StandardBundlerParam<>( 168 I18N.getString("param.app-fs-name.name"), 169 I18N.getString("param.app-fs-name.description"), 170 "name.fs", 171 String.class, 172 params -> TO_FS_NAME.matcher(APP_NAME.fetchFrom(params)).replaceAll(""), 173 (s, p) -> s 174 ); 175 176 177 public static final StandardBundlerParam<String> VENDOR = 178 new StandardBundlerParam<>( 179 I18N.getString("param.vendor.name"), 180 I18N.getString("param.vendor.description"), 181 BundleParams.PARAM_VENDOR, 182 String.class, 183 params -> I18N.getString("param.vendor.default"), 184 (s, p) -> s 185 ); 186 187 public static final StandardBundlerParam<String> CATEGORY = 188 new StandardBundlerParam<>( 189 I18N.getString("param.category.name"), 190 I18N.getString("param.category.description"), 191 BundleParams.PARAM_CATEGORY, 192 String.class, 193 params -> I18N.getString("param.category.default"), 194 (s, p) -> s 195 ); 196 197 public static final StandardBundlerParam<String> DESCRIPTION = 198 new StandardBundlerParam<>( 199 I18N.getString("param.description.name"), 200 I18N.getString("param.description.description"), 201 BundleParams.PARAM_DESCRIPTION, 202 String.class, 203 params -> params.containsKey(APP_NAME.getID()) 204 ? APP_NAME.fetchFrom(params) 205 : I18N.getString("param.description.default"), 206 (s, p) -> s 207 ); 208 209 public static final StandardBundlerParam<String> COPYRIGHT = 210 new StandardBundlerParam<>( 211 I18N.getString("param.copyright.name"), 212 I18N.getString("param.copyright.description"), 213 BundleParams.PARAM_COPYRIGHT, 214 String.class, 215 params -> MessageFormat.format(I18N.getString("param.copyright.default"), new Date()), 216 (s, p) -> s 217 ); 218 219 // note that each bundler is likely to replace this one with their own converter 220 public static final StandardBundlerParam<RelativeFileSet> MAIN_JAR = 221 new StandardBundlerParam<>( 222 I18N.getString("param.main-jar.name"), 223 I18N.getString("param.main-jar.description"), 224 "mainJar", 225 RelativeFileSet.class, 226 params -> { 227 extractMainClassInfoFromAppResources(params); 228 return (RelativeFileSet) params.get("mainJar"); 229 }, 230 (s, p) -> { 231 for (RelativeFileSet rfs : APP_RESOURCES_LIST.fetchFrom(p)) { 232 File appResourcesRoot = rfs.getBaseDirectory(); 233 File f = new File(appResourcesRoot, s); 234 if (f.exists()) { 235 return new RelativeFileSet(appResourcesRoot, new LinkedHashSet<>(Collections.singletonList(f))); 236 } 237 } 238 throw new IllegalArgumentException( 239 new ConfigException( 240 MessageFormat.format(I18N.getString("error.main-jar-does-not-exist"), s), 241 I18N.getString("error.main-jar-does-not-exist.advice"))); 242 } 243 ); 244 245 public static final StandardBundlerParam<String> CLASSPATH = 246 new StandardBundlerParam<>( 247 I18N.getString("param.classpath.name"), 248 I18N.getString("param.classpath.description"), 249 "classpath", 250 String.class, 251 params -> { 252 extractMainClassInfoFromAppResources(params); 253 String cp = (String) params.get("classpath"); 254 return cp == null ? "" : cp; 255 }, 256 (s, p) -> s.replace(File.pathSeparator, " ") 257 ); 258 259 public static final StandardBundlerParam<Boolean> USE_FX_PACKAGING = 260 new StandardBundlerParam<>( 261 I18N.getString("param.use-javafx-packaging.name"), 262 I18N.getString("param.use-javafx-packaging.description"), 263 "fxPackaging", 264 Boolean.class, 265 params -> { 266 extractMainClassInfoFromAppResources(params); 267 Boolean result = (Boolean) params.get("fxPackaging"); 268 return (result == null) ? Boolean.FALSE : result; 269 }, 270 (s, p) -> Boolean.valueOf(s) 271 ); 272 273 @SuppressWarnings("unchecked") 274 public static final StandardBundlerParam<List<String>> ARGUMENTS = 275 new StandardBundlerParam<>( 276 I18N.getString("param.arguments.name"), 277 I18N.getString("param.arguments.description"), 278 "arguments", 279 (Class<List<String>>) (Object) List.class, 280 params -> Collections.emptyList(), 281 (s, p) -> splitStringWithEscapes(s) 282 ); 283 284 @SuppressWarnings("unchecked") 285 public static final StandardBundlerParam<List<String>> JVM_OPTIONS = 286 new StandardBundlerParam<>( 287 I18N.getString("param.jvm-options.name"), 288 I18N.getString("param.jvm-options.description"), 289 "jvmOptions", 290 (Class<List<String>>) (Object) List.class, 291 params -> Collections.emptyList(), 292 (s, p) -> Arrays.asList(s.split("\\s+")) 293 ); 294 295 @SuppressWarnings("unchecked") 296 public static final StandardBundlerParam<Map<String, String>> JVM_PROPERTIES = 297 new StandardBundlerParam<>( 298 I18N.getString("param.jvm-system-properties.name"), 299 I18N.getString("param.jvm-system-properties.description"), 300 "jvmProperties", 301 (Class<Map<String, String>>) (Object) Map.class, 302 params -> Collections.emptyMap(), 303 (s, params) -> { 304 Map<String, String> map = new HashMap<>(); 305 try { 306 Properties p = new Properties(); 307 p.load(new StringReader(s)); 308 for (Map.Entry<Object, Object> entry : p.entrySet()) { 309 map.put((String)entry.getKey(), (String)entry.getValue()); 310 } 311 } catch (IOException e) { 312 e.printStackTrace(); 313 } 314 return map; 315 } 316 ); 317 318 @SuppressWarnings("unchecked") 319 public static final StandardBundlerParam<Map<String, String>> USER_JVM_OPTIONS = 320 new StandardBundlerParam<>( 321 I18N.getString("param.user-jvm-options.name"), 322 I18N.getString("param.user-jvm-options.description"), 323 "userJvmOptions", 324 (Class<Map<String, String>>) (Object) Map.class, 325 params -> Collections.emptyMap(), 326 (s, params) -> { 327 Map<String, String> map = new HashMap<>(); 328 try { 329 Properties p = new Properties(); 330 p.load(new StringReader(s)); 331 for (Map.Entry<Object, Object> entry : p.entrySet()) { 332 map.put((String)entry.getKey(), (String)entry.getValue()); 333 } 334 } catch (IOException e) { 335 e.printStackTrace(); 336 } 337 return map; 338 } 339 ); 340 341 public static final StandardBundlerParam<String> TITLE = 342 new StandardBundlerParam<>( 343 I18N.getString("param.title.name"), 344 I18N.getString("param.title.description"), //?? but what does it do? 345 BundleParams.PARAM_TITLE, 346 String.class, 347 APP_NAME::fetchFrom, 348 (s, p) -> s 349 ); 350 351 352 // note that each bundler is likely to replace this one with their own converter 353 public static final StandardBundlerParam<String> VERSION = 354 new StandardBundlerParam<>( 355 I18N.getString("param.version.name"), 356 I18N.getString("param.version.description"), 357 BundleParams.PARAM_VERSION, 358 String.class, 359 params -> I18N.getString("param.version.default"), 360 (s, p) -> s 361 ); 362 363 public static final StandardBundlerParam<Boolean> SYSTEM_WIDE = 364 new StandardBundlerParam<>( 365 I18N.getString("param.system-wide.name"), 366 I18N.getString("param.system-wide.description"), 367 BundleParams.PARAM_SYSTEM_WIDE, 368 Boolean.class, 369 params -> null, 370 // valueOf(null) is false, and we actually do want null in some cases 371 (s, p) -> (s == null || "null".equalsIgnoreCase(s))? null : Boolean.valueOf(s) 372 ); 373 374 public static final StandardBundlerParam<Boolean> SERVICE_HINT = 375 new StandardBundlerParam<>( 376 I18N.getString("param.service-hint.name"), 377 I18N.getString("param.service-hint.description"), 378 BundleParams.PARAM_SERVICE_HINT, 379 Boolean.class, 380 params -> false, 381 (s, p) -> (s == null || "null".equalsIgnoreCase(s))? false : Boolean.valueOf(s) 382 ); 383 384 public static final StandardBundlerParam<Boolean> START_ON_INSTALL = 385 new StandardBundlerParam<>( 386 I18N.getString("param.start-on-install.name"), 387 I18N.getString("param.start-on-install.description"), 388 "startOnInstall", 389 Boolean.class, 390 params -> false, 391 (s, p) -> (s == null || "null".equalsIgnoreCase(s))? false : Boolean.valueOf(s) 392 ); 393 394 public static final StandardBundlerParam<Boolean> STOP_ON_UNINSTALL = 395 new StandardBundlerParam<>( 396 I18N.getString("param.stop-on-uninstall.name"), 397 I18N.getString("param.stop-on-uninstall.description"), 398 "stopOnUninstall", 399 Boolean.class, 400 params -> true, 401 (s, p) -> (s == null || "null".equalsIgnoreCase(s))? true : Boolean.valueOf(s) 402 ); 403 404 public static final StandardBundlerParam<Boolean> RUN_AT_STARTUP = 405 new StandardBundlerParam<>( 406 I18N.getString("param.run-at-startup.name"), 407 I18N.getString("param.run-at-startup.description"), 408 "runAtStartup", 409 Boolean.class, 410 params -> false, 411 (s, p) -> (s == null || "null".equalsIgnoreCase(s))? false : Boolean.valueOf(s) 412 ); 413 414 public static final StandardBundlerParam<Boolean> SIGN_BUNDLE = 415 new StandardBundlerParam<>( 416 I18N.getString("param.sign-bundle.name"), 417 I18N.getString("param.sign-bundle.description"), 418 "signBundle", 419 Boolean.class, 420 params -> null, 421 // valueOf(null) is false, and we actually do want null in some cases 422 (s, p) -> (s == null || "null".equalsIgnoreCase(s))? null : Boolean.valueOf(s) 423 ); 424 425 public static final StandardBundlerParam<Boolean> SHORTCUT_HINT = 426 new StandardBundlerParam<>( 427 I18N.getString("param.desktop-shortcut-hint.name"), 428 I18N.getString("param.desktop-shortcut-hint.description"), 429 BundleParams.PARAM_SHORTCUT, 430 Boolean.class, 431 params -> false, 432 // valueOf(null) is false, and we actually do want null in some cases 433 (s, p) -> (s == null || "null".equalsIgnoreCase(s))? false : Boolean.valueOf(s) 434 ); 435 436 public static final StandardBundlerParam<Boolean> MENU_HINT = 437 new StandardBundlerParam<>( 438 I18N.getString("param.menu-shortcut-hint.name"), 439 I18N.getString("param.menu-shortcut-hint.description"), 440 BundleParams.PARAM_MENU, 441 Boolean.class, 442 params -> true, 443 // valueOf(null) is false, and we actually do want null in some cases 444 (s, p) -> (s == null || "null".equalsIgnoreCase(s))? true : Boolean.valueOf(s) 445 ); 446 447 @SuppressWarnings("unchecked") 448 public static final StandardBundlerParam<List<String>> LICENSE_FILE = 449 new StandardBundlerParam<>( 450 I18N.getString("param.license-file.name"), 451 I18N.getString("param.license-file.description"), 452 BundleParams.PARAM_LICENSE_FILE, 453 (Class<List<String>>)(Object)List.class, 454 params -> Collections.<String>emptyList(), 455 (s, p) -> Arrays.asList(s.split(",")) 456 ); 457 458 public static final BundlerParamInfo<String> LICENSE_TYPE = 459 new StandardBundlerParam<>( 460 I18N.getString("param.license-type.name"), 461 I18N.getString("param.license-type.description"), 462 BundleParams.PARAM_LICENSE_TYPE, 463 String.class, 464 params -> I18N.getString("param.license-type.default"), 465 (s, p) -> s 466 ); 467 468 public static final StandardBundlerParam<File> BUILD_ROOT = 469 new StandardBundlerParam<>( 470 I18N.getString("param.build-root.name"), 471 I18N.getString("param.build-root.description"), 472 "buildRoot", 473 File.class, 474 params -> { 475 try { 476 return Files.createTempDirectory("fxbundler").toFile(); 477 } catch (IOException ioe) { 478 return null; 479 } 480 }, 481 (s, p) -> new File(s) 482 ); 483 484 public static final StandardBundlerParam<String> IDENTIFIER = 485 new StandardBundlerParam<>( 486 I18N.getString("param.identifier.name"), 487 I18N.getString("param.identifier.description"), 488 BundleParams.PARAM_IDENTIFIER, 489 String.class, 490 params -> { 491 String s = MAIN_CLASS.fetchFrom(params); 492 if (s == null) return null; 493 494 int idx = s.lastIndexOf("."); 495 if (idx >= 1) { 496 return s.substring(0, idx); 497 } 498 return s; 499 }, 500 (s, p) -> s 501 ); 502 503 public static final StandardBundlerParam<String> PREFERENCES_ID = 504 new StandardBundlerParam<>( 505 I18N.getString("param.preferences-id.name"), 506 I18N.getString("param.preferences-id.description"), 507 "preferencesID", 508 String.class, 509 p -> Optional.ofNullable(IDENTIFIER.fetchFrom(p)).orElse("").replace('.', '/'), 510 (s, p) -> s 511 ); 512 513 public static final StandardBundlerParam<String> PRELOADER_CLASS = 514 new StandardBundlerParam<>( 515 I18N.getString("param.preloader.name"), 516 I18N.getString("param.preloader.description"), 517 "preloader", 518 String.class, 519 p -> null, 520 null 521 ); 522 523 public static final StandardBundlerParam<Boolean> VERBOSE = 524 new StandardBundlerParam<>( 525 I18N.getString("param.verbose.name"), 526 I18N.getString("param.verbose.description"), 527 "verbose", 528 Boolean.class, 529 params -> false, 530 // valueOf(null) is false, and we actually do want null in some cases 531 (s, p) -> (s == null || "null".equalsIgnoreCase(s))? true : Boolean.valueOf(s) 532 ); 533 534 public static final StandardBundlerParam<File> DROP_IN_RESOURCES_ROOT = 535 new StandardBundlerParam<>( 536 I18N.getString("param.drop-in-resources-root.name"), 537 I18N.getString("param.drop-in-resources-root.description"), 538 "dropinResourcesRoot", 539 File.class, 540 params -> null, 541 (s, p) -> new File(s) 542 ); 543 544 @SuppressWarnings("unchecked") 545 public static final StandardBundlerParam<List<Map<String, ? super Object>>> SECONDARY_LAUNCHERS = 546 new StandardBundlerParam<>( 547 I18N.getString("param.secondary-launchers.name"), 548 I18N.getString("param.secondary-launchers.description"), 549 "secondaryLaunchers", 550 (Class<List<Map<String, ? super Object>>>) (Object) List.class, 551 params -> new ArrayList<>(1), 552 // valueOf(null) is false, and we actually do want null in some cases 553 (s, p) -> null 554 ); 555 556 @SuppressWarnings("unchecked") 557 public static final StandardBundlerParam<List<Map<String, ? super Object>>> FILE_ASSOCIATIONS = 558 new StandardBundlerParam<>( 559 I18N.getString("param.file-associations.name"), 560 I18N.getString("param.file-associations.description"), 561 "fileAssociations", 562 (Class<List<Map<String, ? super Object>>>) (Object) List.class, 563 params -> new ArrayList<>(1), 564 // valueOf(null) is false, and we actually do want null in some cases 565 (s, p) -> null 566 ); 567 568 @SuppressWarnings("unchecked") 569 public static final StandardBundlerParam<List<String>> FA_EXTENSIONS = 570 new StandardBundlerParam<>( 571 I18N.getString("param.fa-extension.name"), 572 I18N.getString("param.fa-extension.description"), 573 "fileAssociation.extension", 574 (Class<List<String>>) (Object) List.class, 575 params -> null, // null means not matched to an extension 576 (s, p) -> Arrays.asList(s.split("(,|\\s)+")) 577 ); 578 579 @SuppressWarnings("unchecked") 580 public static final StandardBundlerParam<List<String>> FA_CONTENT_TYPE = 581 new StandardBundlerParam<>( 582 I18N.getString("param.fa-content-type.name"), 583 I18N.getString("param.fa-content-type.description"), 584 "fileAssociation.contentType", 585 (Class<List<String>>) (Object) List.class, 586 params -> null, // null means not matched to a content/mime type 587 (s, p) -> Arrays.asList(s.split("(,|\\s)+")) 588 ); 589 590 public static final StandardBundlerParam<String> FA_DESCRIPTION = 591 new StandardBundlerParam<>( 592 I18N.getString("param.fa-description.name"), 593 I18N.getString("param.fa-description.description"), 594 "fileAssociation.description", 595 String.class, 596 params -> APP_NAME.fetchFrom(params) + " File", 597 null 598 ); 599 600 public static final StandardBundlerParam<File> FA_ICON = 601 new StandardBundlerParam<>( 602 I18N.getString("param.fa-icon.name"), 603 I18N.getString("param.fa-icon.description"), 604 "fileAssociation.icon", 605 File.class, 606 ICON::fetchFrom, 607 (s, p) -> new File(s) 608 ); 609 610 public static final StandardBundlerParam<Boolean> UNLOCK_COMMERCIAL_FEATURES = 611 new StandardBundlerParam<>( 612 I18N.getString("param.commercial-features.name"), 613 I18N.getString("param.commercial-features.description"), 614 "commercialFeatures", 615 Boolean.class, 616 p -> false, 617 (s, p) -> Boolean.parseBoolean(s) 618 ); 619 620 public static final StandardBundlerParam<Boolean> ENABLE_APP_CDS = 621 new StandardBundlerParam<>( 622 I18N.getString("param.com-app-cds.name"), 623 I18N.getString("param.com-app-cds.description"), 624 "commercial.AppCDS", 625 Boolean.class, 626 p -> false, 627 (s, p) -> Boolean.parseBoolean(s) 628 ); 629 630 public static final StandardBundlerParam<String> APP_CDS_CACHE_MODE = 631 new StandardBundlerParam<>( 632 I18N.getString("param.com-app-cds-cache-mode.name"), 633 I18N.getString("param.com-app-cds-cache-mode.description"), 634 "commercial.AppCDS.cache", 635 String.class, 636 p -> "auto", 637 (s, p) -> s 638 ); 639 640 @SuppressWarnings("unchecked") 641 public static final StandardBundlerParam<List<String>> APP_CDS_CLASS_ROOTS = 642 new StandardBundlerParam<>( 643 I18N.getString("param.com-app-cds-root.name"), 644 I18N.getString("param.com-app-cds-root.description"), 645 "commercial.AppCDS.classRoots", 646 (Class<List<String>>)((Object)List.class), 647 p -> Collections.singletonList(MAIN_CLASS.fetchFrom(p)), 648 (s, p) -> Arrays.asList(s.split("[ ,:]")) 649 ); 650 651 public static void extractMainClassInfoFromAppResources(Map<String, ? super Object> params) { 652 boolean hasMainClass = params.containsKey(MAIN_CLASS.getID()); 653 boolean hasMainJar = params.containsKey(MAIN_JAR.getID()); 654 boolean hasMainJarClassPath = params.containsKey(CLASSPATH.getID()); 655 boolean hasPreloader = params.containsKey(PRELOADER_CLASS.getID()); 656 657 if (hasMainClass && hasMainJar && hasMainJarClassPath) { 658 return; 659 } 660 // it's a pair. The [0] is the srcdir [1] is the file relative to sourcedir 661 List<String[]> filesToCheck = new ArrayList<>(); 662 663 if (hasMainJar) { 664 RelativeFileSet rfs = MAIN_JAR.fetchFrom(params); 665 for (String s : rfs.getIncludedFiles()) { 666 filesToCheck.add(new String[]{rfs.getBaseDirectory().toString(), s}); 667 } 668 } else if (hasMainJarClassPath) { 669 for (String s : CLASSPATH.fetchFrom(params).split("\\s+")) { 670 filesToCheck.add(new String[] {APP_RESOURCES.fetchFrom(params).getBaseDirectory().toString(), s}); 671 } 672 } else { 673 List<RelativeFileSet> rfsl = APP_RESOURCES_LIST.fetchFrom(params); 674 if (rfsl == null || rfsl.isEmpty()) { 675 return; 676 } 677 for (RelativeFileSet rfs : rfsl) { 678 if (rfs == null) continue; 679 680 for (String s : rfs.getIncludedFiles()) { 681 filesToCheck.add(new String[]{rfs.getBaseDirectory().toString(), s}); 682 } 683 } 684 } 685 686 String declaredMainClass = (String) params.get(MAIN_CLASS.getID()); 687 688 // presume the set iterates in-order 689 for (String[] fnames : filesToCheck) { 690 try { 691 // only sniff jars 692 if (!fnames[1].toLowerCase().endsWith(".jar")) continue; 693 694 File file = new File(fnames[0], fnames[1]); 695 // that actually exist 696 if (!file.exists()) continue; 697 698 try (JarFile jf = new JarFile(file)) { 699 Manifest m = jf.getManifest(); 700 Attributes attrs = (m != null) ? m.getMainAttributes() : null; 701 702 if (attrs != null) { 703 String mainClass = attrs.getValue(Attributes.Name.MAIN_CLASS); 704 String fxMain = attrs.getValue(MANIFEST_JAVAFX_MAIN); 705 String preloaderClass = attrs.getValue(MANIFEST_PRELOADER); 706 if (hasMainClass) { 707 if (declaredMainClass.equals(fxMain)) { 708 params.put(USE_FX_PACKAGING.getID(), true); 709 } else if (declaredMainClass.equals(mainClass)) { 710 params.put(USE_FX_PACKAGING.getID(), false); 711 } else { 712 if (fxMain != null) { 713 Log.info(MessageFormat.format(I18N.getString("message.fx-app-does-not-match-specified-main"), fnames[1], fxMain, declaredMainClass)); 714 } 715 if (mainClass != null) { 716 Log.info(MessageFormat.format(I18N.getString("message.main-class-does-not-match-specified-main"), fnames[1], mainClass, declaredMainClass)); 717 } 718 continue; 719 } 720 } else { 721 if (fxMain != null) { 722 params.put(USE_FX_PACKAGING.getID(), true); 723 params.put(MAIN_CLASS.getID(), fxMain); 724 } else if (mainClass != null) { 725 params.put(USE_FX_PACKAGING.getID(), false); 726 params.put(MAIN_CLASS.getID(), mainClass); 727 } else { 728 continue; 729 } 730 } 731 if (!hasPreloader && preloaderClass != null) { 732 params.put(PRELOADER_CLASS.getID(), preloaderClass); 733 } 734 if (!hasMainJar) { 735 if (fnames[0] == null) { 736 fnames[0] = file.getParentFile().toString(); 737 } 738 params.put(MAIN_JAR.getID(), new RelativeFileSet(new File(fnames[0]), new LinkedHashSet<>(Collections.singletonList(file)))); 739 } 740 if (!hasMainJarClassPath) { 741 String cp = attrs.getValue(Attributes.Name.CLASS_PATH); 742 params.put(CLASSPATH.getID(), cp == null ? "" : cp); 743 } 744 break; 745 } 746 } 747 } catch (IOException ignore) { 748 ignore.printStackTrace(); 749 } 750 } 751 } 752 753 public static void validateMainClassInfoFromAppResources(Map<String, ? super Object> params) throws ConfigException { 754 boolean hasMainClass = params.containsKey(MAIN_CLASS.getID()); 755 boolean hasMainJar = params.containsKey(MAIN_JAR.getID()); 756 boolean hasMainJarClassPath = params.containsKey(CLASSPATH.getID()); 757 758 if (hasMainClass && hasMainJar && hasMainJarClassPath) { 759 return; 760 } 761 762 extractMainClassInfoFromAppResources(params); 763 if (!params.containsKey(MAIN_CLASS.getID())) { 764 if (hasMainJar) { 765 throw new ConfigException( 766 MessageFormat.format(I18N.getString("error.no-main-class-with-main-jar"), 767 MAIN_JAR.fetchFrom(params)), 768 MessageFormat.format(I18N.getString("error.no-main-class-with-main-jar.advice"), 769 MAIN_JAR.fetchFrom(params))); 770 } else if (hasMainJarClassPath) { 771 throw new ConfigException( 772 I18N.getString("error.no-main-class-with-classpath"), 773 I18N.getString("error.no-main-class-with-classpath.advice")); 774 } else { 775 throw new ConfigException( 776 I18N.getString("error.no-main-class"), 777 I18N.getString("error.no-main-class.advice")); 778 } 779 } 780 } 781 782 783 private static List<String> splitStringWithEscapes(String s) { 784 List<String> l = new ArrayList<>(); 785 StringBuilder current = new StringBuilder(); 786 boolean quoted = false; 787 boolean escaped = false; 788 for (char c : s.toCharArray()) { 789 if (escaped) { 790 current.append(c); 791 } else if ('"' == c) { 792 quoted = !quoted; 793 } else if (!quoted && Character.isWhitespace(c)) { 794 l.add(current.toString()); 795 current = new StringBuilder(); 796 } else { 797 current.append(c); 798 } 799 } 800 l.add(current.toString()); 801 return l; 802 } 803 }