--- old/modules/fxpackager/src/main/native/launcher/mac/main.m 2014-07-03 11:20:23.000000000 -0600 +++ new/modules/fxpackager/src/main/native/launcher/mac/main.m 2014-07-03 11:20:22.000000000 -0600 @@ -50,6 +50,7 @@ #define JVM_CLASSPATH_KEY "JVMAppClasspath" #define JVM_PREFERENCES_ID "JVMPreferencesID" #define JVM_LAUNCHER_DEBUG_OPTIONS_KEY "JVMLauncherDebugOptions" +#define DEFAULT_JAVA_PREFS_DOMAIN "com.apple.java.util.prefs" #define LIBJLI_DYLIB "/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home/lib/jli/libjli.dylib" @@ -104,7 +105,7 @@ // Locate the JLI_Launch() function NSString *runtime = [infoDictionary objectForKey:@JVM_RUNTIME_KEY]; - JLI_Launch_t jli_LaunchFxnPtr; + JLI_Launch_t jli_LaunchFxnPtr = NULL; if ([runtime length] != 0) { //missing key or empty value NSString *runtimePath = [[[NSBundle mainBundle] builtInPlugInsPath] stringByAppendingPathComponent:runtime]; NSString *libjliPath = [runtimePath stringByAppendingPathComponent:@"Contents/Home/jre/lib/jli/libjli.dylib"]; @@ -223,6 +224,7 @@ } } } + [options release]; argv[i] = NULL; @@ -339,17 +341,75 @@ } - //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]; - + // calculate a normalized path including the JVMUserOptions key + BOOL leadingSlash = [preferencesID hasPrefix: @"/"]; + BOOL trailingSlash = [preferencesID hasSuffix: @"/"] || ([preferencesID length] == 0); + + NSString *fullPath = [NSString stringWithFormat:@"%@%@%@%@/", leadingSlash?@"":@"/", + preferencesID, trailingSlash?@"":@"/", @JVM_USER_OPTIONS_KEY]; + + // now pull out the parts... + NSString *strippedPath = [fullPath stringByTrimmingCharactersInSet: [NSCharacterSet + characterSetWithCharactersInString:@"/"]]; + NSArray *pathParts = [strippedPath componentsSeparatedByString:@"/"]; + + // calculate our persistent domain and the path of dictionaries to descend + NSString *persistentDomain; + NSMutableArray *dictPath = [NSMutableArray arrayWithArray: pathParts]; + if ([pathParts count] > 2) { + // for 3 or more steps, the domain is first.second.third and the keys are "/first/second/third/", "fourth/", "fifth/"... etc + persistentDomain = [[NSString stringWithFormat: @"%@.%@.%@", [pathParts objectAtIndex: 0], + [pathParts objectAtIndex: 1], [pathParts objectAtIndex: 2]] lowercaseString]; + + [dictPath replaceObjectAtIndex: 0 withObject: [NSString stringWithFormat:@"/%@/%@/%@", [pathParts objectAtIndex: 0], + [pathParts objectAtIndex: 1], [pathParts objectAtIndex: 2]]]; + [dictPath removeObjectAtIndex: 2]; + [dictPath removeObjectAtIndex: 1]; + } else { + // for 1 or two steps, the domain is first.second.third and the keys are "/", "first/", "second/" + persistentDomain = @DEFAULT_JAVA_PREFS_DOMAIN; + [dictPath insertObject: @"" atIndex:0]; + } +NSLog(@" persistent domain - ", persistentDomain); + + // set up the user defaults for the appropriate persistent domain + NSUserDefaults *userDefaults = [[NSUserDefaults alloc] init]; + NSDictionary *userOverrides = [userDefaults persistentDomainForName: persistentDomain]; + + // walk down our path parts, making dictionaries along the way if they are missing + NSMutableDictionary *parentNode = NULL; + int pathLength = [dictPath count]; + for (int i = 0; i < pathLength; i++) { + NSString *nodeKey = [[dictPath objectAtIndex: i] stringByAppendingString: @"/"]; +NSLog(@" Stepping into %@", nodeKey); + parentNode = userOverrides; + userOverrides = [parentNode objectForKey: nodeKey]; + if (userOverrides == Nil) { + userOverrides = [NSMutableDictionary dictionaryWithCapacity: 2]; + [parentNode setValue: userOverrides forKey: nodeKey]; + } + } + //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]; +NSLog(@"Empty Overrides"); + if (parentNode == NULL) { +NSLog(@"Empty Parent"); + [userDefaults setPersistentDomain: defaultOverrides forName: persistentDomain]; + userOverrides = [userDefaults persistentDomainForName: persistentDomain]; + } else { + NSString *nodeKey = [[dictPath objectAtIndex: ([dictPath count] - 1)] stringByAppendingString: @"/"]; + [parentNode setObject: defaultOverrides forKey:nodeKey]; + userOverrides = [parentNode objectForKey: nodeKey]; + } } - + + // some writes may have occured, sync + [userDefaults synchronize]; + [userDefaults release]; + + // now we examine the prefs node for defaulted values and substitute user options for (id key in defaultOverrides) { - NSString *newOption; if ([userOverrides valueForKey: key] != nil && [[userOverrides valueForKey:key] isNotEqualTo:[defaultOverrides valueForKey:key]]) { --- old/modules/fxpackager/src/test/java/com/oracle/tools/packager/mac/MacAppBundlerTest.java 2014-07-03 11:20:24.000000000 -0600 +++ new/modules/fxpackager/src/test/java/com/oracle/tools/packager/mac/MacAppBundlerTest.java 2014-07-03 11:20:23.000000000 -0600 @@ -37,6 +37,7 @@ import org.junit.Assume; import org.junit.Before; import org.junit.BeforeClass; +import org.junit.Ignore; import org.junit.Test; import java.io.File; @@ -309,4 +310,40 @@ assertNotNull(result); assertTrue(result.exists()); } + + @Test + public void jvmUserOptionsTest() throws IOException, ConfigException, UnsupportedPlatformException { + + for (String name : Arrays.asList("", "example", "com.example", "com.example.helloworld", "com.example.hello.world", "com.example.hello.world.app")) { + + AbstractBundler bundler = new MacAppBundler(); + + Map bundleParams = new HashMap<>(); + + bundleParams.put(BUILD_ROOT.getID(), tmpBase); + + bundleParams.put(APP_NAME.getID(), "User JVM Options App - " + name); + bundleParams.put(MAC_CF_BUNDLE_NAME.getID(), name + ".application"); + bundleParams.put(MAIN_CLASS.getID(), "hello.TestPackager"); + bundleParams.put(IDENTIFIER.getID(), name); + bundleParams.put(PREFERENCES_ID.getID(), name.replace(".", "/")); + bundleParams.put(MAIN_JAR.getID(), + new RelativeFileSet(fakeMainJar.getParentFile(), + new HashSet<>(Arrays.asList(fakeMainJar))) + ); + bundleParams.put(CLASSPATH.getID(), fakeMainJar.toString()); + bundleParams.put(APP_RESOURCES.getID(), new RelativeFileSet(appResourcesDir, appResources)); + bundleParams.put(VERBOSE.getID(), true); + bundleParams.put(DEVELOPER_ID_APP_SIGNING_KEY.getID(), null); // force no signing + + boolean valid = bundler.validate(bundleParams); + assertTrue(valid); + + File result = bundler.execute(bundleParams, new File(workDir, "UserOpts-" + name.replace(".", "-"))); + System.err.println("Bundle at - " + result); + assertNotNull(result); + assertTrue(result.exists()); + } + } + }