1 /*
   2  * Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 #import "JVMArgs.h"
  27 
  28 
  29 #define kArgsFailure "JVMArgsFailure"
  30 
  31 NSString *kArgumentsKey = @"Arguments";
  32 
  33 NSString *kClassPathKey = @"ClassPath";
  34 #ifdef __i386__
  35 NSString *kArchClassPathKey = @"ClassPath.i386";
  36 #elif __x86_64__
  37 NSString *kArchClassPathKey = @"ClassPath.x86_64";
  38 #endif
  39 
  40 NSString *kVMOptionsKey = @"VMOptions";
  41 #ifdef __i386__
  42 NSString *kArchVMOptionsKey = @"VMOptions.i386";
  43 #elif __x86_64__
  44 NSString *kArchVMOptionsKey = @"VMOptions.x86_64";
  45 #endif
  46 
  47 
  48 @implementation JVMArgs
  49 
  50 @synthesize jreBundle;
  51 @synthesize preferredJVMLib;
  52 @synthesize vm_args;
  53 @synthesize startOnFirstThread;
  54 @synthesize debug;
  55 
  56 @synthesize appInfo;
  57 @synthesize jvmInfo;
  58 
  59 @synthesize userHome;
  60 @synthesize appPackage;
  61 @synthesize javaRoot;
  62 
  63 - (void) dealloc {
  64     self.jreBundle = nil;
  65     if (self.preferredJVMLib) free(self.preferredJVMLib);
  66 
  67     self.appInfo = nil;
  68     self.jvmInfo = nil;
  69 
  70     self.userHome = nil;
  71     self.appPackage = nil;
  72     self.javaRoot = nil;
  73 
  74     [super dealloc];
  75 }
  76 
  77 
  78 NSString *GetJavaRoot(NSDictionary *jvmInfoDict) {
  79     NSObject *javaRoot = [jvmInfoDict objectForKey:@"$JAVAROOT"];
  80     if (![javaRoot isKindOfClass:[NSString class]]) return @"$APP_PACKAGE/Contents/Java";
  81     return (NSString *)javaRoot;
  82 }
  83 
  84 // Replaces occurances of $JAVAROOT, $APP_PACKAGE, and $USER_HOME
  85 - (NSString *) expandMacros:(NSString *)str {
  86     if ([str rangeOfString:@"$JAVAROOT"].length == 0 && [str rangeOfString:@"$APP_PACKAGE"].length == 0 && [str rangeOfString:@"$USER_HOME"].length == 0) return str;
  87 
  88     // expand $JAVAROOT first, because it can contain $APP_PACKAGE
  89     NSMutableString *mutable = [str mutableCopy];
  90     [mutable replaceOccurrencesOfString:@"$JAVAROOT" withString:javaRoot options:0 range:NSMakeRange(0, [str length])];
  91     [mutable replaceOccurrencesOfString:@"$APP_PACKAGE" withString:appPackage options:0 range:NSMakeRange(0, [str length])];
  92     [mutable replaceOccurrencesOfString:@"$USER_HOME" withString:userHome options:0 range:NSMakeRange(0, [str length])];
  93     return mutable;
  94 }
  95 
  96 - (NSArray *) arrayFrom:(id) obj delimitedBy:(NSString *)delimiter withErrKey:(NSString *)key {
  97     if (obj == nil) return nil;
  98     if ([obj isKindOfClass:[NSArray class]]) return obj;
  99     if (![obj isKindOfClass:[NSString class]]) {
 100         [NSException raise:@kArgsFailure format:@"%@", [NSString stringWithFormat:@"Failed to find '%@' array in JVMInfo Info.plist"]];
 101     }
 102 
 103     // split
 104     return [(NSString *)obj componentsSeparatedByString:delimiter];
 105 }
 106 
 107 - (void) buildArgsForBundle:(NSBundle *)appBundle argc:(int)argc argv:(char *[])argv {
 108     // for verbose logging
 109     self.debug = NULL != getenv("JAVA_LAUNCHER_VERBOSE");
 110 
 111     self.appInfo = [appBundle infoDictionary];
 112 
 113     // all apps must have a JVMInfo dictionary inside their Info.plist
 114     self.jvmInfo = [[self.appInfo objectForKey:@"JVMInfo"] mutableCopy];
 115     if (![jvmInfo isKindOfClass:[NSDictionary class]]) {
 116         [NSException raise:@kArgsFailure format:@"Failed to find 'JVMInfo' dictionary in Info.plist"];
 117     }
 118 
 119     // initialize macro expansion values
 120     self.userHome = NSHomeDirectory();
 121     self.appPackage = [appBundle bundlePath];
 122     self.javaRoot = GetJavaRoot(jvmInfo);
 123     self.javaRoot = [self expandMacros:self.javaRoot]; // dereference $APP_PACKAGE
 124 
 125     // if the 'Arguments' key is defined, those override the ones that came into main()
 126     NSArray *jvmInfoArgs = [jvmInfo valueForKey:kArgumentsKey];
 127     if (jvmInfoArgs != nil) {
 128         // substitute all the variables in the 'Arguments' array/string
 129         jvmInfoArgs = [self arrayFrom:jvmInfoArgs delimitedBy:@" " withErrKey:kArgumentsKey];
 130         NSMutableArray *arguments = [NSMutableArray arrayWithCapacity:[jvmInfoArgs count]];
 131         [jvmInfoArgs enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
 132             [arguments replaceObjectAtIndex:idx withObject:[self expandMacros:[obj description]]];
 133         }];
 134         [jvmInfo setObject:arguments forKey:kArgumentsKey];
 135     } else if (argc != 0) {
 136         // put the (macro expanded) args to main() in an NSArray
 137         NSMutableArray *arguments = [NSMutableArray arrayWithCapacity:argc];
 138         for (int i = 0; i < argc; i++) {
 139             [arguments addObject:[self expandMacros:[NSString stringWithUTF8String:(argv[i])]]];
 140         }
 141         [jvmInfo setObject:arguments forKey:kArgumentsKey];
 142     }
 143 
 144     // all JVMInfo's must have a JRE or JDK key
 145     NSString *jreBundleName = [jvmInfo objectForKey:@"JRE"];
 146     if (!jreBundleName) jreBundleName = [jvmInfo objectForKey:@"JDK"];
 147     if (![jreBundleName isKindOfClass:[NSString class]]) {
 148         [NSException raise:@kArgsFailure format:@"Failed to find 'JRE' or 'JDK' string in Info.plist JVMInfo"];
 149     }
 150 
 151     // the JRE/JDK must be loadable from the ($APP_PACKAGE)/Contents/PlugIns/ directory
 152     NSURL *jreBundleURL = [[appBundle builtInPlugInsURL] URLByAppendingPathComponent:jreBundleName];
 153     self.jreBundle = [NSBundle bundleWithURL:jreBundleURL];
 154     if (!self.jreBundle) {
 155         [NSException raise:@kArgsFailure format:@"Failed to find JRE/JDK at: %@", jreBundleURL];
 156     }
 157 
 158     // if the app prefers 'client' or 'server', use the JVM key
 159     NSString *JVMLib = [jvmInfo objectForKey:@"JVM"];
 160     if (JVMLib != nil) self.preferredJVMLib = strdup([JVMLib UTF8String]);
 161 
 162     // sniff for StartOnFirstThread
 163     if ([[jvmInfo objectForKey:@"StartOnFirstThread"] boolValue]) {
 164         self.startOnFirstThread = YES;
 165     } else if ([[jvmInfo objectForKey:@"StartOnMainThread"] boolValue]) {
 166         // for key compatibility with the Apple JavaApplicationStub's 'Java' dictionary
 167         self.startOnFirstThread = YES;
 168     }
 169 
 170     // add $JAVAROOT directory to the JNI library search path
 171     setenv("JAVA_LIBRARY_PATH", [javaRoot UTF8String], 1);
 172 
 173     // 'WorkingDirectory' key changes current working directory
 174     NSString *javaWorkingDir = [jvmInfo objectForKey:@"WorkingDirectory"];
 175     if (javaWorkingDir == nil) javaWorkingDir = @"$APP_PACKAGE/..";
 176     javaWorkingDir = [self expandMacros:javaWorkingDir];
 177     if (chdir([javaWorkingDir UTF8String]) == -1) {
 178         NSLog(@kArgsFailure " chdir() failed, could not change the current working directory to %s\n", [javaWorkingDir UTF8String]);
 179     }
 180 
 181     NSMutableArray *classpath = [NSMutableArray array];
 182 
 183     // 'Jar' key sets exactly one classpath entry
 184     NSString *jarFile = [jvmInfo objectForKey:@"Jar"];
 185     if (jarFile != nil) {
 186         [jvmInfo setObject:[self expandMacros:jarFile] forKey:@"Jar"];
 187         [classpath addObject:jarFile];
 188     }
 189 
 190     // 'ClassPath' key allows arbitrary classpath
 191     [classpath addObjectsFromArray:[self arrayFrom:[jvmInfo objectForKey:kClassPathKey] delimitedBy:@":" withErrKey:kClassPathKey]];
 192     [classpath addObjectsFromArray:[self arrayFrom:[jvmInfo objectForKey:kArchClassPathKey] delimitedBy:@":" withErrKey:kArchClassPathKey]];
 193 
 194     // Sum up all the classpath entries into one big JVM arg
 195     NSMutableString *classpathOption = [NSMutableString stringWithString:@"-Djava.class.path="];
 196     [classpath enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
 197         if (idx > 1) [classpathOption appendString:@":"];
 198         [classpathOption appendString:obj];
 199     }];
 200 
 201     NSMutableArray *jvmOptions = [NSMutableArray arrayWithObject:classpathOption];
 202 
 203     // 'VMOptions' key allows arbitary VM start up options
 204     [jvmOptions addObjectsFromArray:[self arrayFrom:[jvmInfo objectForKey:kVMOptionsKey] delimitedBy:@" " withErrKey:kVMOptionsKey]];
 205     [jvmOptions addObjectsFromArray:[self arrayFrom:[jvmInfo objectForKey:kArchVMOptionsKey] delimitedBy:@" " withErrKey:kArchVMOptionsKey]];
 206 
 207     // 'Properties' key is a sub-dictionary transfered to initial System.properties
 208     NSDictionary *properties = [jvmInfo objectForKey:@"Properties"];
 209     if (properties != nil) {
 210         if (![properties isKindOfClass:[NSDictionary class]]) {
 211             [NSException raise:@kArgsFailure format:@"Failed to find 'Properties' dictionary in Info.plist JVMInfo"];
 212         }
 213 
 214         [properties enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
 215             [jvmOptions addObject:[NSString stringWithFormat:@"-D%@=%@", key, obj]];
 216         }];
 217     }
 218 
 219     // build the real JVM init args struct
 220     vm_args.version = JNI_VERSION_1_6;
 221     vm_args.ignoreUnrecognized = JNI_TRUE;
 222     vm_args.nOptions = [jvmOptions count];
 223     vm_args.options = calloc(vm_args.nOptions, sizeof(JavaVMOption));
 224     [jvmOptions enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
 225         NSString *expanded = [self expandMacros:[obj description]]; // turn everything into a string, and expand macros
 226         vm_args.options[idx].optionString = strdup([expanded UTF8String]);
 227     }];
 228 }
 229 
 230 + (JVMArgs *)jvmArgsForBundle:(NSBundle *)appBundle argc:(int)argc argv:(char *[])argv {
 231     JVMArgs *args = [JVMArgs new];
 232     [args buildArgsForBundle:appBundle argc:argc argv:argv];
 233     return [args autorelease];
 234 }
 235 
 236 @end