changeset 4009:1ef5290637c0

RT-25339 Add to the launcher generated by the packager support for handling VM parameters [ngthomas]
author mhowe
date Mon, 17 Jun 2013 15:14:32 -0700
parents 0809810d0ae1
children d5f576e93c8d
files deploy/packager/native/linux/launcher.c deploy/packager/native/macosx/main.m deploy/packager/native/windows/WinLauncher.cpp deploy/packager/src/com/sun/javafx/tools/ant/DeployFXTask.java deploy/packager/src/com/sun/javafx/tools/ant/Platform.java deploy/packager/src/com/sun/javafx/tools/packager/DeployParams.java deploy/packager/src/com/sun/javafx/tools/packager/bundlers/BundleParams.java deploy/packager/src/com/sun/javafx/tools/packager/bundlers/LinuxAppBundler.java deploy/packager/src/com/sun/javafx/tools/packager/bundlers/MacAppBundler.java deploy/packager/src/com/sun/javafx/tools/packager/bundlers/WinAppBundler.java deploy/packager/src/com/sun/javafx/tools/resource/mac/Info.plist.template
diffstat 11 files changed, 617 insertions(+), 66 deletions(-) [+]
line wrap: on
line diff
--- a/deploy/packager/native/linux/launcher.c	Wed Jun 12 15:34:39 2013 -0700
+++ b/deploy/packager/native/linux/launcher.c	Mon Jun 17 15:14:32 2013 -0700
@@ -31,9 +31,12 @@
 #include <unistd.h>
 #include <sys/stat.h>
 #include <dlfcn.h>
+#include <pwd.h>
 
 #include "jni.h"
 
+#include "xmlparser.h"
+
 #define TRUE  1
 #define FALSE 0
 
@@ -51,7 +54,7 @@
 }
 
 //constructs full file name for file in the package
-// and check for it existance
+// and check for it existence
 int getFileInPackage(char* basedir, char *relative_path, char *fullpath, int buffer_size) {
      makeFullFileName(basedir, relative_path, fullpath, buffer_size);
     return fileExists(fullpath);
@@ -62,6 +65,7 @@
 #define CONFIG_MAINJAR_KEY    "app.mainjar"
 #define CONFIG_MAINCLASS_KEY  "app.mainclass"
 #define CONFIG_CLASSPATH_KEY  "app.classpath"
+#define CONFIG_APP_ID_KEY     "app.preferences.id"
 
 //remove trailing end of line character
 //modifies buffer in place
@@ -260,8 +264,226 @@
     return strdup(buffer);
 }
 
+#define MAX_OPTIONS 100
+#define MAX_ARGUMENT_LEN 1000
 
