changeset 6495:7339a5bb9864

RT-35635: new bundlers for fxpackager RT-35969: Daemon and Services apps for fxpackager
author Danno Ferrin (shemnon) <danno.ferrin@oracle.com>
date Mon, 17 Mar 2014 09:51:09 -0600
parents c7f043d40430
children 03b02a7bcfb9
files modules/fxpackager/src/main/java/com/oracle/bundlers/AbstractBundler.java modules/fxpackager/src/main/java/com/oracle/bundlers/BasicBundlers.java modules/fxpackager/src/main/java/com/oracle/bundlers/BundlerParamInfo.java modules/fxpackager/src/main/java/com/oracle/bundlers/Bundlers.java modules/fxpackager/src/main/java/com/oracle/bundlers/EnumeratedBundlerParam.java modules/fxpackager/src/main/java/com/oracle/bundlers/InvalidBundlerParamException.java modules/fxpackager/src/main/java/com/oracle/bundlers/JreUtils.java modules/fxpackager/src/main/java/com/oracle/bundlers/StandardBundlerParam.java modules/fxpackager/src/main/java/com/oracle/bundlers/mac/MacAppStoreBundler.java modules/fxpackager/src/main/java/com/oracle/bundlers/mac/MacBaseInstallerBundler.java modules/fxpackager/src/main/java/com/oracle/bundlers/mac/MacDaemonBundler.java modules/fxpackager/src/main/java/com/oracle/bundlers/mac/MacPKGBundler.java modules/fxpackager/src/main/java/com/oracle/bundlers/windows/WindowsBundlerParam.java modules/fxpackager/src/main/java/com/sun/javafx/tools/packager/DeployParams.java modules/fxpackager/src/main/java/com/sun/javafx/tools/packager/Log.java modules/fxpackager/src/main/java/com/sun/javafx/tools/packager/Main.java modules/fxpackager/src/main/java/com/sun/javafx/tools/packager/PackagerLib.java modules/fxpackager/src/main/java/com/sun/javafx/tools/packager/bundlers/BundleParams.java modules/fxpackager/src/main/java/com/sun/javafx/tools/packager/bundlers/ConfigException.java modules/fxpackager/src/main/java/com/sun/javafx/tools/packager/bundlers/LinuxAppBundler.java modules/fxpackager/src/main/java/com/sun/javafx/tools/packager/bundlers/LinuxDebBundler.java modules/fxpackager/src/main/java/com/sun/javafx/tools/packager/bundlers/LinuxRPMBundler.java modules/fxpackager/src/main/java/com/sun/javafx/tools/packager/bundlers/MacAppBundler.java modules/fxpackager/src/main/java/com/sun/javafx/tools/packager/bundlers/MacDMGBundler.java modules/fxpackager/src/main/java/com/sun/javafx/tools/packager/bundlers/WinAppBundler.java modules/fxpackager/src/main/java/com/sun/javafx/tools/packager/bundlers/WinExeBundler.java modules/fxpackager/src/main/java/com/sun/javafx/tools/packager/bundlers/WinMsiBundler.java modules/fxpackager/src/main/native/launcher/win/WinLauncherSvc.cpp modules/fxpackager/src/main/resources/com/oracle/bundlers/StandardBundlerParam.properties modules/fxpackager/src/main/resources/com/oracle/bundlers/linux/LinuxDebBundler.properties modules/fxpackager/src/main/resources/com/oracle/bundlers/linux/LinuxRpmBundler.properties modules/fxpackager/src/main/resources/com/oracle/bundlers/mac/MacAppBundler.properties modules/fxpackager/src/main/resources/com/oracle/bundlers/mac/MacAppStoreBundler.properties modules/fxpackager/src/main/resources/com/oracle/bundlers/mac/MacBaseInstallerBundler.properties modules/fxpackager/src/main/resources/com/oracle/bundlers/mac/MacDaemonBundler.properties modules/fxpackager/src/main/resources/com/oracle/bundlers/mac/MacPKGBundler.properties modules/fxpackager/src/main/resources/com/oracle/bundlers/windows/WinExeBundler.properties modules/fxpackager/src/main/resources/com/oracle/bundlers/windows/WinMsiBundler.properties modules/fxpackager/src/main/resources/com/sun/javafx/tools/packager/Bundle.properties modules/fxpackager/src/main/resources/com/sun/javafx/tools/resource/linux/template.deb.init.script modules/fxpackager/src/main/resources/com/sun/javafx/tools/resource/linux/template.postinst modules/fxpackager/src/main/resources/com/sun/javafx/tools/resource/linux/template.postrm modules/fxpackager/src/main/resources/com/sun/javafx/tools/resource/linux/template.rpm.init.script modules/fxpackager/src/main/resources/com/sun/javafx/tools/resource/linux/template.spec modules/fxpackager/src/main/resources/com/sun/javafx/tools/resource/mac/GenericAppHiDPI.icns modules/fxpackager/src/main/resources/com/sun/javafx/tools/resource/mac/MacAppStore.entitlements modules/fxpackager/src/main/resources/com/sun/javafx/tools/resource/mac/MacAppStore_Inherit.entitlements modules/fxpackager/src/main/resources/com/sun/javafx/tools/resource/mac/launchd.plist.template modules/fxpackager/src/main/resources/com/sun/javafx/tools/resource/mac/postinstall.template modules/fxpackager/src/main/resources/com/sun/javafx/tools/resource/mac/preinstall.template modules/fxpackager/src/main/resources/com/sun/javafx/tools/resource/windows/template.iss modules/fxpackager/src/test/java/com/oracle/bundlers/BundlersTest.java modules/fxpackager/src/test/java/com/oracle/bundlers/linux/LinuxAppBundlerTest.java modules/fxpackager/src/test/java/com/oracle/bundlers/linux/LinuxRpmBundlerTest.java modules/fxpackager/src/test/java/com/oracle/bundlers/mac/MacAppBundlerTest.java modules/fxpackager/src/test/java/com/oracle/bundlers/mac/MacAppStoreBundlerTest.java modules/fxpackager/src/test/java/com/oracle/bundlers/mac/MacDMGBundlerTest.java modules/fxpackager/src/test/java/com/oracle/bundlers/mac/MacPKGBundlerTest.java modules/fxpackager/src/test/java/com/oracle/bundlers/windows/WinAppBundlerTest.java modules/fxpackager/src/test/java/com/oracle/bundlers/windows/WinEXEBundlerTest.java modules/fxpackager/src/test/java/com/oracle/bundlers/windows/WinMSIBundlerTest.java modules/fxpackager/src/test/java/com/sun/javafx/tools/packager/CLITest.java
diffstat 62 files changed, 4476 insertions(+), 1388 deletions(-) [+]
line wrap: on
line diff
--- a/modules/fxpackager/src/main/java/com/oracle/bundlers/AbstractBundler.java	Mon Mar 17 16:56:40 2014 +0400
+++ b/modules/fxpackager/src/main/java/com/oracle/bundlers/AbstractBundler.java	Mon Mar 17 09:51:09 2014 -0600
@@ -26,7 +26,6 @@
 package com.oracle.bundlers;
 
 import com.oracle.bundlers.windows.WindowsBundlerParam;
