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 }