-#define MAX_OPTIONS 100
+typedef struct {
+    char* name;
+    char* value;
+} JVMUserArg;
+
+typedef struct {
+    JVMUserArg *args;
+    int maxSize;
+    int currentSize;
+    int initialElements;
+} JVMUserArgs;
+
+/**
+ * Creates an array of string pointer where each non null entry is malloced and
+ * needs to be freed
+ *
+ * @param basedir
+ * @param keys
+ * @param size
+ */
+void JVMUserArgs_initializeDefaults(JVMUserArgs *this, char* basedir) {
+    //void getArray(char* basedir, char** keys, int maxSize, int *actualSize) {
+    char jvmArgID[40];
+    char argvalue[MAX_ARGUMENT_LEN];
+    char name[MAX_ARGUMENT_LEN];
+    char value[MAX_ARGUMENT_LEN];
+    JVMUserArg* keys = this->args;
+
+    int index = 0;
+    int found = 0;
+    do {
+        sprintf(jvmArgID, "jvmuserarg.%d", (index + 1));
+        found = getConfigValue(basedir, jvmArgID, argvalue, sizeof (argvalue));
+        if (found) {
+            index++;
+            if (splitOptionIntoNameValue(argvalue, name, sizeof (name), value, sizeof (value))) {
+                keys->name = strdup(name);
+                keys->value = strdup(value);
+                keys++;
+                this->initialElements++;
+                this->currentSize++;
+            } else {
+                printf("Failed to split jvmuserarg \"%s\" into name, value pair\n", argvalue);
+            }
+        }
+    } while (found && index <= this->maxSize);
+}
+
+/*
+ * Assumes that userPref can hold the size of this path
+ */
+int getUserPrefFile(char* userPref, char* appid) {
+    userPref[0] = 0;
+    struct passwd *pw = getpwuid(getuid());
+    const char *homedir = pw->pw_dir;
+
+    strcat(userPref, homedir);
+    strcat(userPref, "/.java/.userPrefs/");
+    strcat(userPref, appid);
+    strcat(userPref, "/JVMOptions");
+    strcat(userPref, "/prefs.xml");
+    return fileExists(userPref);
+}
+
+void addModifyArgs(JVMUserArgs* this, char* name, char* value) {
+    int i;
+    JVMUserArg* arg = this->args;
+    for (i = 0; i < this->initialElements; i++) {
+        if (strcmp(arg[i].name, name) == 0) {
+            free(arg[i].value);
+            arg[i].value = malloc((strlen(value) + 1) * sizeof (char));
+            strcpy(arg[i].value, value);
+            return; //Replaced
+        }
+    }
+
+    //Add new jvm arg from name value
+    int newIndex = this->currentSize;
+    arg[newIndex].name = malloc((strlen(name) + 1) * sizeof (char));
+    strcpy(arg[newIndex].name, name);
+    arg[newIndex].value = malloc((strlen(value) + 1) * sizeof (char));
+    strcpy(arg[newIndex].value, value);
+    this->currentSize++;
+}
+
+void findAndModifyNode(XMLNode* node, JVMUserArgs* args) {
+    XMLNode* keyNode = NULL;
+    char* key;
+    char* value;
+    keyNode = FindXMLChild(node->_sub, "entry");
+
+    while (keyNode != NULL && args->currentSize < args->maxSize) {
+        key = FindXMLAttribute(keyNode->_attributes, "key");
+        value = FindXMLAttribute(keyNode->_attributes, "value");
+        addModifyArgs(args, key, value);
+        keyNode = keyNode->_next;
+    }
+}
+
+int getJvmUserArgs(JVMUserArgs* args, char* userPrefsPath) {
+    FILE *fp;
+
+    if (fileExists(userPrefsPath)) {
+        //scan file for the key
+        fp = fopen(userPrefsPath, "r");
+        if (fp == NULL) {
+            return;
+        }
+
+        fseek(fp, 0, SEEK_END);
+        long fsize = ftell(fp);
+        rewind(fp);
+        char *buf = malloc(fsize + 1);
+        fread(buf, fsize, 1, fp);
+        fclose(fp);
+        buf[fsize] = 0;
+
+        XMLNode* node = NULL;
+        XMLNode* doc = ParseXMLDocument(buf);
+        if (doc != NULL) {
+            node = FindXMLChild(doc, "map");
+            if (node != NULL) {
+                findAndModifyNode(node, args);
+            }
+        }
+        free(buf);
+    }
+    return args->currentSize;
+}
+
+//Assumes are values passed in are big enough
+
+int splitOptionIntoNameValue(char* argValue,
+        char* optionName,
+        int nameSize,
+        char* optionValue,
+        int valueSize) {
+    char* ptr;
+
+    ptr = strstr(argValue, "##");
+    if (ptr != NULL) {
+        int len = strlen(ptr);
+        if (len > 0 && len < valueSize) {
+            ptr++;
+            ptr++;
+            strcpy(optionValue, ptr);
+
+            int argLen = strlen(argValue);
+            if ((argLen - len + 1) < nameSize) {
+                memcpy(optionName, argValue, argLen - len);
+                optionName[argLen - len] = '\0';
+                return TRUE;
+            }
+        }
+    }
+    return FALSE;
+}
+
+/*
+ * Converts JVMUserArg to a single jvm argument.
+ *
+ * This is used to convert to the actual string passed into the jvm, so has
+ * option to free memory of the sub strings
+ *
+ * Returned string can be freed
+ */
+char* JVMUserArg_toString(JVMUserArg arg, int freeMemory) {
+    int len = strlen(arg.name);
+    len += strlen(arg.value);
+    char* newString = calloc(len + 1, sizeof (char));
+    strcat(newString, arg.name);
+    strcat(newString, arg.value);
+    if (freeMemory == TRUE) {
+        free(arg.name);
+        free(arg.value);
+    }
+    return newString;
+}
+
+int addUserOptions(char* basedir, JavaVMOption* options, int cnt) {
+    JVMUserArg args[MAX_OPTIONS - cnt];
+    JVMUserArgs jvmUserArgs;
+    char appid[MAX_ARGUMENT_LEN] = {0};
+    char userPref[MAX_PATH] = {0};
+    char argvalue[MAX_ARGUMENT_LEN];
+
+
+    jvmUserArgs.args = args;
+    jvmUserArgs.currentSize = 0;
+    jvmUserArgs.initialElements = 0;
+    jvmUserArgs.maxSize = MAX_OPTIONS - cnt;
+    memset(&args, 0, sizeof (JVMUserArg)*(MAX_OPTIONS - cnt + 1));
+
+    JVMUserArgs_initializeDefaults(&jvmUserArgs, basedir);
+
+    //Add property to command line for preferences id
+    if (getConfigValue(basedir, CONFIG_APP_ID_KEY, appid, sizeof (appid))) {
+        sprintf(argvalue, "-D%s=%s", CONFIG_APP_ID_KEY, appid);
+        options[cnt].optionString = strdup(argvalue);
+        cnt++;
+
+        if (getUserPrefFile(userPref, appid)) {
+            getJvmUserArgs(&jvmUserArgs, userPref);
+            int i;
+            for (i = 0; i < jvmUserArgs.currentSize; i++) {
+                options[cnt].optionString = JVMUserArg_toString(args[i], TRUE);
+                cnt++;
+            }
+        } else {
+            printf("MESSAGE: User preferences file doesn't exist: %s", userPref);
+        }
+    } else {
+        printf("WARNING: %s not defined:", CONFIG_APP_ID_KEY);
+    }
+    return cnt;
+}
+
 
 int startJVM(char* basedir, char *appFolder, char* jar, int argc, const char**argv) {
     char jvmPath[MAX_PATH+1] = {0},
@@ -338,7 +560,7 @@
 
     options[0].optionString = classpath;
 
-    //add app scecific JVM parameters
+    //add application specific JVM parameters
     int cnt = 1;
 
     //Note: should not try to quote the path. Spaces are fine here
@@ -360,6 +582,8 @@
        }
     } while (found && cnt < MAX_OPTIONS);
 