-import com.sun.javafx.tools.ant.DeployFXTask;
 import com.sun.javafx.tools.packager.Log;
 import com.sun.javafx.tools.packager.bundlers.ConfigException;
 import com.sun.javafx.tools.packager.bundlers.IOUtils;
@@ -40,44 +39,32 @@
 import java.text.MessageFormat;
 import java.util.*;
 
+import static com.oracle.bundlers.StandardBundlerParam.*;
+
 public abstract class AbstractBundler implements Bundler {
 
     private static final ResourceBundle I18N =
             ResourceBundle.getBundle("com.oracle.bundlers.AbstractBundler");
 
-    protected boolean verbose = false;
-
     public static final BundlerParamInfo<File> IMAGES_ROOT = new WindowsBundlerParam<>(
             I18N.getString("param.images-root.name"),
             I18N.getString("param.images-root.description"),
-            "imagesRoot", //KEY
+            "imagesRoot",
             File.class, null,
-            params -> {
-                File imagesRoot = new File(StandardBundlerParam.BUILD_ROOT.fetchFrom(params), "images");
-                imagesRoot.mkdirs();
-                return imagesRoot;
-            },
-            false, s -> null);
+            params -> new File(BUILD_ROOT.fetchFrom(params), "images"),
+            false, (s, p) -> null);
 
     //do not use file separator -
-    // we use it for classpath lookup and there / are not platfrom specific
+    // we use it for classpath lookup and there / are not platform specific
     public final static String BUNDLER_PREFIX = "package/";
 
     protected static final String JAVAFX_LAUNCHER_CLASS = "com.javafx.main.Main";
 
     protected Class baseResourceLoader = null;
