1 /*
   2  * Copyright (c) 2014, 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.bundlers;
  27 
  28 import com.sun.javafx.tools.packager.Log;
  29 import com.sun.javafx.tools.packager.PackagerLib;
  30 import com.sun.javafx.tools.packager.bundlers.BundleParams;
  31 import com.sun.javafx.tools.packager.bundlers.RelativeFileSet;
  32 
  33 import java.io.File;
  34 import java.io.IOException;
  35 import java.io.StringReader;
  36 import java.nio.file.Files;
  37 import java.text.MessageFormat;
  38 import java.util.*;
  39 import java.util.function.BiFunction;
  40 import java.util.function.Function;
  41 import java.util.jar.Attributes;
  42 import java.util.jar.JarFile;
  43 import java.util.jar.Manifest;
  44 
  45 public class StandardBundlerParam<T> extends BundlerParamInfo<T> {
  46 
  47     private static final ResourceBundle I18N =
  48             ResourceBundle.getBundle("com.oracle.bundlers.StandardBundlerParam");
  49 
  50     public StandardBundlerParam(String name, String description, String id,
  51                                 Class<T> valueType, String[] fallbackIDs,
  52                                 Function<Map<String, ? super Object>, T> defaultValueFunction,
  53                                 boolean requiresUserSetting,
  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.fallbackIDs = fallbackIDs;
  60         this.defaultValueFunction = defaultValueFunction;
  61         this.requiresUserSetting = requiresUserSetting;
  62         this.stringConverter = stringConverter;
  63     }
  64 
  65     public static final StandardBundlerParam<RelativeFileSet> RUNTIME =
  66             new StandardBundlerParam<>(
  67                     I18N.getString("param.runtime.name"),
  68                     I18N.getString("param.runtime.description"),
  69                     BundleParams.PARAM_RUNTIME,
  70                     RelativeFileSet.class,
  71                     null,
  72                     params -> null,
  73                     false,
  74                     (s, p) -> null
  75             );
  76 
  77     public static final StandardBundlerParam<RelativeFileSet> APP_RESOURCES =
  78             new StandardBundlerParam<>(
  79                     I18N.getString("param.app-resources.name"),
  80                     I18N.getString("param.app-resource.description"),
  81                     BundleParams.PARAM_APP_RESOURCES,
  82                     RelativeFileSet.class,
  83                     null,
  84                     null, // no default.  Required parameter
  85                     false,
  86                     null // no string translation, tool must provide complex type
  87             );
  88 
  89     public static final StandardBundlerParam<File> ICON =
  90             new StandardBundlerParam<>(
  91                     I18N.getString("param.icon-file.name"),
  92                     I18N.getString("param.icon-file.description"),
  93                     BundleParams.PARAM_ICON,
  94                     File.class,
  95                     null,
  96                     params -> null,
  97                     false,
  98                     (s, p) -> new File(s)
  99             );
 100 
 101 
 102     public static final StandardBundlerParam<String> MAIN_CLASS =
 103             new StandardBundlerParam<>(
 104                     I18N.getString("param.main-class.name"),
 105                     I18N.getString("param.main-class.description"),
 106                     BundleParams.PARAM_APPLICATION_CLASS,
 107                     String.class,
 108                     null,
 109                     params -> {
 110                         extractParamsFromAppResources(params);
 111                         return (String) params.get(BundleParams.PARAM_APPLICATION_CLASS);
 112                     },
 113                     false,
 114                     (s, p) -> s
 115             );
 116 
 117     public static final StandardBundlerParam<String> APP_NAME =
 118             new StandardBundlerParam<>(
 119                     I18N.getString("param.app-name.name"),
 120                     I18N.getString("param.app-name.description"),
 121                     BundleParams.PARAM_NAME,
 122                     String.class,
 123                     null,
 124                     params -> {
 125                         String s = MAIN_CLASS.fetchFrom(params);
 126                         if (s == null) return null;
 127 
 128                         int idx = s.lastIndexOf(".");
 129                         if (idx >= 0) {
 130                             return s.substring(idx+1);
 131                         }
 132                         return s;
 133                     },
 134                     true,
 135                     (s, p) -> s
 136             );
 137 
 138     public static final StandardBundlerParam<String> VENDOR =
 139             new StandardBundlerParam<>(
 140                     I18N.getString("param.vendor.name"),
 141                     I18N.getString("param.vendor.description"),
 142                     BundleParams.PARAM_VENDOR,
 143                     String.class,
 144                     null,
 145                     params -> I18N.getString("param.vendor.default"),
 146                     false,
 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                     null,
 157                     params -> I18N.getString("param.category.default"),
 158                     false,
 159                     (s, p) -> s
 160             );
 161 
 162     public static final StandardBundlerParam<String> DESCRIPTION =
 163             new StandardBundlerParam<>(
 164                     I18N.getString("param.description.name"),
 165                     I18N.getString("param.description.description"),
 166                     BundleParams.PARAM_DESCRIPTION,
 167                     String.class,
 168                     new String[] {APP_NAME.getID()},
 169                     params -> I18N.getString("param.description.default"),
 170                     false,
 171                     (s, p) -> s
 172             );
 173 
 174     public static final StandardBundlerParam<String> COPYRIGHT =
 175             new StandardBundlerParam<>(
 176                     I18N.getString("param.copyright.name"),
 177                     I18N.getString("param.copyright.description"),
 178                     BundleParams.PARAM_COPYRIGHT,
 179                     String.class,
 180                     null,
 181                     params -> MessageFormat.format(I18N.getString("param.copyright.default"), new Date()),
 182                     false,
 183                     (s, p) -> s
 184             );
 185 
 186     // note that each bundler is likely to replace this one with their own converter
 187     public static final StandardBundlerParam<RelativeFileSet> MAIN_JAR =
 188             new StandardBundlerParam<>(
 189                     I18N.getString("param.main-jar.name"),
 190                     I18N.getString("param.main-jar.description"),
 191                     "mainJar",
 192                     RelativeFileSet.class,
 193                     null,
 194                     params -> {
 195                         extractParamsFromAppResources(params);
 196                         return (RelativeFileSet) params.get("mainJar");
 197                     },
 198                     false,
 199                     (s, p) -> {
 200                         File f = new File(s);
 201                         return new RelativeFileSet(f.getParentFile(), new LinkedHashSet<>(Arrays.asList(f)));
 202                     }
 203             );
 204 
 205     public static final StandardBundlerParam<String> MAIN_JAR_CLASSPATH =
 206             new StandardBundlerParam<>(
 207                     I18N.getString("param.main-jar-classpath.name"),
 208                     I18N.getString("param.main-jar-classpath.description"),
 209                     "classpath",
 210                     String.class,
 211                     null,
 212                     params -> {
 213                         extractParamsFromAppResources(params);
 214                         String cp = (String) params.get("classpath");
 215                         return cp == null ? "" : cp;
 216                     },
 217                     false,
 218                     (s, p) -> s
 219             );
 220 
 221     public static final StandardBundlerParam<Boolean> USE_FX_PACKAGING =
 222             new StandardBundlerParam<>(
 223                     I18N.getString("param.use-javafx-packaging.name"),
 224                     I18N.getString("param.use-javafx-packaging.description"),
 225                     "fxPackaging",
 226                     Boolean.class,
 227                     null,
 228                     params -> {
 229                         extractParamsFromAppResources(params);
 230                         return (Boolean) params.get("fxPackaging");
 231                     },
 232                     false,
 233                     (s, p) -> Boolean.valueOf(s)
 234             );
 235 
 236     @SuppressWarnings("unchecked")
 237     public static final StandardBundlerParam<List<String>> JVM_OPTIONS =
 238             new StandardBundlerParam<>(
 239                     I18N.getString("param.jvm-options.name"),
 240                     I18N.getString("param.jvm-options.description"),
 241                     "jvmOptions",
 242                     (Class<List<String>>) (Object) List.class,
 243                     null,
 244                     params -> Collections.emptyList(),
 245                     false,
 246                     (s, p) -> Arrays.asList(s.split("\\s+"))
 247             );
 248 
 249     @SuppressWarnings("unchecked")
 250     public static final StandardBundlerParam<Map<String, String>> JVM_PROPERTIES =
 251             new StandardBundlerParam<>(
 252                     I18N.getString("param.jvm-system-properties.name"),
 253                     I18N.getString("param.jvm-system-properties.description"),
 254                     "jvmProperties",
 255                     (Class<Map<String, String>>) (Object) Map.class,
 256                     null,
 257                     params -> Collections.emptyMap(),
 258                     false,
 259                     (s, params) -> {
 260                         Map<String, String> map = new HashMap<>();
 261                         try {
 262                             Properties p = new Properties();
 263                             p.load(new StringReader(s));
 264                             for (Map.Entry<Object, Object> entry : p.entrySet()) {
 265                                 map.put((String)entry.getKey(), (String)entry.getValue());
 266                             }
 267                         } catch (IOException e) {
 268                             e.printStackTrace();
 269                         }
 270                         return map;
 271                     }
 272             );
 273 
 274     @SuppressWarnings("unchecked")
 275     public static final StandardBundlerParam<Map<String, String>> USER_JVM_OPTIONS =
 276             new StandardBundlerParam<>(
 277                     I18N.getString("param.user-jvm-options.name"),
 278                     I18N.getString("param.user-jvm-options.description"),
 279                     "userJvmOptions",
 280                     (Class<Map<String, String>>) (Object) Map.class,
 281                     null,
 282                     params -> Collections.emptyMap(),
 283                     false,
 284                     (s, params) -> {
 285                         Map<String, String> map = new HashMap<>();
 286                         try {
 287                             Properties p = new Properties();
 288                             p.load(new StringReader(s));
 289                             for (Map.Entry<Object, Object> entry : p.entrySet()) {
 290                                 map.put((String)entry.getKey(), (String)entry.getValue());
 291                             }
 292                         } catch (IOException e) {
 293                             e.printStackTrace();
 294                         }
 295                         return map;
 296                     }
 297             );
 298 
 299     public static final StandardBundlerParam<String> TITLE =
 300             new StandardBundlerParam<>(
 301                     I18N.getString("param.title.name"),
 302                     I18N.getString("param.title.description"), //?? but what does it do?
 303                     BundleParams.PARAM_TITLE,
 304                     String.class,
 305                     new String[] {APP_NAME.getID()},
 306                     APP_NAME::fetchFrom,
 307                     false,
 308                     (s, p) -> s
 309             );
 310 
 311 
 312     // note that each bundler is likely to replace this one with their own converter
 313     public static final StandardBundlerParam<String> VERSION =
 314             new StandardBundlerParam<>(
 315                     I18N.getString("param.version.name"),
 316                     I18N.getString("param.version.description"),
 317                     BundleParams.PARAM_VERSION,
 318                     String.class,
 319                     null,
 320                     params -> I18N.getString("param.version.default"),
 321                     false,
 322                     (s, p) -> s
 323             );
 324 
 325     public static final StandardBundlerParam<Boolean> SYSTEM_WIDE =
 326             new StandardBundlerParam<>(
 327                     I18N.getString("param.system-wide.name"),
 328                     I18N.getString("param.system-wide.description"),
 329                     BundleParams.PARAM_SYSTEM_WIDE,
 330                     Boolean.class,
 331                     null,
 332                     params -> null,
 333                     false,
 334                     // valueOf(null) is false, and we actually do want null in some cases
 335                     (s, p) -> (s == null || "null".equalsIgnoreCase(s))? null : Boolean.valueOf(s)
 336             );
 337 
 338     public static final StandardBundlerParam<Boolean> SERVICE_HINT  =
 339             new StandardBundlerParam<>(
 340                     I18N.getString("param.service-hint.name"),
 341                     I18N.getString("param.service-hint.description"),
 342                     "serviceHint",
 343                     Boolean.class,
 344                     null,
 345                     params -> false,
 346                     false,
 347                     // valueOf(null) is false, and we actually do want null in some cases
 348                     (s, p) -> (s == null || "null".equalsIgnoreCase(s))? true : Boolean.valueOf(s)
 349             );
 350 
 351     public static final StandardBundlerParam<Boolean> START_ON_INSTALL  =
 352             new StandardBundlerParam<>(
 353                     I18N.getString("param.start-on-install.name"),
 354                     I18N.getString("param.start-on-install.description"),
 355                     "startOnInstall",
 356                     Boolean.class,
 357                     null,
 358                     params -> false,
 359                     false,
 360                     // valueOf(null) is false, and we actually do want null in some cases
 361                     (s, p) -> (s == null || "null".equalsIgnoreCase(s))? true : Boolean.valueOf(s)
 362             );
 363 
 364     public static final StandardBundlerParam<Boolean> STOP_ON_UNINSTALL  =
 365             new StandardBundlerParam<>(
 366                     I18N.getString("param.stop-on-uninstall.name"),
 367                     I18N.getString("param.stop-on-uninstall.description"),
 368                     "stopOnUninstall",
 369                     Boolean.class,
 370                     null,
 371                     params -> false,
 372                     false,
 373                     // valueOf(null) is false, and we actually do want null in some cases
 374                     (s, p) -> (s == null || "null".equalsIgnoreCase(s))? true : Boolean.valueOf(s)
 375             );
 376 
 377     public static final StandardBundlerParam<Boolean> RUN_AT_STARTUP  =
 378             new StandardBundlerParam<>(
 379                     I18N.getString("param.run-at-startup.name"),
 380                     I18N.getString("param.run-at-startup.description"),
 381                     "runAtStartup",
 382                     Boolean.class,
 383                     null,
 384                     params -> false,
 385                     false,
 386                     // valueOf(null) is false, and we actually do want null in some cases
 387                     (s, p) -> (s == null || "null".equalsIgnoreCase(s))? true : Boolean.valueOf(s)
 388             );
 389 
 390     public static final StandardBundlerParam<Boolean> SHORTCUT_HINT =
 391             new StandardBundlerParam<>(
 392                     I18N.getString("param.desktop-shortcut-hint.name"),
 393                     I18N.getString("param.desktop-shortcut-hint.description"),
 394                     BundleParams.PARAM_SHORTCUT,
 395                     Boolean.class,
 396                     null,
 397                     params -> false,
 398                     false,
 399                     // valueOf(null) is false, and we actually do want null in some cases
 400                     (s, p) -> (s == null || "null".equalsIgnoreCase(s))? false : Boolean.valueOf(s)
 401             );
 402 
 403     public static final StandardBundlerParam<Boolean> MENU_HINT =
 404             new StandardBundlerParam<>(
 405                     I18N.getString("param.menu-shortcut-hint.name"),
 406                     I18N.getString("param.menu-shortcut-hint.description"),
 407                     BundleParams.PARAM_MENU,
 408                     Boolean.class,
 409                     null,
 410                     params -> true,
 411                     false,
 412                     // valueOf(null) is false, and we actually do want null in some cases
 413                     (s, p) -> (s == null || "null".equalsIgnoreCase(s))? true : Boolean.valueOf(s)
 414             );
 415 
 416     @SuppressWarnings("unchecked")
 417     public static final StandardBundlerParam<List<String>> LICENSE_FILES =
 418             new StandardBundlerParam<>(
 419                     I18N.getString("param.license-files.name"),
 420                     I18N.getString("param.license-files.description"), //FIXME incorrect
 421                     BundleParams.PARAM_LICENSE_FILES,
 422                     (Class<List<String>>)(Object)List.class,
 423                     null,
 424                     params -> Collections.<String>emptyList(),
 425                     false,
 426                     (s, p) -> Arrays.asList(s.split(","))
 427             );
 428 
 429     public static final BundlerParamInfo<String> LICENSE_TYPE =
 430             new StandardBundlerParam<> (
 431                     I18N.getString("param.license-type.name"),
 432                     I18N.getString("param.license-type.description"),
 433                     BundleParams.PARAM_LICENSE_TYPE,
 434                     String.class, null,
 435                     params -> I18N.getString("param.license-type.default"),
 436                     false, (s, p) -> s
 437             );
 438 
 439     public static final StandardBundlerParam<File> BUILD_ROOT =
 440             new StandardBundlerParam<>(
 441                     I18N.getString("param.build-root.name"),
 442                     I18N.getString("param.build-root.description"),
 443                     "buildRoot",
 444                     File.class,
 445                     null,
 446                     params -> {
 447                         try {
 448                             return Files.createTempDirectory("fxbundler").toFile();
 449                         } catch (IOException ioe) {
 450                             return null;
 451                         }
 452                     },
 453                     false,
 454                     (s, p) -> new File(s)
 455             );
 456 
 457     public static final StandardBundlerParam<String> IDENTIFIER =
 458             new StandardBundlerParam<>(
 459                     I18N.getString("param.identifier.name"),
 460                     I18N.getString("param.identifier.description"),
 461                     BundleParams.PARAM_IDENTIFIER,
 462                     String.class,
 463                     null,
 464                     params -> {
 465                         String s = MAIN_CLASS.fetchFrom(params);
 466                         if (s == null) return null;
 467 
 468                         int idx = s.lastIndexOf(".");
 469                         if (idx >= 1) {
 470                             return s.substring(0, idx);
 471                         }
 472                         return s;
 473                     },
 474                     false,
 475                     (s, p) -> s
 476             );
 477 
 478     public static final StandardBundlerParam<String> PREFERENCES_ID =
 479             new StandardBundlerParam<>(
 480                     I18N.getString("param.preferences-id.name"),
 481                     I18N.getString("param.preferences-id.description"),
 482                     "preferencesID",
 483                     String.class,
 484                     new String[] {IDENTIFIER.getID()},
 485                     params -> null, // todo take the package of the main app class
 486                     false,
 487                     (s, p) -> s
 488             );
 489 
 490     public static final StandardBundlerParam<Boolean> VERBOSE  =
 491             new StandardBundlerParam<>(
 492                     I18N.getString("param.verbose.name"),
 493                     I18N.getString("param.verbose.description"),
 494                     "verbose",
 495                     Boolean.class,
 496                     null,
 497                     params -> false,
 498                     false,
 499                     // valueOf(null) is false, and we actually do want null in some cases
 500                     (s, p) -> (s == null || "null".equalsIgnoreCase(s))? true : Boolean.valueOf(s)
 501             );
 502 
 503     public static void extractParamsFromAppResources(Map<String, ? super Object> params) {
 504         RelativeFileSet appResources = APP_RESOURCES.fetchFrom(params);
 505 
 506         if (appResources == null) {
 507             return;
 508         }
 509         boolean hasMainClass = params.containsKey(MAIN_CLASS.getID());
 510         boolean hasMainJar = params.containsKey(MAIN_JAR.getID());
 511         boolean hasMainJarClassPath = params.containsKey(MAIN_JAR_CLASSPATH.getID());
 512 
 513         if (hasMainClass && hasMainJar && hasMainJarClassPath) {
 514             return;
 515         }
 516         String declaredMainClass = (String) params.get(MAIN_CLASS.getID());
 517 
 518         File srcdir = appResources.getBaseDirectory();
 519         // presume the set iterates in-order
 520         for (String fname : appResources.getIncludedFiles()) {
 521             try {
 522                 File file = new File(srcdir, fname);
 523                 JarFile jf = new JarFile(file);
 524                 Manifest m = jf.getManifest();
 525                 Attributes attrs = (m != null) ? m.getMainAttributes() : null;
 526 
 527                 if (attrs != null) {
 528                     String mainClass = attrs.getValue(Attributes.Name.MAIN_CLASS);
 529                     String fxMain = attrs.getValue(PackagerLib.MANIFEST_JAVAFX_MAIN);
 530                     if (hasMainClass) {
 531                         if (declaredMainClass.equals(fxMain)) {
 532                             params.put(USE_FX_PACKAGING.getID(), true);
 533                         } else if (declaredMainClass.equals(mainClass)) {
 534                             params.put(USE_FX_PACKAGING.getID(), false);
 535                         } else {
 536                             if (fxMain != null) {
 537                                 Log.info(MessageFormat.format(I18N.getString("message.fx-app-does-not-match-specified-main"), fname, fxMain, declaredMainClass));
 538                             }
 539                             if (mainClass != null) {
 540                                 Log.info(MessageFormat.format(I18N.getString("message.main-class-does-not-match-specified-main"), fname, mainClass, declaredMainClass));
 541                             }
 542                             continue;
 543                         }
 544                     } else {
 545                         if (fxMain != null) {
 546                             params.put(USE_FX_PACKAGING.getID(), true);
 547                             params.put(MAIN_CLASS.getID(), fxMain);
 548                         } else if (mainClass != null) {
 549                             params.put(USE_FX_PACKAGING.getID(), false);
 550                             params.put(MAIN_CLASS.getID(), mainClass);
 551                         } else {
 552                             continue;
 553                         }
 554                     }
 555                     if (!hasMainJar) {
 556                         params.put(MAIN_JAR.getID(), new RelativeFileSet(appResources.getBaseDirectory(), new LinkedHashSet<>(Arrays.asList(file))));
 557                     }
 558                     if (!hasMainJarClassPath) {
 559                         String cp = attrs.getValue(Attributes.Name.CLASS_PATH);
 560                         params.put(MAIN_JAR_CLASSPATH.getID(), cp == null ? "" : cp);
 561                     }
 562                     break;
 563                 }
 564             } catch (IOException ignore) {
 565                 ignore.printStackTrace();
 566             }
 567         }
 568     }
 569 }