+    cnt = addUserOptions(basedir, options, cnt);
+
     jvmArgs.version = 0x00010002;
     jvmArgs.options = options;
     jvmArgs.nOptions = cnt;
--- a/deploy/packager/native/macosx/main.m	Wed Jun 12 15:34:39 2013 -0700
+++ b/deploy/packager/native/macosx/main.m	Mon Jun 17 15:14:32 2013 -0700
@@ -46,7 +46,9 @@
 #define JVM_MAIN_JAR_NAME_KEY "JVMMainJarName"
 #define JVM_OPTIONS_KEY "JVMOptions"
 #define JVM_ARGUMENTS_KEY "JVMArguments"
+#define JVM_USER_OPTIONS_KEY "JVMUserOptions"
 #define JVM_CLASSPATH_KEY "JVMAppClasspath"
+#define JVM_PREFERENCES_ID "JVMPreferencesID"
 
 #define LIBJLI_DYLIB "/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home/lib/jli/libjli.dylib"
 
@@ -64,6 +66,8 @@
 
 int launch(int appArgc, char *appArgv[]);
 
+NSArray *getJVMOptions(NSDictionary *infoDictionary, NSString *mainBundlePath);
+
 int main(int argc, char *argv[]) {
     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
 
@@ -151,24 +155,7 @@
     // Set the library path
     NSString *libraryPath = [NSString stringWithFormat:@"-Djava.library.path=%@/Contents/Java", mainBundlePath];
 
-    // Get the VM options
-    NSArray *options = [infoDictionary objectForKey:@JVM_OPTIONS_KEY];
-    if (options == nil) {
-        options = [NSArray array];
-    }
-    else {
-        //Do string substitutions - for now only one is $APPDIR
-        NSString *contentsPath = [mainBundlePath stringByAppendingString:@"/Contents"];
-        NSMutableArray *expandedOptions = [options mutableCopy];
-        NSUInteger index = 0;
-        for (id option in options) {
-            NSString *expandedOption =
-                    [option stringByReplacingOccurrencesOfString:@"$APPDIR" withString:contentsPath];
-            [expandedOptions replaceObjectAtIndex:index withObject:expandedOption];
-            index++;
-        }
-        options = expandedOptions;
-    }
+    NSArray *options= getJVMOptions(infoDictionary, mainBundlePath);
 
     // Get the application arguments
     NSArray *arguments = [infoDictionary objectForKey:@JVM_ARGUMENTS_KEY];
@@ -240,3 +227,107 @@
             FALSE,
             0);
 }
