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