1 /*
   2  * Copyright (c) 2012, 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.sun.javafx.tools.packager.bundlers;
  27 
  28 import com.oracle.bundlers.BundlerParamInfo;
  29 import com.oracle.bundlers.StandardBundlerParam;
  30 import com.sun.javafx.tools.packager.Log;
  31 import com.sun.javafx.tools.packager.PackagerLib;
  32 
  33 import java.io.File;
  34 import java.io.IOException;
  35 import java.util.*;
  36 import java.util.jar.Attributes;
  37 import java.util.jar.JarFile;
  38 import java.util.jar.Manifest;
  39 
  40 public class BundleParams {
  41       
  42     final protected Map<String, ? super Object> params;
  43     
  44     public static final String PARAM_RUNTIME                = "runtime"; // RelativeFileSet //KEY
  45     public static final String PARAM_APP_RESOURCES          = "appResources"; // RelativeFileSet //KEY
  46     public static final String PARAM_TYPE                   = "type"; // BundlerType
  47     public static final String PARAM_BUNDLE_FORMAT          = "bundleFormat"; // String
  48     public static final String PARAM_ICON                   = "icon"; // String //KEY
  49 
  50     /* Name of bundle file and native launcher.
  51        Also used as CFBundleName on Mac */
  52     public static final String PARAM_APP_NAME               = "appName"; // String //KEY
  53     public static final String PARAM_NAME                   = "name"; // String //KEY
  54 
  55     /* application vendor, used by most of the bundlers */
  56     public static final String PARAM_VENDOR                 = "vendor"; // String //KEY
  57 
  58     /* email name and email, only used for debian */
  59     public static final String PARAM_EMAIL                  = "email"; // String  //KEY
  60 
  61     /* Copyright. Used on Mac */
  62     public static final String PARAM_COPYRIGHT              = "copyright"; // String //KEY
  63 
  64     /* GUID on windows for MSI, CFBundleIdentifier on Mac
  65        If not compatible with requirements then bundler either do not bundle
  66        or autogenerate */
  67     public static final String PARAM_IDENTIFIER             = "identifier"; // String  //KEY
  68 
  69     /* shortcut preferences */
  70     public static final String PARAM_SHORTCUT               = "shortcutHint"; // boolean //KEY
  71     public static final String PARAM_MENU                   = "menuHint"; // boolean //KEY
  72 
  73     /* Application version. Format may differ for different bundlers */
  74     public static final String PARAM_VERSION                = "appVersion"; // String //KEY
  75     /* Application category. Used at least on Mac/Linux. Value is platform specific */
  76     public static final String PARAM_CATEGORY               = "applicationCategory"; // String //KEY
  77 
  78     /* Optional short application */
  79     public static final String PARAM_TITLE                  = "title"; // String //KEY
  80 
  81     /* Optional application description. Used by MSI and on Linux */
  82     public static final String PARAM_DESCRIPTION            = "description"; // String //KEY
  83 
  84     /* License type. Needed on Linux (rpm) */
  85     public static final String PARAM_LICENSE_TYPE           = "licenseType"; // String //KEY
  86 
  87     /* File(s) with license. Format is OS/bundler specific */
  88     public static final String PARAM_LICENSE_FILES          = "licenseFiles"; // List<String> //KEY
  89 
  90     /* user or system level install.
  91        null means "default" */
  92     public static final String PARAM_SYSTEM_WIDE            = "systemWide"; // Boolean //KEY
  93 
  94     /* Main application class. Not used directly but used to derive default values */
  95     public static final String PARAM_APPLICATION_CLASS      = "applicationClass"; // String //KEY
  96 
  97     //list of jvm args (in theory string can contain spaces and need to be escaped
  98     private List<String> jvmargs = new LinkedList<>();
  99     //list of jvm args (in theory string can contain spaces and need to be escaped
 100     private Map<String, String> jvmUserArgs = new HashMap<>();
 101 
 102     //list of jvm properties (can also be passed as VM args
 103     private Map<String, String> jvmProperties = new HashMap<>();
 104 
 105     /**
 106      * create a new bundle with all default values
 107      */
 108     public BundleParams() {
 109         params = new HashMap<>();
 110     }
 111 
 112     /**
 113      * Create a bundle params with a copy of the params 
 114      * @param params map of initial parameters to be copied in.
 115      */
 116     public BundleParams(Map<String, ?> params) {
 117         this.params = new HashMap<>(params);
 118     }
 119 
 120     public void addAllBundleParams(Map<String, ? super Object> p) {
 121         params.putAll(p);
 122     }
 123 
 124     public <C> C fetchParam(BundlerParamInfo<C> paramInfo) {
 125         return paramInfo.fetchFrom(params);
 126     }
 127     
 128     @SuppressWarnings("unchecked")
 129     public <C> C fetchParamWithDefault(Class<C> klass, C defaultValue, String... keys) {
 130         for (String key : keys) {
 131             Object o = params.get(key);
 132             if (klass.isInstance(o)) {
 133                 return (C) o;
 134             } else if (params.containsKey(keys) && o == null) {
 135                 return null;
 136                 // } else if (o != null) {
 137                 // TODO log an error.
 138             }
 139         }
 140         return defaultValue;
 141     }
 142 
 143     public <C> C fetchParam(Class<C> klass, String... keys) {
 144         return fetchParamWithDefault(klass, null, keys);
 145     }
 146 
 147     //NOTE: we do not care about application parameters here
 148     // as they will be embeded into jar file manifest and
 149     // java launcher will take care of them!
 150     
 151     public Map<String, ? super Object> getBundleParamsAsMap() {
 152         return new HashMap<String, Object>(params);
 153     }
 154 
 155     public void setJvmargs(List<String> jvmargs) {
 156         this.jvmargs = jvmargs;
 157     }
 158 
 159     public void setJvmUserArgs(Map<String, String> userArgs) {
 160         this.jvmUserArgs = userArgs;
 161     }
 162 
 163     public void setJvmProperties(Map<String, String> jvmProperties) {
 164         this.jvmProperties = jvmProperties;
 165     }
 166 
 167     public List<String> getAllJvmOptions() {
 168         List<String> all = new LinkedList<>();
 169         all.addAll(jvmargs);
 170         for(String k: jvmProperties.keySet()) {
 171             all.add("-D"+k+"="+jvmProperties.get(k)); //TODO: We are not escaping values here...
 172         }
 173         return all;
 174     }
 175 
 176     public Map<String, String> getAllJvmUserOptions() {
 177         Map<String, String> all = new HashMap<>();
 178         all.putAll(jvmUserArgs);
 179         return all;
 180     }
 181 
 182     public String getApplicationID() {
 183         return fetchParam(StandardBundlerParam.IDENTIFIER);        
 184     }
 185 
 186     public String getPreferencesID() {
 187         return fetchParam(StandardBundlerParam.PREFERENCES_ID);
 188     }
 189 
 190     public String getTitle() {
 191         return fetchParam(StandardBundlerParam.TITLE); //FIXME title
 192     }
 193 
 194     public void setTitle(String title) {
 195         putUnlessNull(PARAM_TITLE, title);
 196     }
 197 
 198     public String getApplicationClass() {
 199         return fetchParam(StandardBundlerParam.MAIN_CLASS);
 200     }
 201 
 202     public void setApplicationClass(String applicationClass) {
 203         putUnlessNull(PARAM_APPLICATION_CLASS, applicationClass);
 204     }
 205 
 206     public String getAppVersion() {
 207         return fetchParam(StandardBundlerParam.VERSION);
 208     }
 209 
 210     public void setAppVersion(String version) {
 211         putUnlessNull(PARAM_VERSION, version);
 212     }
 213 
 214     public String getDescription() {
 215         return fetchParam(StandardBundlerParam.NAME); //FIXME DESCRIPTION);
 216     }
 217 
 218     public void setDescription(String s) {
 219         putUnlessNull(PARAM_DESCRIPTION, s);
 220     }
 221 
 222     public String getLicenseType() {
 223         return fetchParam(StandardBundlerParam.LICENSE_TYPE); //FIXME
 224     }
 225 
 226     public void setLicenseType(String version) {
 227         putUnlessNull(PARAM_LICENSE_TYPE, version);
 228     }
 229 
 230     //path is relative to the application root
 231     public void addLicenseFile(String path) {
 232         @SuppressWarnings("unchecked") 
 233         List<String> licenseFile = fetchParam(List.class, PARAM_LICENSE_FILES);
 234         if (licenseFile == null) {
 235             licenseFile = new ArrayList<>();
 236             params.put(PARAM_LICENSE_FILES, licenseFile);
 237         }
 238         licenseFile.add(path);
 239     }
 240 
 241     public Boolean getSystemWide() {
 242         return fetchParam(StandardBundlerParam.SYSTEM_WIDE);
 243     }
 244 
 245     public void setSystemWide(Boolean b) {
 246         putUnlessNull(PARAM_SYSTEM_WIDE, b);
 247     }
 248 
 249     public RelativeFileSet getRuntime() {
 250         return fetchParam(StandardBundlerParam.RUNTIME);
 251     }
 252 
 253     public boolean isShortcutHint() {
 254         return fetchParam(StandardBundlerParam.SHORTCUT_HINT);
 255     }
 256 
 257     public void setShortcutHint(boolean v) {
 258         putUnlessNull(PARAM_SHORTCUT, v);
 259     }
 260 
 261     public boolean isMenuHint() {
 262         return fetchParam(StandardBundlerParam.MENU_HINT);
 263     }
 264 
 265     public void setMenuHint(boolean v) {
 266         putUnlessNull(PARAM_MENU, v);
 267     }
 268 
 269     public String getName() {
 270         return fetchParam(StandardBundlerParam.NAME);
 271     }
 272 
 273     public void setName(String name) {
 274         putUnlessNull(PARAM_NAME, name);
 275     }
 276 
 277     public BundleType getType() {
 278         return fetchParam(BundleType.class, PARAM_TYPE); 
 279     }
 280 
 281     public void setType(BundleType type) {
 282         putUnlessNull(PARAM_TYPE, type);
 283     }
 284 
 285     public String getBundleFormat() {
 286         return fetchParam(String.class, PARAM_BUNDLE_FORMAT); 
 287     }
 288     
 289     public void setBundleFormat(String t) {
 290         putUnlessNull(PARAM_BUNDLE_FORMAT, t);
 291     }
 292 
 293     public static boolean shouldExclude(File baseDir, File f, Rule ruleset[]) {
 294         if (ruleset == null) {
 295             return false;
 296         }
 297 
 298         String fname = f.getAbsolutePath().toLowerCase().substring(
 299                 baseDir.getAbsolutePath().length());
 300         //first rule match defines the answer
 301         for (Rule r: ruleset) {
 302             if (r.match(fname)) {
 303                 return !r.treatAsAccept();
 304             }
 305         }
 306         //default is include
 307         return false;
 308     }
 309 
 310     public static void walk(File base, File root, Rule ruleset[], Set<File> files) {
 311         if (!root.isDirectory()) {
 312             if (root.isFile()) {
 313                 files.add(root);
 314             }
 315             return;
 316         }
 317 
 318         File[] lst = root.listFiles();
 319         if (lst != null) {
 320             for (File f : lst) {
 321                 //ignore symbolic links!
 322                 if (IOUtils.isNotSymbolicLink(f) && !shouldExclude(base, f, ruleset)) {
 323                     if (f.isDirectory()) {
 324                         walk(base, f, ruleset, files);
 325                     } else if (f.isFile()) {
 326                         //add to list
 327                         files.add(f);
 328                     }
 329                 }
 330             }
 331         }
 332     }
 333 
 334     
 335     @SuppressWarnings("unchecked")
 336     public List<String> getLicenseFile() {
 337         return fetchParam(List.class, PARAM_LICENSE_FILES);
 338     }
 339 
 340     public List<String> getJvmargs() {
 341         return jvmargs;
 342     }
 343 
 344     public static class Rule {
 345         String regex;
 346         boolean includeRule;
 347         Type type;
 348         enum Type {SUFFIX, PREFIX, SUBSTR, REGEX}
 349 
 350         private Rule(String regex, boolean includeRule, Type type) {
 351             this.regex = regex;
 352             this.type = type;
 353             this.includeRule = includeRule;
 354         }
 355 
 356         boolean match(String str) {
 357             if (type == Type.SUFFIX) {
 358                 return str.endsWith(regex);
 359             }
 360             if (type == Type.PREFIX) {
 361                 return str.startsWith(regex);
 362             }
 363             if (type == Type.SUBSTR) {
 364                 return str.contains(regex);
 365             }
 366             return str.matches(regex);
 367         }
 368 
 369         boolean treatAsAccept() {return includeRule;}
 370 
 371         static Rule suffix(String s) {
 372             return new Rule(s, true, Type.SUFFIX);
 373         }
 374         static Rule suffixNeg(String s) {
 375             return new Rule(s, false, Type.SUFFIX);
 376         }
 377         static Rule prefix(String s) {
 378             return new Rule(s, true, Type.PREFIX);
 379         }
 380         static Rule prefixNeg(String s) {
 381             return new Rule(s, false, Type.PREFIX);
 382         }
 383         static Rule substr(String s) {
 384             return new Rule(s, true, Type.SUBSTR);
 385         }
 386         static Rule substrNeg(String s) {
 387             return new Rule(s, false, Type.SUBSTR);
 388         }
 389     }
 390 
 391     //Subsetting of JRE is restricted.
 392     //JRE README defines what is allowed to strip:
 393     //   http://www.oracle.com/technetwork/java/javase/jre-7-readme-430162.html
 394     //
 395     //In addition to this we may need to keep deploy jars and deploy.dll
 396     // because JavaFX apps might need JNLP services
 397     //Also, embedded launcher uses deploy.jar to access registry
 398     // (although this is not needed)
 399     public static Rule macRules[] = {
 400         Rule.suffixNeg("macos/libjli.dylib"),
 401         Rule.suffixNeg("resources"),
 402         Rule.suffixNeg("home/bin"),
 403         Rule.suffixNeg("home/db"),
 404         Rule.suffixNeg("home/demo"),
 405         Rule.suffixNeg("home/include"),
 406         Rule.suffixNeg("home/lib"),
 407         Rule.suffixNeg("home/man"),
 408         Rule.suffixNeg("home/release"),
 409         Rule.suffixNeg("home/sample"),
 410         Rule.suffixNeg("home/src.zip"),
 411         //"home/rt" is not part of the official builds
 412         // but we may be creating this symlink to make older NB projects
 413         // happy. Make sure to not include it into final artifact
 414         Rule.suffixNeg("home/rt"),
 415         Rule.suffixNeg("jre/bin"),
 416         Rule.suffixNeg("jre/bin/rmiregistry"),
 417         Rule.suffixNeg("jre/bin/tnameserv"),
 418         Rule.suffixNeg("jre/bin/keytool"),
 419         Rule.suffixNeg("jre/bin/klist"),
 420         Rule.suffixNeg("jre/bin/ktab"),
 421         Rule.suffixNeg("jre/bin/policytool"),
 422         Rule.suffixNeg("jre/bin/orbd"),
 423         Rule.suffixNeg("jre/bin/servertool"),
 424         Rule.suffixNeg("jre/bin/javaws"),
 425         Rule.suffixNeg("jre/bin/java"),
 426 //        Rule.suffixNeg("jre/lib/ext"), //need some of jars there for https to work
 427         Rule.suffixNeg("jre/lib/nibs"),
 428 //keep core deploy APIs but strip plugin dll
 429 //        Rule.suffixNeg("jre/lib/deploy"),
 430 //        Rule.suffixNeg("jre/lib/deploy.jar"),
 431 //        Rule.suffixNeg("jre/lib/javaws.jar"),
 432 //        Rule.suffixNeg("jre/lib/libdeploy.dylib"),
 433 //        Rule.suffixNeg("jre/lib/plugin.jar"),
 434         Rule.suffixNeg("jre/lib/libnpjp2.dylib"),
 435         Rule.suffixNeg("jre/lib/security/javaws.policy")
 436     };
 437 
 438     public static Rule[] winRules = {
 439         Rule.prefixNeg("\\bin\\new_plugin"),
 440         Rule.suffix("deploy.jar"), //take deploy.jar
 441         Rule.prefixNeg("\\lib\\deploy"),
 442         Rule.suffixNeg(".pdb"),
 443         Rule.suffixNeg(".map"),
 444         Rule.suffixNeg("axbridge.dll"),
 445         Rule.suffixNeg("eula.dll"),
 446         Rule.substrNeg("javacpl"),
 447         Rule.suffixNeg("wsdetect.dll"),
 448         Rule.substrNeg("eployjava1.dll"), //NP and IE versions
 449         Rule.substrNeg("bin\\jp2"),
 450         Rule.substrNeg("bin\\jpi"),
 451 //        Rule.suffixNeg("lib\\ext"), //need some of jars there for https to work
 452         Rule.suffixNeg("ssv.dll"),
 453         Rule.substrNeg("npjpi"),
 454         Rule.substrNeg("npoji"),
 455         Rule.suffixNeg(".exe"),
 456 //keep core deploy files as JavaFX APIs use them
 457 //        Rule.suffixNeg("deploy.dll"),
 458 //        Rule.suffixNeg("deploy.jar"),
 459 //        Rule.suffixNeg("javaws.jar"),
 460 //        Rule.suffixNeg("plugin.jar"),
 461         Rule.suffix(".jar")
 462     };
 463 
 464     public static Rule[] linuxRules = {
 465         Rule.prefixNeg("/bin"),
 466         Rule.prefixNeg("/plugin"),
 467 //        Rule.prefixNeg("/lib/ext"), //need some of jars there for https to work
 468         Rule.suffix("deploy.jar"), //take deploy.jar
 469         Rule.prefixNeg("/lib/deploy"),
 470         Rule.prefixNeg("/lib/desktop"),
 471         Rule.substrNeg("libnpjp2.so")
 472     };
 473 
 474     //Validation approach:
 475     //  - JRE marker (rt.jar)
 476     //  - FX marker (jfxrt.jar)
 477     //  - JDK marker (tools.jar)
 478     private static boolean checkJDKRoot(File jdkRoot) {
 479         File rtJar = new File(jdkRoot, "jre/lib/rt.jar");
 480         if (!rtJar.exists()) {
 481             Log.verbose("rt.jar is not found at " + rtJar.getAbsolutePath());
 482             return false;
 483         }
 484 
 485         File jfxJar = new File(jdkRoot, "jre/lib/ext/jfxrt.jar");
 486         if (!jfxJar.exists()) {
 487             //Try again with new location
 488             jfxJar = new File(jdkRoot, "jre/lib/jfxrt.jar");
 489             if (!jfxJar.exists()) {
 490                 Log.verbose("jfxrt.jar is not found at " + jfxJar.getAbsolutePath());
 491                 return false;
 492             }
 493         }
 494 
 495 
 496         File toolsJar = new File(jdkRoot, "lib/tools.jar");
 497         if (!toolsJar.exists()) {
 498             Log.verbose("tools.jar is not found at " + toolsJar.getAbsolutePath());
 499             return false;
 500         }
 501 
 502         return true;
 503     }
 504 
 505     //Depending on platform and user input we may get different "references"
 506     //Should support
 507     //   - java.home
 508     //   - reference to JDK install folder
 509     //   - should NOT support JRE dir
 510     //Note: input could be null (then we asked to use system JRE)
 511     //       or it must be valid directory
 512     //Returns null on validation failure. Returns jre root if ok.
 513     static File validateRuntimeLocation(File javaHome) {
 514         if (javaHome == null) {
 515             return null;
 516         }
 517         File jdkRoot;
 518 
 519         boolean isMac = System.getProperty("os.name").toLowerCase().contains("os x");
 520 
 521         File rtJar = new File(javaHome, "lib/rt.jar");
 522         if (rtJar.exists()) { //must be "java.home" case
 523                               //i.e. we are in JRE folder
 524             jdkRoot = javaHome.getParentFile();
 525         } else { //expect it to be root of JDK installation folder
 526             //On Mac it could be jdk/ or jdk/Contents/Home
 527             //Norm to jdk/Contents/Home for validation
 528             if (isMac) {
 529                 File f = new File(javaHome, "Contents/Home");
 530                 if (f.exists() && f.isDirectory()) {
 531                     javaHome = f;
 532                 }
 533             }
 534             jdkRoot = javaHome;
 535         }
 536 
 537         if (!checkJDKRoot(jdkRoot)) {
 538             throw new RuntimeException(
 539                     "Can not find JDK artifacts in specified location: "
 540                     + javaHome.getAbsolutePath());
 541         }
 542 
 543         return new File(jdkRoot, "jre");
 544     }
 545 
 546     //select subset of given runtime using predefined rules
 547     public void setRuntime(File baseDir) {
 548         baseDir = validateRuntimeLocation(baseDir);
 549 
 550         //mistake or explicit intent to use system runtime
 551         if (baseDir == null) {
 552             Log.verbose("No Java runtime to embed. Package will need system Java.");
 553             params.put(PARAM_RUNTIME, null);
 554             return;
 555         }
 556         doSetRuntime(baseDir);
 557     }
 558 
 559     public void setDefaultRuntime() {
 560         File f = new File(System.getProperty("java.home"));
 561         doSetRuntime(f);
 562     }
 563 
 564     //input dir "jdk/jre" (i.e. jre folder in the jdk)
 565     private void doSetRuntime(File baseDir) {
 566         boolean isMac = System.getProperty("os.name").toLowerCase().contains("os x");
 567 
 568         //Normalization: on MacOS we need to point to the top of JDK dir
 569         // (other platforms are fine)
 570         if (isMac) {
 571             //On Mac we need Bundle root, not jdk/Contents/Home
 572             baseDir = baseDir.getParentFile().getParentFile().getParentFile();
 573         }
 574 
 575         Set<File> lst = new HashSet<>();
 576 
 577         Rule ruleset[];
 578         if (System.getProperty("os.name").startsWith("Mac")) {
 579             ruleset = macRules;
 580         } else if (System.getProperty("os.name").startsWith("Win")) {
 581             ruleset = winRules;
 582         } else {
 583             //must be linux
 584             ruleset = linuxRules;
 585         }
 586 
 587         walk(baseDir, baseDir, ruleset, lst);
 588 
 589         params.put(PARAM_RUNTIME, new RelativeFileSet(baseDir, lst));
 590     }
 591 
 592     //Currently unused?
 593     //
 594     //public void setRuntime(RelativeFileSet fs) {
 595     //       runtime = fs;
 596     //}
 597 
 598     public RelativeFileSet getAppResource() {
 599         return fetchParam(StandardBundlerParam.APP_RESOURCES);
 600     }
 601 
 602     public void setAppResource(RelativeFileSet fs) {
 603         putUnlessNull(PARAM_APP_RESOURCES, fs);
 604     }
 605 
 606     public File getIcon() {
 607         return fetchParam(StandardBundlerParam.ICON);
 608     }
 609 
 610     public void setIcon(File icon) {
 611         putUnlessNull(PARAM_ICON, icon);
 612     }
 613 
 614     public String getApplicationCategory() {
 615         return fetchParam(String.class, PARAM_CATEGORY); //FIXME
 616     }
 617 
 618     public void setApplicationCategory(String category) {
 619         putUnlessNull(PARAM_CATEGORY, category);
 620     }
 621 
 622     public String getMainClassName() {
 623         String applicationClass = getApplicationClass();
 624         
 625         if (applicationClass == null) {
 626             return null;
 627         }
 628 
 629         int idx = applicationClass.lastIndexOf(".");
 630         if (idx >= 0) {
 631             return applicationClass.substring(idx+1);
 632         }
 633         return applicationClass;
 634     }
 635 
 636     public String getCopyright() {
 637         return fetchParam(StandardBundlerParam.COPYRIGHT);
 638     }
 639 
 640     public void setCopyright(String c) {
 641         putUnlessNull(PARAM_COPYRIGHT, c);
 642     }
 643 
 644     public String getIdentifier() {
 645         return fetchParam(StandardBundlerParam.IDENTIFIER);
 646     }
 647 
 648     public void setIdentifier(String s) {
 649         putUnlessNull(PARAM_IDENTIFIER, s);
 650     }
 651 
 652     private String mainJar = null;
 653     private String mainJarClassPath = null;
 654     private boolean useFXPackaging = true;
 655 
 656     //are we packaging JavaFX application or regular executable Jar?
 657     public boolean useJavaFXPackaging() {
 658         if (mainJar == null) {
 659             //this will find out answer
 660             getMainApplicationJar();
 661         }
 662         return useFXPackaging;
 663     }
 664 
 665     //For regular executable Jars we need to take care of classpath
 666     //For JavaFX executable jars we do not need to pay attention to ClassPath entry in manifest
 667     public String getAppClassPath() {
 668         if (mainJar == null) {
 669             //this will find out answer
 670             getMainApplicationJar();
 671         }
 672         if (useFXPackaging || mainJarClassPath == null) {
 673             return "";
 674         }
 675         return mainJarClassPath;
 676     }
 677 
 678     //assuming that application was packaged according to the rules
 679     // we must have application jar, i.e. jar where we embed launcher
 680     // and have main application class listed as main class!
 681     //If there are more than one, or none - it will be treated as deployment error
 682     //
 683     //Note we look for both JavaFX executable jars and regular executable jars
 684     //As long as main "application" entry point is the same it is main class
 685     // (i.e. for FX jar we will use JavaFX manifest entry ...)
 686     public String getMainApplicationJar() {
 687         if (mainJar != null) {
 688             return mainJar;
 689         }
 690         
 691         RelativeFileSet appResources = getAppResource();
 692         String applicationClass = getApplicationClass();
 693 
 694         if (appResources == null || applicationClass == null) {
 695             return null;
 696         }
 697         File srcdir = appResources.getBaseDirectory();
 698         for (String fname : appResources.getIncludedFiles()) {
 699             JarFile jf;
 700             try {
 701                 jf = new JarFile(new File(srcdir, fname));
 702                 Manifest m = jf.getManifest();
 703                 Attributes attrs = (m != null) ? m.getMainAttributes() : null;
 704                 if (attrs != null) {
 705                     boolean javaMain = applicationClass.equals(
 706                                attrs.getValue(Attributes.Name.MAIN_CLASS));
 707                     boolean fxMain = applicationClass.equals(
 708                                attrs.getValue(PackagerLib.MANIFEST_JAVAFX_MAIN));
 709                     if (javaMain || fxMain) {
 710                         useFXPackaging = fxMain;
 711                         mainJar = fname;
 712                         mainJarClassPath = attrs.getValue(Attributes.Name.CLASS_PATH);
 713                         return mainJar;
 714                     }
 715                 }
 716             } catch (IOException ignore) {
 717             }
 718         }
 719         return null;
 720     }
 721 
 722     public String getVendor() {
 723         return fetchParam(StandardBundlerParam.VENDOR);
 724     }
 725 
 726     public void setVendor(String vendor) {
 727        putUnlessNull(PARAM_VENDOR, vendor);
 728     }
 729 
 730     public String getEmail() {
 731         return fetchParam(String.class, PARAM_EMAIL);
 732     }
 733 
 734     public void setEmail(String email) {
 735         putUnlessNull(PARAM_EMAIL, email);
 736     }
 737     
 738     public void putUnlessNull(String param, Object value) {
 739         if (value != null) {
 740             params.put(param, value);
 741         }
 742     }
 743 
 744 }