+
+/**
+* This gets the JVMOptions, both the internal developer only options and set of options the developer
+* wants a user to be able to override.
+*
+* The developer would set the options they required, for instance:
+
+            <fx:platform>
+              <fx:jvmarg value="-verbose:class"/>
+              <fx:jvmarg value="-Djava.policy.file=$APPDIR/app/whatever.policy"/>
+              <fx:jvmuserarg name="-Xmx" value="768m" />
+              <fx:jvmuserarg name="-Djava.util.logging.config.file=" value="~/logging.properties" />
+            </fx:platform>
+
+* this will result in Info.plist having (default)
+
+  <key>JVMOptions</key>
+  <array>
+    <string>-verbose:class</string>
+    <string>-Djava.policy.file=$APPDIR/app/whatever.policy</string>
+  </array>
+  <key>JVMUserOptions</key>
+    <dict>
+      <key>-Djava.util.logging.config.file=</key>
+      <string>~/logging.properties</string>
+      <key>-Xmx</key>
+      <string>768m</string>
+    </dict>
+
+* and initially set up the applications preference file in ~/Library/Preferences/..name based on bundleid..plist,
+* i.e. just the JVMUserOptions
+
+	<key>JVMUserOptions</key>
+	<dict>
+		<key>-Djava.util.logging.config.file=</key>
+		<string>~/logging.properties</string>
+		<key>-Xmx</key>
+		<string>860m</string>
+	</dict>
+
+*/
+NSArray *getJVMOptions(NSDictionary *infoDictionary, NSString *mainBundlePath) {
+    NSArray *options = [infoDictionary objectForKey:@JVM_OPTIONS_KEY];
+    NSDictionary *defaultOverrides = [infoDictionary objectForKey:@JVM_USER_OPTIONS_KEY];
+
+    if (options == nil) {
+        options = [NSArray array];
+    }
+
+
+    //Do string substitutions - for now only one is $APPDIR, if a second one is added this will
+    //be generalized and use a set of options
+    NSString *contentsPath = [mainBundlePath stringByAppendingString:@"/Contents"];
+
+    //Create some extra room for user options and preferences id
+    NSMutableArray *expandedOptions = [[NSMutableArray alloc] initWithCapacity:(
+            [options count] + [defaultOverrides count] + 5)];
+
+    //Add preferences ID
+    NSString *preferencesID = [infoDictionary objectForKey:@JVM_PREFERENCES_ID];
+    if (preferencesID != nil) {
+        [expandedOptions addObject: [@"-Djvm.preferences.id=" stringByAppendingString: preferencesID]];
+    }
+
+    for (id option in options) {
+        NSString *expandedOption =
+                [option stringByReplacingOccurrencesOfString:@"$APPDIR" withString:contentsPath];
+        [expandedOptions addObject:expandedOption];
+    }
+
+
+    //Add user overridable jvm options, from user config if different otherwise from defaults
+    NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
+    NSArray *userOverrides= [userDefaults objectForKey:@JVM_USER_OPTIONS_KEY];
+
+    //If overrides don't exist add them - the intent is to make it easier for the user to actually modify them
+    if ([userOverrides count] == 0) {
+        [userDefaults setObject: defaultOverrides forKey:@JVM_USER_OPTIONS_KEY];
+    }
+
+    for (id key in defaultOverrides) {
+
+        NSString *newOption;
+        if ([userOverrides valueForKey: key] != nil &&
+            [[userOverrides valueForKey:key] isNotEqualTo:[defaultOverrides valueForKey:key]]) {
+            newOption = [key stringByAppendingString:[userOverrides valueForKey:key]];
+        }
+        else {
+            newOption = [key stringByAppendingString:[defaultOverrides valueForKey:key]];
+        }
+        [expandedOptions addObject: newOption];
+    }
+
+    //Loop through all user override keys again looking for ones we haven't already uses
+    for (id key in userOverrides) {
+        //If the default object for key is nil, this is an option the user added so include
+        if ([defaultOverrides objectForKey: key] == nil) {
+            NSString *newOption = [key stringByAppendingString:[userOverrides valueForKey:key]];
+            [expandedOptions addObject: newOption];
+        }
+    }
+
+    return expandedOptions;
+}
\ No newline at end of file
--- a/deploy/packager/native/windows/WinLauncher.cpp	Wed Jun 12 15:34:39 2013 -0700
+++ b/deploy/packager/native/windows/WinLauncher.cpp	Mon Jun 17 15:14:32 2013 -0700
@@ -29,6 +29,7 @@
 #define _WIN32_WINNT 0x0501
 
 #include <Windows.h>
+#include <shellapi.h> //DO NOT REMOVE, necessary for Gradle builds
 #include <tchar.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -39,7 +40,6 @@
 
 #include "jni.h"
 
-#define snprintf _snprintf
 
 /*
    This is launcher program for application package on Windows.
@@ -129,6 +129,7 @@
 #define CONFIG_MAINJAR_KEY    _T("app.mainjar")
 #define CONFIG_MAINCLASS_KEY  _T("app.mainclass")
 #define CONFIG_CLASSPATH_KEY  _T("app.classpath")
+#define CONFIG_APP_ID_KEY     _T("app.preferences.id");
 
 //remove trailing end of line character
 //modifies buffer in place
@@ -307,31 +308,157 @@
  * @return either original str or a new str (via strdup) that replaces the
  *         pattern with the replaceWith string
  */