-
-    public boolean isVerbose() {
-        return verbose;
-    }
-
-    public void setVerbose(boolean verbose) {
-        this.verbose = verbose;
-    }
-
+    
     //helper method to test if required files are present in the runtime
     public void testRuntime(Map<String, ? super Object> p, String[] file) throws ConfigException {
-        RelativeFileSet runtime = StandardBundlerParam.RUNTIME.fetchFrom(p);
+        RelativeFileSet runtime = RUNTIME.fetchFrom(p);
         if (runtime == null) {
             return; //null runtime is ok (request to use system)
         }
@@ -94,9 +81,9 @@
 
     protected void fetchResource(
             String publicName, String category,
-            String defaultName, File result)
+            String defaultName, File result, boolean verbose)
             throws IOException {
-        URL u = locateResource(publicName, category, defaultName);
+        URL u = locateResource(publicName, category, defaultName, verbose);
         if (u != null) {
             IOUtils.copyFromURL(u, result);
         } else {
@@ -108,9 +95,9 @@
 
     protected void fetchResource(
             String publicName, String category,
-            File defaultFile, File result)
+            File defaultFile, File result, boolean verbose)
             throws IOException {
-        URL u = locateResource(publicName, category, null);
+        URL u = locateResource(publicName, category, null, verbose);
         if (u != null) {
             IOUtils.copyFromURL(u, result);
         } else {
@@ -122,7 +109,7 @@
     }
 
     private URL locateResource(String publicName, String category,
-                               String defaultName) throws IOException {
+                               String defaultName, boolean verbose) throws IOException {
         URL u = null;
         boolean custom = false;
         if (publicName != null) {
@@ -145,8 +132,9 @@
     }
 
     protected String preprocessTextResource(String publicName, String category,
-                                            String defaultName, Map<String, String> pairs) throws IOException {
-        URL u = locateResource(publicName, category, defaultName);
+                                            String defaultName, Map<String, String> pairs,
+                                            boolean verbose) throws IOException {
+        URL u = locateResource(publicName, category, defaultName, verbose);
         InputStream inp = u.openStream();
         if (inp == null) {
             throw new RuntimeException("Jar corrupt? No "+defaultName+" resource!");
@@ -177,10 +165,4 @@
             Log.info("    id: " + e.getKey() + " value: " + e.getValue());
         }
     }
-
-    public void validateDynamicArguments(List<DeployFXTask.BundleArgument> dynamicArgs) {
-        for(BundlerParamInfo info: getBundleParameters()) {
-            //TODO
-        }
-    }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/fxpackager/src/main/java/com/oracle/bundlers/BasicBundlers.java	Mon Mar 17 09:51:09 2014 -0600
@@ -0,0 +1,145 @@
+/*
+ * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.oracle.bundlers;
+
+import com.oracle.bundlers.mac.MacAppStoreBundler;
+import com.oracle.bundlers.mac.MacPKGBundler;
+import com.sun.javafx.tools.packager.bundlers.BundleType;
+import com.sun.javafx.tools.packager.bundlers.LinuxAppBundler;
+import com.sun.javafx.tools.packager.bundlers.LinuxDebBundler;
+import com.sun.javafx.tools.packager.bundlers.LinuxRPMBundler;
+import com.sun.javafx.tools.packager.bundlers.MacAppBundler;
+import com.sun.javafx.tools.packager.bundlers.MacDMGBundler;
+import com.sun.javafx.tools.packager.bundlers.WinAppBundler;
+import com.sun.javafx.tools.packager.bundlers.WinExeBundler;
+import com.sun.javafx.tools.packager.bundlers.WinMsiBundler;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.ServiceLoader;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * A basic bundlers collection that loads the OpenJFX default bundlers.
+ * Loads the bundlers common to OpenJFX.
+ * <UL>
+ *     <LI>Windows file image</LI>
+ *     <LI>Mac .app</LI>
+ *     <LI>Linux file image</LI>
+ *     <LI>Windows MSI</LI>
+ *     <LI>Windows EXE</LI>
+ *     <LI>Mac DMG</LI>
+ *     <LI>Mac PKG</LI>
+ *     <LI>Linux DEB</LI>
+ *     <LI>Linux RPM</LI>
+ *
+ * </UL>
+ */
+public class BasicBundlers implements Bundlers {
+
+    boolean defaultsLoaded = false;
+
+    private Collection<Bundler> bundlers = new CopyOnWriteArrayList<>();
+
+    public Collection<Bundler> getBundlers() {
+        return Collections.unmodifiableCollection(bundlers);
+    }
+
+    public Collection<Bundler> getBundlers(BundleType type) {
+        if (type == null) return Collections.emptySet();
+        switch (type) {
+            case NONE:
+                return Collections.emptySet();
+            case ALL:
+                return getBundlers();
+            default:
+                return Arrays.asList(getBundlers().stream()
+                        .filter(b -> type.equals(b.getBundleType()))
+                        .toArray(Bundler[]::new));
+        }
+    }
+
+    /**
+     * A list of the "standard" parameters that bundlers should support
+     * or fall back to when their specific parameters are not used.
+     * @return an unmodifieable collection of the standard parameters.
+     */
+    public Collection<BundlerParamInfo> getStandardParameters() {
+        //TODO enumerate the stuff in BundleParams
+        return null;
+    }
+
+    /**
+     * Loads the bundlers common to OpenJFX.
+     * <UL>
+     *     <LI>Windows file image</LI>
+     *     <LI>Mac .app</LI>
+     *     <LI>Linux file image</LI>
+     *     <LI>Windows MSI</LI>
+     *     <LI>Windows EXE</LI>
+     *     <LI>Mac DMG</LI>
+     *     <LI>Mac PKG</LI>
+     *     <LI>Linux DEB</LI>
+     *     <LI>Linux RPM</LI>
+     *
+     * </UL>
+     */
+    public void loadDefaultBundlers() {
+        if (defaultsLoaded) return;
+
+        bundlers.add(new WinAppBundler());
+        bundlers.add(new WinExeBundler());
+        bundlers.add(new WinMsiBundler());
+
+        bundlers.add(new LinuxAppBundler());
+        bundlers.add(new LinuxDebBundler());
+        bundlers.add(new LinuxRPMBundler());
+
+        bundlers.add(new MacAppBundler());
+        bundlers.add(new MacDMGBundler());
+        bundlers.add(new MacPKGBundler());
+        bundlers.add(new MacAppStoreBundler());
+
+        //bundlers.add(new JNLPBundler());
+
+        defaultsLoaded = true;
+    }
+
+    /**
+     * Loads bundlers from the META-INF/services direct
+     */
+    public void loadBundlersFromServices(ClassLoader cl) {
+        ServiceLoader<Bundler> loader = ServiceLoader.load(Bundler.class, cl);
+        for (Bundler aLoader : loader) {
+            bundlers.add(aLoader);
+        }
+    }
+
+    public void loadBundler(Bundler bundler) {
+        bundlers.add(bundler);
+    }
+}
--- a/modules/fxpackager/src/main/java/com/oracle/bundlers/BundlerParamInfo.java	Mon Mar 17 16:56:40 2014 +0400
+++ b/modules/fxpackager/src/main/java/com/oracle/bundlers/BundlerParamInfo.java	Mon Mar 17 09:51:09 2014 -0600
@@ -26,6 +26,7 @@
 package com.oracle.bundlers;
 
 import java.util.Map;
+import java.util.function.BiFunction;
 import java.util.function.Function;
 
 public class BundlerParamInfo<T> {
@@ -69,7 +70,7 @@
     /**
      * An optional string converter for command line arguments.
      */
-    Function<String, T> stringConverter;
+    BiFunction<String, Map<String, ? super Object>, T> stringConverter;
 
     public String getName() {
         return name;
@@ -127,11 +128,11 @@
         this.requiresUserSetting = requiresUserSetting;
     }
 
-    public Function<String, T> getStringConverter() {
+    public BiFunction<String, Map<String, ? super Object>,T> getStringConverter() {
         return stringConverter;
     }
 
-    public void setStringConverter(Function<String, T> stringConverter) {
+    public void setStringConverter(BiFunction<String, Map<String, ? super Object>, T> stringConverter) {
         this.stringConverter = stringConverter;
     }
 
@@ -139,7 +140,7 @@
     public final T fetchFrom(Map<String, ? super Object> params) {
         Object o = params.get(getID());
         if (o instanceof String && getStringConverter() != null) {
-            return getStringConverter().apply((String)o);
+            return getStringConverter().apply((String)o, params);
         }
 
         Class klass = getValueType();
@@ -159,6 +160,8 @@
                 o = params.get(fallback);
                 if (klass.isInstance(o)) {
                     return (T) o;
+                } else if (o instanceof String) {
+                    return getStringConverter().apply((String)o, params);
                 }
             }
         }
--- a/modules/fxpackager/src/main/java/com/oracle/bundlers/Bundlers.java	Mon Mar 17 16:56:40 2014 +0400
+++ b/modules/fxpackager/src/main/java/com/oracle/bundlers/Bundlers.java	Mon Mar 17 09:51:09 2014 -0600
@@ -25,70 +25,90 @@
 
 package com.oracle.bundlers;
 
-import com.oracle.bundlers.mac.MacAppStoreBundler;
-import com.oracle.bundlers.mac.MacPKGBundler;
-import com.sun.javafx.tools.packager.bundlers.*;
+import com.sun.javafx.tools.packager.bundlers.BundleType;
 
-import java.util.*;
-import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.ServiceLoader;
 
-public class Bundlers {
-    
+
+public interface Bundlers {
+
+    /**
+     * This convenience method will call {@link #createBundlersInstance(ClassLoader)}
+     * with the classloader that this Bundlers is loaded from.
+     *
+     * @return an instance of Bundlers loaded and configured from the current ClassLoader.
+     */
     public static Bundlers createBundlersInstance() {
         return createBundlersInstance(Bundlers.class.getClassLoader());
     }
-    
+
+    /**
+     * This convenience method will automatically load a Bundlers instance
+     * from either META-INF/services or the default
+     * {@link com.oracle.bundlers.BasicBundlers} if none are found in
+     * the services meta-inf.
+     *
+     * After instantiating the bundlers instance it will load the default
+     * bundlers via {@link #loadDefaultBundlers()} as well as requesting
+     * the services loader to load any other bundelrs via
+     * {@link #loadBundlersFromServices(ClassLoader)}.
+
+     *
+     * @param servicesClassLoader the classloader to search for
+     *                            META-INF/service registered bundlers
+     * @return an instance of Bundlers loaded and configured from the specified ClassLoader
+     */
     public static Bundlers createBundlersInstance(ClassLoader servicesClassLoader) {
-        Bundlers bundlers = new Bundlers();
+        ServiceLoader<Bundlers> bundlersLoader = ServiceLoader.load(Bundlers.class, servicesClassLoader);
+        Bundlers bundlers = null;
+        Iterator<Bundlers> iter = bundlersLoader.iterator();
+        if (iter.hasNext()) {
+            bundlers = iter.next();
+        }
+        if (bundlers == null) {
+            bundlers = new BasicBundlers();
+        }
+
         bundlers.loadDefaultBundlers();
         bundlers.loadBundlersFromServices(servicesClassLoader);
         return bundlers;
     }
 
-    private Collection<Bundler> bundlers = new CopyOnWriteArrayList<>();
+    /**
+     * Returns all of the preconfigured, requested, and manually
+     * configured bundlers loaded with this instance.
+     *
+     * @return  a read-only collection of the requested bundlers
+     */
+    Collection<Bundler> getBundlers();
 
-    public Collection<Bundler> getBundlers() {
-        return Collections.unmodifiableCollection(bundlers);
-    }
-
-    public Collection<Bundler> getBundlers(BundleType type) {
-        if (type == null) return Collections.emptySet();
-        switch (type) {
-            case NONE:
-                return Collections.emptySet();
-            case ALL:
-                return getBundlers();
-            default:
-                Collection<Bundler> results = new LinkedHashSet<>();
-                for (Bundler bundler : getBundlers()) {
-                    if (type.equals(bundler.getBundleType())) {
-                        results.add(bundler);
-                    }
-                }
-                return results;
-                //return Arrays.asList(
-                //    getBundlers().stream()
-                //        .filter(b -> type.equals(b.getBundleType()))
-                //        .toArray(Bundler[]::new));
-        }
-    }
+    /**
+     * Returns all of the preconfigured, requested, and manually
+     * configured bundlers loaded with this instance that are of
+     * a specific BundleType, such as disk images, installers, or
+     * remote installers.
+     *
+     * @return a read-only collection of the requested bundlers
+     */
+    Collection<Bundler> getBundlers(BundleType type);
 
     /**
      * A list of the "standard" parameters that bundlers should support
      * or fall back to when their specific parameters are not used.
-     * @return an unmodifieable collection of the standard parameters.
+     *
+     * @return an unmodifiable collection of the standard parameters.
      */
-    public static Collection<BundlerParamInfo> getStandardParameters() {
-        //TODO enumerate the stuff in BundleParams
-        return null;
-    }
+    Collection<BundlerParamInfo> getStandardParameters();
 
     /**
-     * Loads the bundlers common to the JDK.
+     * Loads the bundlers common to the JDK.  A typical implementation
+     * would load:
      * <UL>
-     *     <LI>Windows file tree</LI>
+     *     <LI>Windows file image</LI>
      *     <LI>Mac .app</LI>
-     *     <LI>Linux file tree</LI>
+     *     <LI>Linux file image</LI>
 
      *     <LI>Windows MSI</LI>
      *     <LI>Windows EXE</LI>
@@ -97,35 +117,25 @@
      *     <LI>Linux RPM</LI>
      *
      * </UL>
+     *
+     * This method is called from the {@link #createBundlersInstance(ClassLoader)}
+     * and {@link #createBundlersInstance()} methods.
      */
-    public void loadDefaultBundlers() {
-        bundlers.add(new WinAppBundler());
-        bundlers.add(new WinExeBundler());
-        bundlers.add(new WinMsiBundler());
-
-        bundlers.add(new LinuxAppBundler());
-        bundlers.add(new LinuxDebBundler());
-        bundlers.add(new LinuxRPMBundler());
-
-        bundlers.add(new MacAppBundler());
-        bundlers.add(new MacDMGBundler());
-        bundlers.add(new MacPKGBundler());
-        bundlers.add(new MacAppStoreBundler());
-
-        //bundlers.add(new JNLPBundler());
-    }
+    void loadDefaultBundlers();
 
     /**
-     * Loads bundlers from the META-INF/services direct
+     * Loads bundlers from the META-INF/services directly.
+     *
+     * This method is called from the {@link #createBundlersInstance(ClassLoader)}
+     * and {@link #createBundlersInstance()} methods.
      */
-    public void loadBundlersFromServices(ClassLoader cl) {
-        ServiceLoader<Bundler> loader = ServiceLoader.load(Bundler.class, cl);
-        for (Bundler aLoader : loader) {
-            bundlers.add(aLoader);
-        }
-    }
+    void loadBundlersFromServices(ClassLoader cl);
 
-    public void loadBundler(Bundler bundler) {
-        bundlers.add(bundler);
-    }
+    /**
+     * Loads a specific bundler into the set of bundlers.
+     * Useful for a manually configured bundler.
+     *
+     * @param bundler the specific bundler to add
+     */
+    void loadBundler(Bundler bundler);
 }
--- a/modules/fxpackager/src/main/java/com/oracle/bundlers/EnumeratedBundlerParam.java	Mon Mar 17 16:56:40 2014 +0400
+++ b/modules/fxpackager/src/main/java/com/oracle/bundlers/EnumeratedBundlerParam.java	Mon Mar 17 09:51:09 2014 -0600
@@ -26,6 +26,7 @@
 package com.oracle.bundlers;
 
 import java.util.*;
+import java.util.function.BiFunction;
 import java.util.function.Function;
 
 /**
@@ -49,7 +50,7 @@
                                   String[] fallbackIDs,
                                   Function<Map<String, ? super Object>, T> defaultValueFunction,
                                   boolean requiresUserSetting,
-                                  Function<String, T> stringConverter,
+                                  BiFunction<String, Map<String, ? super Object>, T> stringConverter,
                                   Map<String, T> possibleValues,
                                   boolean strict) {
         this.name = name;
--- a/modules/fxpackager/src/main/java/com/oracle/bundlers/InvalidBundlerParamException.java	Mon Mar 17 16:56:40 2014 +0400
+++ b/modules/fxpackager/src/main/java/com/oracle/bundlers/InvalidBundlerParamException.java	Mon Mar 17 09:51:09 2014 -0600
@@ -25,7 +25,7 @@
 
 package com.oracle.bundlers;
 
-public class InvalidBundlerParamException extends Exception {
+public class InvalidBundlerParamException extends RuntimeException {
     public InvalidBundlerParamException(String message) {
         super(message);
     }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/fxpackager/src/main/java/com/oracle/bundlers/JreUtils.java	Mon Mar 17 09:51:09 2014 -0600
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2012, 2014, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.oracle.bundlers;
+
+import com.sun.javafx.tools.packager.bundlers.IOUtils;
+import com.sun.javafx.tools.packager.bundlers.RelativeFileSet;
+
+import java.io.File;
+import java.util.HashSet;
+import java.util.Set;
+
+public class JreUtils {
+
+    public static class Rule {
+        String regex;
+        boolean includeRule;
+        Type type;
+        enum Type {SUFFIX, PREFIX, SUBSTR, REGEX}
+
+        private Rule(String regex, boolean includeRule, Type type) {
+            this.regex = regex;
+            this.type = type;
+            this.includeRule = includeRule;
+        }
+
+        boolean match(String str) {
+            if (type == Type.SUFFIX) {
+                return str.endsWith(regex);
+            }
+            if (type == Type.PREFIX) {
+                return str.startsWith(regex);
+            }
+            if (type == Type.SUBSTR) {
+                return str.contains(regex);
+            }
+            return str.matches(regex);
+        }
+
+        boolean treatAsAccept() {return includeRule;}
+
+        public static Rule suffix(String s) {
+            return new Rule(s, true, Type.SUFFIX);
+        }
+        public static Rule suffixNeg(String s) {
+            return new Rule(s, false, Type.SUFFIX);
+        }
+        static Rule prefix(String s) {
+            return new Rule(s, true, Type.PREFIX);
+        }
+        public static Rule prefixNeg(String s) {
+            return new Rule(s, false, Type.PREFIX);
+        }
+        static Rule substr(String s) {
+            return new Rule(s, true, Type.SUBSTR);
+        }
+        public static Rule substrNeg(String s) {
+            return new Rule(s, false, Type.SUBSTR);
+        }
+    }
+
+    public static boolean shouldExclude(File baseDir, File f, Rule ruleset[]) {
+        if (ruleset == null) {
+            return false;
+        }
+
+        String fname = f.getAbsolutePath().toLowerCase().substring(
+                baseDir.getAbsolutePath().length());
+        //first rule match defines the answer
+        for (Rule r: ruleset) {
+            if (r.match(fname)) {
+                return !r.treatAsAccept();
+            }
+        }
+        //default is include
+        return false;
+    }
+
+    public static void walk(File base, File root, Rule ruleset[], Set<File> files) {
+        if (!root.isDirectory()) {
+            if (root.isFile()) {
+                files.add(root);
+            }
+            return;
+        }
+
+        File[] lst = root.listFiles();
+        if (lst != null) {
+            for (File f : lst) {
+                //ignore symbolic links!
+                if (IOUtils.isNotSymbolicLink(f) && !shouldExclude(base, f, ruleset)) {
+                    if (f.isDirectory()) {
+                        walk(base, f, ruleset, files);
+                    } else if (f.isFile()) {
+                        //add to list
+                        files.add(f);
+                    }
+                }
+            }
+        }
+    }
+
+    public static RelativeFileSet extractJreAsRelativeFileSet(String root, JreUtils.Rule[] ruleset) {
+        File baseDir = new File(root);
+
+        Set<File> lst = new HashSet<>();
+
+        walk(baseDir, baseDir, ruleset, lst);
+
+        return new RelativeFileSet(baseDir, lst);
+    }
+
+}
--- a/modules/fxpackager/src/main/java/com/oracle/bundlers/StandardBundlerParam.java	Mon Mar 17 16:56:40 2014 +0400
+++ b/modules/fxpackager/src/main/java/com/oracle/bundlers/StandardBundlerParam.java	Mon Mar 17 09:51:09 2014 -0600
@@ -36,6 +36,7 @@
 import java.nio.file.Files;
 import java.text.MessageFormat;
 import java.util.*;
+import java.util.function.BiFunction;
 import java.util.function.Function;
 import java.util.jar.Attributes;
 import java.util.jar.JarFile;
@@ -46,7 +47,11 @@
     private static final ResourceBundle I18N =
             ResourceBundle.getBundle("com.oracle.bundlers.StandardBundlerParam");
 
-    public StandardBundlerParam(String name, String description, String id, Class<T> valueType, String[] fallbackIDs, Function<Map<String, ? super Object>, T> defaultValueFunction, boolean requiresUserSetting, Function<String, T> stringConverter) {
+    public StandardBundlerParam(String name, String description, String id,
+                                Class<T> valueType, String[] fallbackIDs,
+                                Function<Map<String, ? super Object>, T> defaultValueFunction,
+                                boolean requiresUserSetting,
+                                BiFunction<String, Map<String, ? super Object>, T> stringConverter) {
         this.name = name;
         this.description = description;
         this.id = id;
@@ -58,45 +63,16 @@
     }
 
     public static final StandardBundlerParam<RelativeFileSet> RUNTIME =
-        new StandardBundlerParam<>(
-                I18N.getString("param.runtime.name"),
-                I18N.getString("param.runtime.description"),
-                BundleParams.PARAM_RUNTIME,
-                RelativeFileSet.class,
-                null,
-                params -> extractJreAsRelativeFileSet(System.getProperty("java.home")),
-                false,
-                StandardBundlerParam::extractJreAsRelativeFileSet
-        );
-
-    public static RelativeFileSet extractJreAsRelativeFileSet(String root) {
-        File baseDir = new File(root);
-
-        boolean isMac = System.getProperty("os.name").toLowerCase().contains("os x");
-
-        //Normalization: on MacOS we need to point to the top of JDK dir
-        // (other platforms are fine)
-        if (isMac) {
-            //On Mac we need Bundle root, not jdk/Contents/Home
-            baseDir = baseDir.getParentFile().getParentFile().getParentFile();
-        }
-
-        Set<File> lst = new HashSet<>();
-
-        BundleParams.Rule ruleset[];
-        if (System.getProperty("os.name").startsWith("Mac")) {
-            ruleset = BundleParams.macRules;
-        } else if (System.getProperty("os.name").startsWith("Win")) {
-            ruleset = BundleParams.winRules;
-        } else {
-            //must be linux
-            ruleset = BundleParams.linuxRules;
-        }
-
-        BundleParams.walk(baseDir, baseDir, ruleset, lst);
-
-        return new RelativeFileSet(baseDir, lst);
-    }
+            new StandardBundlerParam<>(
+                    I18N.getString("param.runtime.name"),
+                    I18N.getString("param.runtime.description"),
+                    BundleParams.PARAM_RUNTIME,
+                    RelativeFileSet.class,
+                    null,
+                    params -> null,
+                    false,
+                    (s, p) -> null
+            );
 
     public static final StandardBundlerParam<RelativeFileSet> APP_RESOURCES =
             new StandardBundlerParam<>(
@@ -107,10 +83,10 @@
                     null,
                     null, // no default.  Required parameter
                     false,
-                    null // no string translation, tool must provide compelx type
+                    null // no string translation, tool must provide complex type
             );
 
-    public static final StandardBundlerParam<File> ICON  =
+    public static final StandardBundlerParam<File> ICON =
             new StandardBundlerParam<>(
                     I18N.getString("param.icon-file.name"),
                     I18N.getString("param.icon-file.description"),
@@ -119,70 +95,11 @@
                     null,
                     params -> null,
                     false,
-                    File::new
+                    (s, p) -> new File(s)
             );
 
-    public static final StandardBundlerParam<String> NAME  =
-            new StandardBundlerParam<>(
-                    I18N.getString("param.name.name"),
-                    I18N.getString("param.name.description"),
-                    BundleParams.PARAM_NAME,
-                    String.class,
-                    null,
-                    params -> {throw new IllegalArgumentException(MessageFormat.format(I18N.getString("error.required-parameter"), BundleParams.PARAM_NAME));},
-                    true,
-                    s -> s
-            );
 
-    public static final StandardBundlerParam<String> VENDOR  =
-            new StandardBundlerParam<>(
-                    I18N.getString("param.vendor.name"),
-                    I18N.getString("param.vendor.description"),
-                    BundleParams.PARAM_VENDOR,
-                    String.class,
-                    null,
-                    params -> I18N.getString("param.vendor.default"),
-                    false,
-                    s -> s
-            );
-
-    public static final StandardBundlerParam<String> CATEGORY  =
-            new StandardBundlerParam<>(
-                    I18N.getString("param.category.name"),
-                    I18N.getString("param.category.description"),
-                    BundleParams.PARAM_CATEGORY,
-                    String.class,
-                    null,
-                    params -> I18N.getString("param.category.default"),
-                    false,
-                    s -> s
-            );
-
-    public static final StandardBundlerParam<String> DESCRIPTION  =
-            new StandardBundlerParam<>(
-                    I18N.getString("param.description.name"),
-                    I18N.getString("param.description.description"),
-                    BundleParams.PARAM_DESCRIPTION,
-                    String.class,
-                    new String[] {NAME.getID()},
-                    params -> I18N.getString("param.description.default"),
-                    false,
-                    s -> s
-            );
-
-    public static final StandardBundlerParam<String> COPYRIGHT  =
-            new StandardBundlerParam<>(
-                    I18N.getString("param.copyright.name"),
-                    "The copyright for the application.",
-                    BundleParams.PARAM_COPYRIGHT,
-                    String.class,
-                    null,
-                    params -> MessageFormat.format(I18N.getString("param.copyright.default"), Calendar.getInstance().get(Calendar.YEAR)),
-                    false,
-                    s -> s
-            );
-
-    public static final StandardBundlerParam<String> MAIN_CLASS  =
+    public static final StandardBundlerParam<String> MAIN_CLASS =
             new StandardBundlerParam<>(
                     I18N.getString("param.main-class.name"),
                     I18N.getString("param.main-class.description"),
@@ -194,15 +111,84 @@
                         return (String) params.get(BundleParams.PARAM_APPLICATION_CLASS);
                     },
                     false,
-                    s -> s
+                    (s, p) -> s
+            );
+
+    public static final StandardBundlerParam<String> APP_NAME =
+            new StandardBundlerParam<>(
+                    I18N.getString("param.app-name.name"),
+                    I18N.getString("param.app-name.description"),
+                    BundleParams.PARAM_NAME,
+                    String.class,
+                    null,
+                    params -> {
+                        String s = MAIN_CLASS.fetchFrom(params);
+                        if (s == null) return null;
+
+                        int idx = s.lastIndexOf(".");
+                        if (idx >= 0) {
+                            return s.substring(idx+1);
+                        }
+                        return s;
+                    },
+                    true,
+                    (s, p) -> s
+            );
+
+    public static final StandardBundlerParam<String> VENDOR =
+            new StandardBundlerParam<>(
+                    I18N.getString("param.vendor.name"),
+                    I18N.getString("param.vendor.description"),
+                    BundleParams.PARAM_VENDOR,
+                    String.class,
+                    null,
+                    params -> I18N.getString("param.vendor.default"),
+                    false,
+                    (s, p) -> s
+            );
+
+    public static final StandardBundlerParam<String> CATEGORY =
+            new StandardBundlerParam<>(
+                    I18N.getString("param.category.name"),
+                    I18N.getString("param.category.description"),
+                    BundleParams.PARAM_CATEGORY,
+                    String.class,
+                    null,
+                    params -> I18N.getString("param.category.default"),
+                    false,
+                    (s, p) -> s
+            );
+
+    public static final StandardBundlerParam<String> DESCRIPTION =
+            new StandardBundlerParam<>(
+                    I18N.getString("param.description.name"),
+                    I18N.getString("param.description.description"),
+                    BundleParams.PARAM_DESCRIPTION,
+                    String.class,
+                    new String[] {APP_NAME.getID()},
+                    params -> I18N.getString("param.description.default"),
+                    false,
+                    (s, p) -> s
+            );
+
+    public static final StandardBundlerParam<String> COPYRIGHT =
+            new StandardBundlerParam<>(
+                    I18N.getString("param.copyright.name"),
+                    I18N.getString("param.copyright.description"),
+                    BundleParams.PARAM_COPYRIGHT,
+                    String.class,
+                    null,
+                    params -> MessageFormat.format(I18N.getString("param.copyright.default"), new Date()),
+                    false,
+                    (s, p) -> s
             );
 
     // note that each bundler is likely to replace this one with their own converter
-    public static final StandardBundlerParam<RelativeFileSet> MAIN_JAR  =
+    public static final StandardBundlerParam<RelativeFileSet> MAIN_JAR =
             new StandardBundlerParam<>(
                     I18N.getString("param.main-jar.name"),
                     I18N.getString("param.main-jar.description"),
-                    "mainJar", //KEY
+                    "mainJar",
                     RelativeFileSet.class,
                     null,
                     params -> {
@@ -210,33 +196,33 @@
                         return (RelativeFileSet) params.get("mainJar");
                     },
                     false,
-                    s -> {
-                        File f  = new File(s);
+                    (s, p) -> {
+                        File f = new File(s);
                         return new RelativeFileSet(f.getParentFile(), new LinkedHashSet<>(Arrays.asList(f)));
                     }
             );
 
-    public static final StandardBundlerParam<String> MAIN_JAR_CLASSPATH  =
+    public static final StandardBundlerParam<String> MAIN_JAR_CLASSPATH =
             new StandardBundlerParam<>(
                     I18N.getString("param.main-jar-classpath.name"),
                     I18N.getString("param.main-jar-classpath.description"),
-                    "mainJarClasspath", //KEY
+                    "classpath",
                     String.class,
                     null,
                     params -> {
                         extractParamsFromAppResources(params);
-                        String cp = (String) params.get("mainJarClasspath");
+                        String cp = (String) params.get("classpath");
                         return cp == null ? "" : cp;
                     },
                     false,
-                    s -> s
+                    (s, p) -> s
             );
 
-    public static final StandardBundlerParam<Boolean> USE_FX_PACKAGING  =
+    public static final StandardBundlerParam<Boolean> USE_FX_PACKAGING =
             new StandardBundlerParam<>(
                     I18N.getString("param.use-javafx-packaging.name"),
                     I18N.getString("param.use-javafx-packaging.description"),
-                    "fxPackaging", //KEY
+                    "fxPackaging",
                     Boolean.class,
                     null,
                     params -> {
@@ -244,33 +230,33 @@
                         return (Boolean) params.get("fxPackaging");
                     },
                     false,
-                    Boolean::valueOf
+                    (s, p) -> Boolean.valueOf(s)
             );
 
     @SuppressWarnings("unchecked")
-    public static final StandardBundlerParam<List<String>> JVM_OPTIONS  =
+    public static final StandardBundlerParam<List<String>> JVM_OPTIONS =
             new StandardBundlerParam<>(
                     I18N.getString("param.jvm-options.name"),
                     I18N.getString("param.jvm-options.description"),
-                    "jvmOptions", //KEY
+                    "jvmOptions",
                     (Class<List<String>>) (Object) List.class,
                     null,
                     params -> Collections.emptyList(),
                     false,
-                    s -> Arrays.<String>asList(s.split("\\s+"))
+                    (s, p) -> Arrays.asList(s.split("\\s+"))
             );
 
     @SuppressWarnings("unchecked")
-    public static final StandardBundlerParam<Map<String, String>> JVM_PROPERTIES  =
+    public static final StandardBundlerParam<Map<String, String>> JVM_PROPERTIES =
             new StandardBundlerParam<>(
                     I18N.getString("param.jvm-system-properties.name"),
                     I18N.getString("param.jvm-system-properties.description"),
-                    "jvmProperties", //KEY
+                    "jvmProperties",
                     (Class<Map<String, String>>) (Object) Map.class,
                     null,
                     params -> Collections.emptyMap(),
                     false,
-                    s -> {
+                    (s, params) -> {
                         Map<String, String> map = new HashMap<>();
                         try {
                             Properties p = new Properties();
@@ -286,16 +272,16 @@
             );
 
     @SuppressWarnings("unchecked")
-    public static final StandardBundlerParam<Map<String, String>> USER_JVM_OPTIONS  =
+    public static final StandardBundlerParam<Map<String, String>> USER_JVM_OPTIONS =
             new StandardBundlerParam<>(
                     I18N.getString("param.user-jvm-options.name"),
                     I18N.getString("param.user-jvm-options.description"),
-                    "userJvmOptions", //KEY
+                    "userJvmOptions",
                     (Class<Map<String, String>>) (Object) Map.class,
                     null,
                     params -> Collections.emptyMap(),
                     false,
-                    s -> {
+                    (s, params) -> {
                         Map<String, String> map = new HashMap<>();
                         try {
                             Properties p = new Properties();
@@ -310,44 +296,21 @@
                     }
             );
 
-
-
-    public static final StandardBundlerParam<String> APP_NAME  =
-            new StandardBundlerParam<>(
-                    I18N.getString("param.app-name.name"),
-                    I18N.getString("param.app-name.description"),
-                    BundleParams.PARAM_APP_NAME, //KEY
-                    String.class,
-                    new String[] {BundleParams.PARAM_NAME},
-                    params -> {
-                        String s = MAIN_CLASS.fetchFrom(params);
-                        if (s == null) return null;
-
-                        int idx = s.lastIndexOf(".");
-                        if (idx >= 0) {
-                            return s.substring(idx+1);
-                        }
-                        return s;
-                    },
-                    true,
-                    s -> s
-            );
-
-    public static final StandardBundlerParam<String> TITLE  =
+    public static final StandardBundlerParam<String> TITLE =
             new StandardBundlerParam<>(
                     I18N.getString("param.title.name"),
                     I18N.getString("param.title.description"), //?? but what does it do?
                     BundleParams.PARAM_TITLE,
                     String.class,
-                    new String[] {NAME.getID()},
+                    new String[] {APP_NAME.getID()},
                     APP_NAME::fetchFrom,
                     false,
-                    s -> s
+                    (s, p) -> s
             );
 
 
     // note that each bundler is likely to replace this one with their own converter
-    public static final StandardBundlerParam<String> VERSION  =
+    public static final StandardBundlerParam<String> VERSION =
             new StandardBundlerParam<>(
                     I18N.getString("param.version.name"),
                     I18N.getString("param.version.description"),
@@ -356,36 +319,88 @@
                     null,
                     params -> I18N.getString("param.version.default"),
                     false,
-                    s -> s
+                    (s, p) -> s
             );
 
-    public static final StandardBundlerParam<Boolean> SYSTEM_WIDE  =
+    public static final StandardBundlerParam<Boolean> SYSTEM_WIDE =
             new StandardBundlerParam<>(
                     I18N.getString("param.system-wide.name"),
                     I18N.getString("param.system-wide.description"),
-                    BundleParams.PARAM_SYSTEM_WIDE, //KEY
+                    BundleParams.PARAM_SYSTEM_WIDE,
                     Boolean.class,
                     null,
                     params -> null,
                     false,
                     // valueOf(null) is false, and we actually do want null in some cases
-                    s -> (s == null || "null".equalsIgnoreCase(s))? null : Boolean.valueOf(s)
+                    (s, p) -> (s == null || "null".equalsIgnoreCase(s))? null : Boolean.valueOf(s)
             );
 
-    public static final StandardBundlerParam<Boolean> SHORTCUT_HINT  =
+    public static final StandardBundlerParam<Boolean> SERVICE_HINT  =
             new StandardBundlerParam<>(
-                    I18N.getString("param.desktop-shortcut-hint.name"),
-                    I18N.getString("param.desktop-shortcut-hint.description"),
-                    BundleParams.PARAM_SHORTCUT, //KEY
+                    I18N.getString("param.service-hint.name"),
+                    I18N.getString("param.service-hint.description"),
+                    "serviceHint",
                     Boolean.class,
                     null,
                     params -> false,
                     false,
                     // valueOf(null) is false, and we actually do want null in some cases
-                    s -> (s == null || "null".equalsIgnoreCase(s))? false : Boolean.valueOf(s)
+                    (s, p) -> (s == null || "null".equalsIgnoreCase(s))? true : Boolean.valueOf(s)
             );
 
-    public static final StandardBundlerParam<Boolean> MENU_HINT  =
+    public static final StandardBundlerParam<Boolean> START_ON_INSTALL  =
+            new StandardBundlerParam<>(
+                    I18N.getString("param.start-on-install.name"),
+                    I18N.getString("param.start-on-install.description"),
+                    "startOnInstall",
+                    Boolean.class,
+                    null,
+                    params -> false,
+                    false,
+                    // valueOf(null) is false, and we actually do want null in some cases
+                    (s, p) -> (s == null || "null".equalsIgnoreCase(s))? true : Boolean.valueOf(s)
+            );
+
+    public static final StandardBundlerParam<Boolean> STOP_ON_UNINSTALL  =
+            new StandardBundlerParam<>(
+                    I18N.getString("param.stop-on-uninstall.name"),
+                    I18N.getString("param.stop-on-uninstall.description"),
+                    "stopOnUninstall",
+                    Boolean.class,
+                    null,
+                    params -> false,
+                    false,
+                    // valueOf(null) is false, and we actually do want null in some cases
+                    (s, p) -> (s == null || "null".equalsIgnoreCase(s))? true : Boolean.valueOf(s)
+            );
+
+    public static final StandardBundlerParam<Boolean> RUN_AT_STARTUP  =
+            new StandardBundlerParam<>(
+                    I18N.getString("param.run-at-startup.name"),
+                    I18N.getString("param.run-at-startup.description"),
+                    "runAtStartup",
+                    Boolean.class,
+                    null,
+                    params -> false,
+                    false,
+                    // valueOf(null) is false, and we actually do want null in some cases
+                    (s, p) -> (s == null || "null".equalsIgnoreCase(s))? true : Boolean.valueOf(s)
+            );
+
+    public static final StandardBundlerParam<Boolean> SHORTCUT_HINT =
+            new StandardBundlerParam<>(
+                    I18N.getString("param.desktop-shortcut-hint.name"),
+                    I18N.getString("param.desktop-shortcut-hint.description"),
+                    BundleParams.PARAM_SHORTCUT,
+                    Boolean.class,
+                    null,
+                    params -> false,
+                    false,
+                    // valueOf(null) is false, and we actually do want null in some cases
+                    (s, p) -> (s == null || "null".equalsIgnoreCase(s))? false : Boolean.valueOf(s)
+            );
+
+    public static final StandardBundlerParam<Boolean> MENU_HINT =
             new StandardBundlerParam<>(
                     I18N.getString("param.menu-shortcut-hint.name"),
                     I18N.getString("param.menu-shortcut-hint.description"),
@@ -395,7 +410,7 @@
                     params -> true,
                     false,
                     // valueOf(null) is false, and we actually do want null in some cases
-                    s -> (s == null || "null".equalsIgnoreCase(s))? true : Boolean.valueOf(s)
+                    (s, p) -> (s == null || "null".equalsIgnoreCase(s))? true : Boolean.valueOf(s)
             );
 
     @SuppressWarnings("unchecked")
@@ -408,24 +423,24 @@
                     null,
                     params -> Collections.<String>emptyList(),
                     false,
-                    s -> Arrays.asList(s.split(","))
+                    (s, p) -> Arrays.asList(s.split(","))
             );
 
-    public static final BundlerParamInfo<String> LICENSE_TYPE = 
+    public static final BundlerParamInfo<String> LICENSE_TYPE =
             new StandardBundlerParam<> (
                     I18N.getString("param.license-type.name"),
                     I18N.getString("param.license-type.description"),
                     BundleParams.PARAM_LICENSE_TYPE,
                     String.class, null,
                     params -> I18N.getString("param.license-type.default"),
-                    false, s -> s
+                    false, (s, p) -> s
             );
 
     public static final StandardBundlerParam<File> BUILD_ROOT =
             new StandardBundlerParam<>(
                     I18N.getString("param.build-root.name"),
                     I18N.getString("param.build-root.description"),
-                    "buildRoot", //KEY
+                    "buildRoot",
                     File.class,
                     null,
                     params -> {
@@ -436,10 +451,10 @@
                         }
                     },
                     false,
-                    File::new
+                    (s, p) -> new File(s)
             );
 
-    public static final StandardBundlerParam<String> IDENTIFIER  =
+    public static final StandardBundlerParam<String> IDENTIFIER =
             new StandardBundlerParam<>(
                     I18N.getString("param.identifier.name"),
                     I18N.getString("param.identifier.description"),
@@ -457,10 +472,10 @@
                         return s;
                     },
                     false,
-                    s -> s
+                    (s, p) -> s
             );
 
-    public static final StandardBundlerParam<String> PREFERENCES_ID  =
+    public static final StandardBundlerParam<String> PREFERENCES_ID =
             new StandardBundlerParam<>(
                     I18N.getString("param.preferences-id.name"),
                     I18N.getString("param.preferences-id.description"),
@@ -469,7 +484,20 @@
                     new String[] {IDENTIFIER.getID()},
                     params -> null, // todo take the package of the main app class
                     false,
-                    s -> s
+                    (s, p) -> s
+            );
+
+    public static final StandardBundlerParam<Boolean> VERBOSE  =
+            new StandardBundlerParam<>(
+                    I18N.getString("param.verbose.name"),
+                    I18N.getString("param.verbose.description"),
+                    "verbose",
+                    Boolean.class,
+                    null,
+                    params -> false,
+                    false,
+                    // valueOf(null) is false, and we actually do want null in some cases
+                    (s, p) -> (s == null || "null".equalsIgnoreCase(s))? true : Boolean.valueOf(s)
             );
 
     public static void extractParamsFromAppResources(Map<String, ? super Object> params) {
--- a/modules/fxpackager/src/main/java/com/oracle/bundlers/mac/MacAppStoreBundler.java	Mon Mar 17 16:56:40 2014 +0400
+++ b/modules/fxpackager/src/main/java/com/oracle/bundlers/mac/MacAppStoreBundler.java	Mon Mar 17 09:51:09 2014 -0600
@@ -25,25 +25,326 @@
 
 package com.oracle.bundlers.mac;
 
-import com.oracle.bundlers.AbstractBundler;
 import com.oracle.bundlers.BundlerParamInfo;
-import com.sun.javafx.tools.packager.bundlers.BundleType;
+import com.oracle.bundlers.JreUtils;
+import com.oracle.bundlers.StandardBundlerParam;
+import com.sun.javafx.tools.packager.Log;
 import com.sun.javafx.tools.packager.bundlers.ConfigException;
+import com.sun.javafx.tools.packager.bundlers.IOUtils;
+import com.sun.javafx.tools.packager.bundlers.MacAppBundler;
 import com.sun.javafx.tools.packager.bundlers.UnsupportedPlatformException;
+import com.sun.javafx.tools.resource.mac.MacResources;
 
+import java.io.ByteArrayOutputStream;
 import java.io.File;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
+import java.util.List;
 import java.util.Map;
+import java.util.ResourceBundle;
+import java.util.function.Consumer;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
 
-public class MacAppStoreBundler extends AbstractBundler {
+import static com.oracle.bundlers.JreUtils.Rule.suffix;
+import static com.oracle.bundlers.JreUtils.Rule.suffixNeg;
+import static com.oracle.bundlers.StandardBundlerParam.IDENTIFIER;
+import static com.oracle.bundlers.StandardBundlerParam.APP_NAME;
+import static com.oracle.bundlers.StandardBundlerParam.VERBOSE;
+
+public class MacAppStoreBundler extends MacBaseInstallerBundler {
+
+    private static final ResourceBundle I18N =
+            ResourceBundle.getBundle("com.oracle.bundlers.mac.MacAppStoreBundler");
+
+    private static final String TEMPLATE_BUNDLE_ICON_HIDPI = "GenericAppHiDPI.icns";
+    private final static String DEFAULT_ENTITLEMENTS = "MacAppStore.entitlements";
+    private final static String DEFAULT_INHERIT_ENTITLEMENTS = "MacAppStore_Inherit.entitlements";
+
+    //Subsetting of JRE is restricted.
+    //JRE README defines what is allowed to strip:
+    //   ´╗┐http://www.oracle.com/technetwork/java/javase/jre-7-readme-430162.html //TODO update when 8 goes GA
+    //
+    public static final JreUtils.Rule[] MAC_APP_STORE_JDK_RULES =  new JreUtils.Rule[]{
+            suffixNeg("macos/libjli.dylib"),
+            suffixNeg("resources"),
+            suffixNeg("home/bin"),
+            suffixNeg("home/db"),
+            suffixNeg("home/demo"),
+            suffixNeg("home/include"),
+            suffixNeg("home/lib"),
+            suffixNeg("home/man"),
+            suffixNeg("home/release"),
+            suffixNeg("home/sample"),
+            suffixNeg("home/src.zip"),
+            //"home/rt" is not part of the official builds
+            // but we may be creating this symlink to make older NB projects
+            // happy. Make sure to not include it into final artifact
+            suffixNeg("home/rt"),
+            suffixNeg("jre/bin"),
+            suffixNeg("bin/rmiregistry"),
+            suffixNeg("bin/tnameserv"),
+            suffixNeg("bin/keytool"),
+            suffixNeg("bin/klist"),
+            suffixNeg("bin/ktab"),
+            suffixNeg("bin/policytool"),
+            suffixNeg("bin/orbd"),
+            suffixNeg("bin/servertool"),
+            suffixNeg("bin/javaws"),
+            suffixNeg("bin/java"),
+            //Rule.suffixNeg("jre/lib/ext"), //need some of jars there for https to work
+            suffixNeg("jre/lib/nibs"),
+            //keep core deploy APIs but strip plugin dll
+            //Rule.suffixNeg("jre/lib/deploy"),
+            //Rule.suffixNeg("jre/lib/deploy.jar"),
+            //Rule.suffixNeg("jre/lib/javaws.jar"),
+            //Rule.suffixNeg("jre/lib/libdeploy.dylib"),
+            //Rule.suffixNeg("jre/lib/plugin.jar"),
+            suffixNeg("lib/libnpjp2.dylib"),
+            suffixNeg("lib/security/javaws.policy"),
+
+            // jfxmedia uses QuickTime, which is not allowed as of OSX 10.9
+            suffixNeg("lib/libjfxmedia.dylib"),
+
+            // the plist is needed for signing
+            suffix("Info.plist"),
+
+    };
+
+    public static final BundlerParamInfo<String> MAC_APP_STORE_SIGNING_KEY_USER = new StandardBundlerParam<>(
+            I18N.getString("param.signing-key-name.name"),
+            I18N.getString("param.signing-key-name.description"),
+            "mac.signing-key-user-name",
+            String.class,
+            null,
+            params -> {
+                try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); PrintStream ps = new PrintStream(baos)) {
+                    ProcessBuilder pb = new ProcessBuilder(
+                            "dscacheutil",
+                            "-q", "user", "-a", "name", System.getProperty("user.name"));
+
+                    IOUtils.exec(pb, Log.isDebug(), false, ps);
+
+                    String commandOutput = baos.toString();