-char *replaceStr(char *str, char *pattern, char *replaceWith) {
-	static char buffer[MAX_PATH*2] = {0};
-    char *p;
+TCHAR *replaceStr(TCHAR *str, TCHAR *pattern, TCHAR *replaceWith) {
+	TCHAR buffer[MAX_PATH*2] = {0};
+    TCHAR *p;
 
     //Return orig if str is not in orig.
-    if(!(p = strstr(str, pattern))) {
-      return str;
+    if(!(p = wcsstr(str, pattern))) {
+		return wcsdup(str);
     }
 
     int loc = p-str;
     if (loc >= sizeof(buffer)) {
-        return str;
+        return wcsdup(str);
     }
 
-    strncpy(buffer, str, loc); // Copy characters from 'str' start to 'orig' st$
+    wcsncpy(buffer, str, loc); // Copy characters from 'str' start to 'orig' st$
     buffer[loc] = '\0';
 
     int remaingBufferSize = sizeof(buffer) - loc;
-    int len = snprintf(buffer+(loc), remaingBufferSize, "%s%s", replaceWith, p+strlen(pattern));
+    int len = _snwprintf(buffer+(loc), remaingBufferSize, _T("%s%s"), replaceWith, p + wcslen(pattern));
     if(len > remaingBufferSize ) {
-        return str;
+        return wcsdup(str);
     }
-    return strdup(buffer);
+    return wcsdup(buffer);
 }
 
+
+//Assumes are values passed in are big enough
+
+int splitOptionIntoNameValue(TCHAR* argValue, TCHAR* optionName, int nameSize,
+        TCHAR* optionValue, int valueSize) {
+    TCHAR* ptr;
+
+    ptr = wcsstr(argValue, _T("##"));
+    if (ptr != NULL) {
+        int len = wcslen(ptr);
+        if (len > 0 && len < valueSize) {
+            ptr++;
+            ptr++;
+            wcscpy(optionValue, ptr);
+
+            int argLen = wcslen(argValue);
+            if ((argLen - len + 1) < nameSize) {
+                wmemcpy(optionName, argValue, argLen - len); //Does this work correctly for wide chars
+                optionName[argLen - len] = '\0';
+                return TRUE;
+            }
+        }
+    }
+    return FALSE;
+}
+
+#define MAX_KEY_LENGTH  80
+#define MAX_VALUE_LENGTH  8192
+
+TCHAR* convertIdToPath(TCHAR* id) {
+    int len = (wcslen(id) + 1) * sizeof (TCHAR);
+    TCHAR* path = (TCHAR*) calloc(len, sizeof (TCHAR));
+    *path = '\0';
+    TCHAR *returnValue = path;
+
+    TCHAR ch = *id;
+    int index = 0;
+    while (ch != 0) {
+        if (ch == '.') {
+            *path = '\\';
+        } else {
+            *path = ch;
+        }
+        id++;
+        ch = *id;
+        path++;
+    }
+    *path = '\0';
+    return returnValue;
+}
+
+//assume regKey big enough to hold largest key
+
+LONG createRegKey(TCHAR* appid, HKEY *hKey) {
+    TCHAR buf[MAX_PATH] = {0};
+    wcscat(buf, _T("SOFTWARE\\JavaSoft\\Prefs\\"));
+    TCHAR* path = convertIdToPath(appid);
+    wcscat(buf, path);
+    wcscat(buf, _T("\\JVMOptions"));
+    free(path);
+    LONG success = RegOpenKeyEx(HKEY_CURRENT_USER, buf, 0, KEY_READ | KEY_WOW64_64KEY, hKey);
+    return success;
+}
+
+TCHAR* convertKeyToWinReg(TCHAR* key) {
+    TCHAR* windowsName = (TCHAR*) calloc((wcslen(key) + 1)*2, sizeof (TCHAR)); //All caps could double size
+    *windowsName = '\0';
+    TCHAR *returnValue = windowsName;
+
+    TCHAR ch = *key;
+    int index = 0;
+    while (ch != 0) {
+        if (ch == '\\') {
+            *windowsName = '//';
+        } else if (ch == '/') {
+            *windowsName = '\\';
+        } else if ((ch >= 'A') && (ch <= 'Z')) {
+            *windowsName++ = '/';
+            *windowsName = ch;
+        } else {
+            *windowsName = ch;
+        }
+        key++;
+        ch = *key;
+        windowsName++;
+    }
+    *windowsName = '\0';
+    return returnValue;
+}
+
+TCHAR* getJvmUserArg(TCHAR* appid, TCHAR* argvalue) {
+    TCHAR optionName[MAX_PATH] = {0};
+    TCHAR optionValue[MAX_PATH] = {0};
+    HKEY hKey = 0;
+    HKEY vKey = 0;
+    DWORD dwType, dwCount = MAX_VALUE_LENGTH * sizeof (TCHAR);
+    TCHAR userValue[MAX_VALUE_LENGTH] = {0};
+    TCHAR* result = NULL;
+
+    if (splitOptionIntoNameValue(argvalue, optionName, sizeof (optionName), optionValue, sizeof (optionValue))) {
+        LONG success = createRegKey(appid, &hKey);
+        TCHAR* regOptionName = convertKeyToWinReg(optionName);
+        if (success == ERROR_SUCCESS) {
+            success = RegQueryValueEx(hKey, regOptionName, NULL, &dwType, (LPBYTE) userValue, &dwCount);
+            if (success == ERROR_SUCCESS) {
+                wcscat(optionName, userValue);
+                result = wcsdup(optionName);
+            }
+        }
+        free(regOptionName);
+
+        //If not found in registry just combine the two and return it
+        if (result == NULL) {
+            TCHAR* concatenated = (TCHAR*) calloc((wcslen(optionName) + wcslen(optionValue) + 1), sizeof (TCHAR));
+            wcscat(concatenated, optionName);
+            wcscat(concatenated, optionValue);
+            result = concatenated;
+        }
+    }        //This should not occur, but if there is no delimeter just treat as complete option
+    else {
+        result = wcsdup(argvalue);
+    }
+    return result;
+}
+
+
 #define MAX_OPTIONS 100
 #define MAX_OPTION_NAME 50
 
@@ -349,13 +476,14 @@
     size_t outlen = 0;
     jclass cls;
     jmethodID mid;
-	TCHAR argname[MAX_OPTION_NAME + 1] = {0};
-	TCHAR argvalue[LAUNCHER_MAXPATH] = {0},
-          mainclass[LAUNCHER_MAXPATH] = {0};
-	CHAR  argvalueASCII[LAUNCHER_MAXPATH] = {0};
+    TCHAR argname[MAX_OPTION_NAME + 1] = {0};
+    TCHAR argvalue[LAUNCHER_MAXPATH] = {0},
+    mainclass[LAUNCHER_MAXPATH] = {0};
+    CHAR  argvalueASCII[LAUNCHER_MAXPATH] = {0};
     HMODULE msvcrtdll;
     bool runtimeBundled;
-	TCHAR tmpPath[LAUNCHER_MAXPATH] = {0};
+    TCHAR tmpPath[LAUNCHER_MAXPATH] = {0};
+    TCHAR appid[LAUNCHER_MAXPATH] = {0};
 
     memset(&options, 0, sizeof(JavaVMOption)*(MAX_OPTIONS + 1));
     memset(&jvmArgs, 0, sizeof(JavaVMInitArgs));
@@ -451,29 +579,52 @@
        _stprintf_s(argname, MAX_OPTION_NAME, _T("jvmarg.%d"), idx);
        found = getConfigValue(basedir, argname, argvalue, LAUNCHER_MAXPATH);
        if (found) {
-           size_t inLen = (size_t) wcslen(argvalue);
-           //convert argument to ASCII string as this is what CreateJVM needs
-           if (wcstombs_s(&outlen, argvalueASCII, sizeof(argvalueASCII), argvalue, inLen + 1) != 0) {
- 	       showError(_T("Failed converting JVM Argument to ASCII"), argname);
-	       return false;
-	   }
-	   char* jvmOption = _strdup(argvalueASCII);
 
-	   //convert basedir to ASCII string - reuse argvalueASCII
-	   if (wcstombs_s(&outlen, argvalueASCII, sizeof(argvalueASCII), basedir, (size_t)wcslen(basedir) + 1)) {
-               showError(_T("Failed converting directory %s to ASCII", basedir), basedir);
-               return false;
-	   }
-	   //If jvmOption contains $APPDIR it will be replaced with basedir (argvalueASCII)
-	   //otherwise the orginal jvmOption is returned (from strdup above). If string is replaced
-	   //the new version is also from a strdup (i.e. can be passed to free)
-	   jvmOption = replaceStr(jvmOption, "$APPDIR", argvalueASCII);
-	   options[cnt].optionString = jvmOption;
-           idx++;
-           cnt++;
-       }
+            TCHAR* option = replaceStr(argvalue, _T("$APPDIR"), basedir);
+            if (wcstombs_s(&outlen, argvalueASCII, sizeof (argvalueASCII), option, wcslen(option) + 1) != 0) {
+                showError(_T("Failed converting JVM Argument to ASCII"), argname);
+                free(option);
+                return false;
+            }
+            free(option);
+            char* jvmOption = _strdup(argvalueASCII);
+            options[cnt].optionString = jvmOption;
+            idx++;
+            cnt++;
+        }
     } while (found && idx < MAX_OPTIONS);
 
+    found = getConfigValue(basedir, _T("app.id"), appid, LAUNCHER_MAXPATH);
+    if (found) {
+        _stprintf_s(argvalue, _T("-Dapp.id=%s"), appid);
+        wcstombs_s(&outlen, argvalueASCII, sizeof (argvalueASCII),
+                argvalue, wcslen(argvalue) + 1);
+        options[cnt].optionString = _strdup(argvalueASCII);
+        cnt++;
+
+        idx = 1;
+        do {
+            _stprintf_s(argname, MAX_OPTION_NAME, _T("jvmuserarg.%d"), idx);
+            found = getConfigValue(basedir, argname, argvalue, LAUNCHER_MAXPATH);
+            if (found) {
+
+                TCHAR* option = getJvmUserArg(appid, argvalue);
+                if (option != NULL) {
+                    if (wcstombs_s(&outlen, argvalueASCII, sizeof (argvalueASCII), option, wcslen(option) + 1) != 0) {
+                        showError(_T("Failed converting JVM Argument to ASCII"), argname);
+                        free(option);
+                        return false;
+                    }
+                    free(option);
+                    char* jvmOption = _strdup(argvalueASCII);
+                    options[cnt].optionString = jvmOption;
+                    cnt++;
+                }
+                idx++;
+            }
+        } while (found && idx < MAX_OPTIONS);
+    }
+
     jvmArgs.version = 0x00010002;
     jvmArgs.options = options;
     jvmArgs.nOptions = cnt;
--- a/deploy/packager/src/com/sun/javafx/tools/ant/DeployFXTask.java	Wed Jun 12 15:34:39 2013 -0700
+++ b/deploy/packager/src/com/sun/javafx/tools/ant/DeployFXTask.java	Mon Jun 17 15:14:32 2013 -0700
@@ -221,6 +221,9 @@
             for (Jvmarg a: pl.jvmargs) {
                 deployParams.addJvmArg(a.value);
             }
+            for (Property a: pl.jvmUserArgs) {
+                deployParams.addJvmUserArg(a.name, a.value);
+            }
         }
 
         if (callbacks != null) {
--- a/deploy/packager/src/com/sun/javafx/tools/ant/Platform.java	Wed Jun 12 15:34:39 2013 -0700
+++ b/deploy/packager/src/com/sun/javafx/tools/ant/Platform.java	Mon Jun 17 15:14:32 2013 -0700
@@ -110,6 +110,7 @@
     String javafx = null;
     List<Property> properties = new LinkedList<Property>();
     List<Jvmarg> jvmargs = new LinkedList<Jvmarg>();
+    List<Property> jvmUserArgs = new LinkedList<Property>();
 
     /**
      * Minimum version of JRE required by application.
@@ -157,6 +158,12 @@
         return t;
     }
 
+    public Property createJVMUserArg() {
+        Property t = new Property();
+        jvmUserArgs.add(t);
+        return t;
+    }
+
     //real object could be available by link
     public Platform get() {
         if (isReference()) {
--- a/deploy/packager/src/com/sun/javafx/tools/packager/DeployParams.java	Wed Jun 12 15:34:39 2013 -0700
+++ b/deploy/packager/src/com/sun/javafx/tools/packager/DeployParams.java	Mon Jun 17 15:14:32 2013 -0700
@@ -107,6 +107,7 @@
 
     //list of jvm args (in theory string can contain spaces and need to be escaped
     List<String> jvmargs = new LinkedList<String>();
+    Map<String, String> jvmUserArgs = new HashMap<String, String>();
 
     //list of jvm properties (can also be passed as VM args
     // but keeping them separate make it a bit more convinient for JNLP generation)
@@ -196,6 +197,10 @@
         jvmargs.add(v);
     }
 
+    public void addJvmUserArg(String n, String v) {
+        jvmUserArgs.put(n, v);
+    }
+
     public void addJvmProperty(String n, String v) {
         properties.put(n, v);
     }
@@ -283,7 +288,7 @@
     public void setJSCallbacks(List<JSCallback> list) {
         callbacks = list;
     }
-    
+
     public void setCallbacks(List<Callback> list) {
         List<JSCallback> jslist = new ArrayList<JSCallback>(list.size());
         for (Callback cb: list) {
@@ -489,6 +494,7 @@
 
             bundleParams.setJvmProperties(properties);
             bundleParams.setJvmargs(jvmargs);
+            bundleParams.setJvmUserArgs(jvmUserArgs);
 
             File appIcon = null;
             for (Icon ic: icons) {
--- a/deploy/packager/src/com/sun/javafx/tools/packager/bundlers/BundleParams.java	Wed Jun 12 15:34:39 2013 -0700
+++ b/deploy/packager/src/com/sun/javafx/tools/packager/bundlers/BundleParams.java	Mon Jun 17 15:14:32 2013 -0700
@@ -92,6 +92,9 @@
 
     //list of jvm args (in theory string can contain spaces and need to be escaped
     List<String> jvmargs = new LinkedList<String>();
+    //list of jvm args (in theory string can contain spaces and need to be escaped
+    Map<String, String> jvmUserArgs = new HashMap<String, String>();
+
     //list of jvm properties (can also be passed as VM args
     Map<String, String> jvmProperties = new HashMap<String, String>();
 
@@ -103,6 +106,10 @@
         this.jvmargs = jvmargs;
     }
 
+    public void setJvmUserArgs(Map<String, String> userArgs) {
+        this.jvmUserArgs = userArgs;
+    }
+
     public void setJvmProperties(Map<String, String> jvmProperties) {
         this.jvmProperties = jvmProperties;
     }
@@ -116,6 +123,27 @@
         return all;
     }
 
+    public Map<String, String> getAllJvmUserOptions() {
+        Map<String, String> all = new HashMap<String, String>();
+        all.putAll(jvmUserArgs);
+        return all;
+    }
+
+    public String getApplicationID() {
+        String id = "";
+        if (identifier != null) {
+            id = identifier;
+        }
+        else {
+            id = applicationClass;
+        }
+        return id;
+    }
+
+    public String getPreferencesID() {
+        return getApplicationID().replaceAll("\\.", "/");
+    }
+
     public void setTitle(String title) {
         this.title = title;
     }
--- a/deploy/packager/src/com/sun/javafx/tools/packager/bundlers/LinuxAppBundler.java	Wed Jun 12 15:34:39 2013 -0700
+++ b/deploy/packager/src/com/sun/javafx/tools/packager/bundlers/LinuxAppBundler.java	Mon Jun 17 15:14:32 2013 -0700
@@ -34,6 +34,7 @@
 import java.io.IOException;
 import java.io.PrintStream;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 public class LinuxAppBundler extends Bundler {
@@ -192,7 +193,19 @@
             out.println("jvmarg."+idx+"="+a);
             idx++;
         }
-        out.close();
+
+        //app.id required for setting user preferences (Java Preferences API)
+        out.println("app.preferences.id="+ params.getPreferencesID());
+
+        Map<String, String> jvmUserOptions = params.getAllJvmUserOptions();
+        //Only create jvmuserargs, if app.id (params.identifier) is set, otherwise we don't know where
+        //to create the jvm.cfg file - warning logged above
+        idx = 1;
+        for (Map.Entry<String, String> arg: jvmUserOptions.entrySet()) {
+            out.println("jvmuserarg."+idx+"="+arg.getKey()+"##"+arg.getValue());
+            idx++;
+        }
+       out.close();
     }
 
     private void copyRuntime(File runtimeDirectory) throws IOException {
--- a/deploy/packager/src/com/sun/javafx/tools/packager/bundlers/MacAppBundler.java	Wed Jun 12 15:34:39 2013 -0700
+++ b/deploy/packager/src/com/sun/javafx/tools/packager/bundlers/MacAppBundler.java	Mon Jun 17 15:14:32 2013 -0700
@@ -273,11 +273,7 @@
 
     private String getBundleIdentifier() {
         //TODO: Check to see what rules/limits are in place for CFBundleIdentifier
-        if (params.identifier != null) {
-            return params.identifier;
-        } else {
-            return "unknown."+params.applicationClass;
-        }
+        return params.getApplicationID();
     }
 
     private void writeInfoPlist(File file) throws IOException {
@@ -288,7 +284,7 @@
         Map<String, String> data = new HashMap<String, String>();
         data.put("DEPLOY_ICON_FILE", getConfig_Icon().getName());
         data.put("DEPLOY_BUNDLE_IDENTIFIER",
-                getBundleIdentifier());
+                getBundleIdentifier().toLowerCase());
         data.put("DEPLOY_BUNDLE_NAME",
                 getBundleName());
         data.put("DEPLOY_BUNDLE_COPYRIGHT",
@@ -306,14 +302,30 @@
                 params.applicationCategory != null ?
                    params.applicationCategory : "unknown");
         data.put("DEPLOY_MAIN_JAR_NAME", params.getMainApplicationJar());
+        data.put("DEPLOY_PREFERENCES_ID", params.getPreferencesID().toLowerCase());
 
         StringBuilder sb = new StringBuilder();
         List<String> jvmOptions = params.getAllJvmOptions();
+
+        String newline = ""; //So we don't add unneccessary extra line after last append
         for (String o : jvmOptions) {
-            sb.append("    <string>").append(o).append("</string>\n");
+            sb.append(newline).append("    <string>").append(o).append("</string>");
+            newline = "\n";
         }
         data.put("DEPLOY_JVM_OPTIONS", sb.toString());
 
+        newline = "";
+        sb = new StringBuilder();
+        Map<String, String> overridableJVMOptions = params.getAllJvmUserOptions();
+        for (Map.Entry<String, String> arg: overridableJVMOptions.entrySet()) {
+            sb.append(newline);
+            sb.append("      <key>").append(arg.getKey()).append("</key>\n");
+            sb.append("      <string>").append(arg.getValue()).append("</string>");
+            newline = "\n";
+        }
+        data.put("DEPLOY_JVM_USER_OPTIONS", sb.toString());
+
+
         if (params.useJavaFXPackaging()) {
             data.put("DEPLOY_LAUNCHER_CLASS", JAVAFX_LAUNCHER_CLASS);
         } else {
--- a/deploy/packager/src/com/sun/javafx/tools/packager/bundlers/WinAppBundler.java	Wed Jun 12 15:34:39 2013 -0700
+++ b/deploy/packager/src/com/sun/javafx/tools/packager/bundlers/WinAppBundler.java	Mon Jun 17 15:14:32 2013 -0700
@@ -32,7 +32,9 @@
 import java.io.IOException;
 import java.io.PrintStream;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
+import java.util.prefs.Preferences;
 
 public class WinAppBundler extends Bundler {
     private BundleParams params;
@@ -249,6 +251,7 @@
         out.println("app.version=" + params.appVersion);
         //for future AU support (to be able to find app in the registry)
         out.println("app.id="+params.identifier);
+        out.println("app.preferences.id="+params.getPreferencesID());
 
         if (params.useJavaFXPackaging()) {
             out.println("app.mainclass=" +
@@ -266,6 +269,13 @@
             out.println("jvmarg."+idx+"="+a);
             idx++;
         }
+
+        Map<String, String> overridableJVMOptions = params.getAllJvmUserOptions();
+        idx = 1;
+        for (Map.Entry<String, String> arg: overridableJVMOptions.entrySet()) {
+            out.println("jvmuserarg."+idx+"="+arg.getKey()+"##"+arg.getValue());
+            idx++;
+        }
         out.close();
     }
 
--- a/deploy/packager/src/com/sun/javafx/tools/resource/mac/Info.plist.template	Wed Jun 12 15:34:39 2013 -0700
+++ b/deploy/packager/src/com/sun/javafx/tools/resource/mac/Info.plist.template	Mon Jun 17 15:14:32 2013 -0700
@@ -38,10 +38,16 @@
   <string>DEPLOY_APP_CLASSPATH</string>
   <key>JVMMainJarName</key>
   <string>DEPLOY_MAIN_JAR_NAME</string>
+  <key>JVMPreferencesID</key>
+  <string>DEPLOY_PREFERENCES_ID</string>
   <key>JVMOptions</key>
   <array>
 DEPLOY_JVM_OPTIONS
   </array>
+  <key>JVMUserOptions</key>
+    <dict>
+DEPLOY_JVM_USER_OPTIONS
+    </dict>
   <key>NSHighResolutionCapable</key>
   <string>true</string>
  </dict>