| // |
| // Created by max on 5/4/12. |
| // |
| // To change the template use AppCode | Preferences | File Templates. |
| // |
| |
| |
| #import "Launcher.h" |
| #import "VMOptionsReader.h" |
| #import "PropertyFileReader.h" |
| #import "utils.h" |
| #import <dlfcn.h> |
| |
| typedef jint (JNICALL *fun_ptr_t_CreateJavaVM)(JavaVM **pvm, void **env, void *args); |
| |
| |
| static NSString *const JVMOptions = @"JVMOptions"; |
| |
| @interface NSString (CustomReplacements) |
| - (NSString *)replaceAll:(NSString *)pattern to:(NSString *)replacement; |
| |
| @end |
| |
| @implementation NSString (CustomReplacements) |
| - (NSString *)replaceAll:(NSString *)pattern to:(NSString *)replacement { |
| if ([self rangeOfString:pattern].length == 0) return self; |
| |
| NSMutableString *answer = [[self mutableCopy] autorelease]; |
| [answer replaceOccurrencesOfString:pattern withString:replacement options:0 range:NSMakeRange(0, [self length])]; |
| return answer; |
| } |
| @end |
| |
| @interface NSDictionary (TypedGetters) |
| - (NSDictionary *)dictionaryForKey:(id)key; |
| - (id)valueForKey:(NSString *)key inDictionary:(NSString *)dictKey defaultObject:(NSString *)defaultValue; |
| @end |
| |
| @implementation NSDictionary (TypedGetters) |
| - (NSDictionary *)dictionaryForKey:(id)key { |
| id answer = [self objectForKey:key]; |
| if ([answer isKindOfClass:[NSDictionary class]]) { |
| return answer; |
| } |
| return nil; |
| } |
| |
| - (id)valueForKey:(NSString *)key inDictionary:(NSString *)dictKey defaultObject: (NSString*) defaultValue { |
| NSDictionary *dict = [self dictionaryForKey:dictKey]; |
| if (dict == nil) return nil; |
| id answer = [dict valueForKey:key]; |
| return answer != nil ? answer : defaultValue; |
| } |
| @end |
| |
| @implementation Launcher |
| |
| - (id)initWithArgc:(int)anArgc argv:(char **)anArgv { |
| self = [super init]; |
| if (self) { |
| argc = anArgc; |
| argv = anArgv; |
| } |
| |
| return self; |
| } |
| |
| |
| void appendBundle(NSString *path, NSMutableArray *sink) { |
| if ([path hasSuffix:@".jdk"] || [path hasSuffix:@".jre"]) { |
| NSBundle *bundle = [NSBundle bundleWithPath:path]; |
| if (bundle != nil) { |
| [sink addObject:bundle]; |
| } |
| } |
| } |
| |
| void appendJvmBundlesAt(NSString *path, NSMutableArray *sink) { |
| NSError *error = nil; |
| NSArray *names = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:&error]; |
| |
| if (names != nil) { |
| for (NSString *name in names) { |
| appendBundle([path stringByAppendingPathComponent:name], sink); |
| } |
| } |
| } |
| |
| NSArray *allVms() { |
| NSMutableArray *jvmBundlePaths = [NSMutableArray array]; |
| |
| NSString *explicit = [[[NSProcessInfo processInfo] environment] objectForKey:@"IDEA_JDK"]; |
| |
| if (explicit != nil) { |
| // check if IDEA_JDK value corresponds with JVMVersion from Info.plist |
| NSLog(@"value of IDEA_JDK: %@", explicit); |
| NSBundle *jdkBundle = [NSBundle bundleWithPath:explicit]; |
| NSString *required = requiredJvmVersion(); |
| if (jdkBundle != nil && required != NULL) { |
| if (satisfies(jvmVersion(jdkBundle), required)) { |
| appendBundle(explicit, jvmBundlePaths); |
| debugLog(@"User VM:"); |
| debugLog([jdkBundle bundlePath]); |
| } |
| } |
| } |
| if (! jvmBundlePaths.count > 0 ) { |
| NSBundle *bundle = [NSBundle mainBundle]; |
| NSString *appDir = [bundle.bundlePath stringByAppendingPathComponent:@"Contents"]; |
| |
| appendJvmBundlesAt([appDir stringByAppendingPathComponent:@"/jre"], jvmBundlePaths); |
| if (jvmBundlePaths.count > 0) return jvmBundlePaths; |
| |
| appendJvmBundlesAt([NSHomeDirectory() stringByAppendingPathComponent:@"Library/Java/JavaVirtualMachines"], jvmBundlePaths); |
| appendJvmBundlesAt(@"/Library/Java/JavaVirtualMachines", jvmBundlePaths); |
| appendJvmBundlesAt(@"/System/Library/Java/JavaVirtualMachines", jvmBundlePaths); |
| } |
| |
| return jvmBundlePaths; |
| } |
| |
| NSString *jvmVersion(NSBundle *bundle) { |
| return [bundle.infoDictionary valueForKey:@"JVMVersion" inDictionary:@"JavaVM" defaultObject:@"0"]; |
| } |
| |
| NSString *requiredJvmVersion() { |
| return [[NSBundle mainBundle].infoDictionary valueForKey:@"JVMVersion" inDictionary: JVMOptions defaultObject:@"1.7*"]; |
| } |
| |
| BOOL satisfies(NSString *vmVersion, NSString *requiredVersion) { |
| if ([requiredVersion hasSuffix:@"+"]) { |
| requiredVersion = [requiredVersion substringToIndex:[requiredVersion length] - 1]; |
| return [requiredVersion compare:vmVersion options:NSNumericSearch] <= 0; |
| } |
| |
| if ([requiredVersion hasSuffix:@"*"]) { |
| requiredVersion = [requiredVersion substringToIndex:[requiredVersion length] - 1]; |
| } |
| |
| return [vmVersion hasPrefix:requiredVersion]; |
| } |
| |
| NSComparisonResult compareVMVersions(id vm1, id vm2, void *context) { |
| return [jvmVersion(vm2) compare:jvmVersion(vm1) options:NSNumericSearch]; |
| } |
| |
| NSBundle *findMatchingVm() { |
| NSArray *vmBundles = [allVms() sortedArrayUsingFunction:compareVMVersions context:NULL]; |
| |
| if (isDebugEnabled()) { |
| debugLog(@"Found Java Virtual Machines:"); |
| for (NSBundle *vm in vmBundles) { |
| debugLog([vm bundlePath]); |
| } |
| } |
| |
| NSString *required = requiredJvmVersion(); |
| debugLog([NSString stringWithFormat:@"Required VM: %@", required]); |
| |
| if (required != nil && required != NULL) { |
| for (NSBundle *vm in vmBundles) { |
| if (satisfies(jvmVersion(vm), required)) { |
| debugLog(@"Chosen VM:"); |
| debugLog([vm bundlePath]); |
| return vm; |
| } |
| } |
| } else { |
| NSLog(@"Info.plist is corrupted, Absent JVMOptios key."); |
| exit(-1); |
| } |
| NSLog(@"No matching VM found."); |
| return nil; |
| } |
| |
| CFBundleRef NSBundle2CFBundle(NSBundle *bundle) { |
| CFURLRef bundleURL = (CFURLRef) ([NSURL fileURLWithPath:bundle.bundlePath]); |
| return CFBundleCreate(kCFAllocatorDefault, bundleURL); |
| } |
| |
| - (NSString *)expandMacros:(NSString *)str { |
| return [[str |
| replaceAll:@"$APP_PACKAGE" to:[[NSBundle mainBundle] bundlePath]] |
| replaceAll:@"$USER_HOME" to:NSHomeDirectory()]; |
| } |
| |
| - (NSMutableString *)buildClasspath:(NSBundle *)jvm { |
| NSDictionary *jvmInfo = [[NSBundle mainBundle] objectForInfoDictionaryKey:JVMOptions]; |
| NSMutableString *classpathOption = [NSMutableString stringWithString:@"-Djava.class.path="]; |
| NSString *classPath = [jvmInfo objectForKey:@"ClassPath"]; |
| if (classPath != nil && classPath != NULL) { |
| [classpathOption appendString:[jvmInfo objectForKey:@"ClassPath"]]; |
| NSString *toolsJar = [[jvm bundlePath] stringByAppendingString:@"/Contents/Home/lib/tools.jar"]; |
| if ([[NSFileManager defaultManager] fileExistsAtPath:toolsJar]) { |
| [classpathOption appendString:@":"]; |
| [classpathOption appendString:toolsJar]; |
| } |
| |
| } else { |
| NSLog(@"Info.plist is corrupted, Absent ClassPath key."); |
| exit(-1); |
| } |
| |
| return classpathOption; |
| } |
| |
| |
| NSString *getSelector() { |
| NSDictionary *jvmInfo = [[NSBundle mainBundle] objectForInfoDictionaryKey:JVMOptions]; |
| NSDictionary *properties = [jvmInfo dictionaryForKey:@"Properties"]; |
| if (properties != nil) { |
| return [properties objectForKey:@"idea.paths.selector"]; |
| } |
| return nil; |
| } |
| |
| NSString *getPreferencesFolderPath() { |
| return [NSString stringWithFormat:@"%@/Library/Preferences/%@", NSHomeDirectory(), getSelector()]; |
| } |
| |
| NSString *getPropertiesFilePath() { |
| return [getPreferencesFolderPath() stringByAppendingString:@"/idea.properties"]; |
| } |
| |
| NSString *getDefaultPropertiesFilePath() { |
| return [[[NSBundle mainBundle] bundlePath] stringByAppendingString:@"/bin/idea.properties"]; |
| } |
| |
| // NSString *getDefaultVMOptionsFilePath() { |
| // return [[[NSBundle mainBundle] bundlePath] stringByAppendingString:@fileName]; |
| |
| NSString *getDefaultFilePath(NSString *fileName) { |
| NSString *fullFileName = [[[NSBundle mainBundle] bundlePath] stringByAppendingString:@"/Contents"]; |
| fullFileName = [fullFileName stringByAppendingString:fileName]; |
| NSLog(@"fullFileName is: %@", fullFileName); |
| if ([[NSFileManager defaultManager] fileExistsAtPath:fullFileName]) { |
| NSLog(@"fullFileName exists: %@", fullFileName); |
| } else{ |
| fullFileName = [[[NSBundle mainBundle] bundlePath] stringByAppendingString:fileName]; |
| NSLog(@"fullFileName exists: %@", fullFileName); |
| } |
| return fullFileName; |
| } |
| |
| |
| NSString *getVMOptionsFilePath() { |
| return [getPreferencesFolderPath() stringByAppendingString:@"/idea.vmoptions"]; |
| } |
| |
| NSArray *parseVMOptions() { |
| NSArray *inConfig=[VMOptionsReader readFile:getVMOptionsFilePath()]; |
| if (inConfig) return inConfig; |
| //return [VMOptionsReader readFile:getDefaultVMOptionsFilePath()]; |
| return [VMOptionsReader readFile:getDefaultFilePath(@"/bin/idea.vmoptions")]; |
| } |
| |
| NSDictionary *parseProperties() { |
| NSDictionary *inConfig = [PropertyFileReader readFile:getPropertiesFilePath()]; |
| if (inConfig) return inConfig; |
| return [PropertyFileReader readFile:getDefaultPropertiesFilePath()]; |
| } |
| |
| - (void)fillArgs:(NSMutableArray *)args_array fromProperties:(NSDictionary *)properties { |
| if (properties != nil) { |
| for (id key in properties) { |
| [args_array addObject:[NSString stringWithFormat:@"-D%@=%@", key, [properties objectForKey:key]]]; |
| } |
| } |
| } |
| |
| - (JavaVMInitArgs)buildArgsFor:(NSBundle *)jvm { |
| NSMutableString *classpathOption = [self buildClasspath:jvm]; |
| |
| NSDictionary *jvmInfo = [[NSBundle mainBundle] objectForInfoDictionaryKey:JVMOptions]; |
| NSMutableArray *args_array = [NSMutableArray array]; |
| |
| [args_array addObject:classpathOption]; |
| |
| [args_array addObjectsFromArray:[[jvmInfo objectForKey:@"VMOptions"] componentsSeparatedByString:@" "]]; |
| [args_array addObjectsFromArray:parseVMOptions()]; |
| |
| [self fillArgs:args_array fromProperties:[jvmInfo dictionaryForKey:@"Properties"]]; |
| [self fillArgs:args_array fromProperties:parseProperties()]; |
| |
| JavaVMInitArgs args; |
| args.version = JNI_VERSION_1_6; |
| args.ignoreUnrecognized = JNI_TRUE; |
| |
| args.nOptions = (jint)[args_array count]; |
| args.options = calloc((size_t) args.nOptions, sizeof(JavaVMOption)); |
| for (NSUInteger idx = 0; idx < args.nOptions; idx++) { |
| id obj = [args_array objectAtIndex:idx]; |
| args.options[idx].optionString = strdup([[self expandMacros:[obj description]] UTF8String]); |
| } |
| return args; |
| } |
| |
| - (const char *)mainClassName { |
| NSDictionary *jvmInfo = [[NSBundle mainBundle] objectForInfoDictionaryKey:JVMOptions]; |
| |
| NSString *mainClass = [jvmInfo objectForKey:@"MainClass"]; |
| if (mainClass == nil || mainClass == NULL) { |
| NSLog(@"Info.plist is corrupted, Absent MainClass key."); |
| exit(-1); |
| } |
| |
| char *answer = strdup([[jvmInfo objectForKey:@"MainClass"] UTF8String]); |
| |
| char *cur = answer; |
| while (*cur) { |
| if (*cur == '.') { |
| *cur = '/'; |
| } |
| cur++; |
| } |
| |
| return answer; |
| } |
| |
| - (void)process_cwd { |
| NSDictionary *jvmInfo = [[NSBundle mainBundle] objectForInfoDictionaryKey:JVMOptions]; |
| NSString *cwd = [jvmInfo objectForKey:@"WorkingDirectory"]; |
| if (cwd != nil && cwd != NULL) { |
| cwd = [self expandMacros:cwd]; |
| if (chdir([cwd UTF8String]) != 0) { |
| NSLog(@"Cannot chdir to working directory at %@", cwd); |
| } |
| } else { |
| NSLog(@"Info.plist is corrupted, Absent WorkingDirectory key."); |
| exit(-1); |
| } |
| } |
| |
| - (void)launch { |
| NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; |
| |
| NSBundle *vm = findMatchingVm(); |
| if (vm == nil) { |
| NSString *old_launcher = [self expandMacros:@"$APP_PACKAGE/Contents/MacOS/idea_appLauncher"]; |
| execv([old_launcher fileSystemRepresentation], self->argv); |
| |
| NSLog(@"Cannot find matching VM, aborting"); |
| exit(-1); |
| } |
| |
| NSError *error = nil; |
| BOOL ok = [vm loadAndReturnError:&error]; |
| if (!ok) { |
| NSLog(@"Cannot load JVM bundle: %@", error); |
| exit(-1); |
| } |
| |
| CFBundleRef cfvm = NSBundle2CFBundle(vm); |
| |
| fun_ptr_t_CreateJavaVM create_vm = CFBundleGetFunctionPointerForName(cfvm, CFSTR("JNI_CreateJavaVM")); |
| |
| if (create_vm == NULL) { |
| // We have Apple VM chosen here... |
| /* |
| [self execCommandLineJava:vm]; |
| return; |
| */ |
| |
| NSString *serverLibUrl = [vm.bundlePath stringByAppendingPathComponent:@"Contents/Libraries/libserver.dylib"]; |
| |
| void *libHandle = dlopen(serverLibUrl.UTF8String, RTLD_NOW + RTLD_GLOBAL); |
| if (libHandle) { |
| create_vm = dlsym(libHandle, "JNI_CreateJavaVM_Impl"); |
| } |
| } |
| |
| if (create_vm == NULL) { |
| NSLog(@"Cannot find JNI_CreateJavaVM in chosen JVM bundle at %@", vm.bundlePath); |
| exit(-1); |
| } |
| |
| [self process_cwd]; |
| |
| JNIEnv *env; |
| JavaVM *jvm; |
| |
| JavaVMInitArgs args = [self buildArgsFor:vm]; |
| |
| jint create_vm_rc = create_vm(&jvm, &env, &args); |
| if (create_vm_rc != JNI_OK || jvm == NULL) { |
| NSLog(@"JNI_CreateJavaVM (%@) failed: %d", vm.bundlePath, create_vm_rc); |
| exit(-1); |
| } |
| |
| jclass string_class = (*env)->FindClass(env, "java/lang/String"); |
| if (string_class == NULL) { |
| NSLog(@"No java.lang.String in classpath!"); |
| exit(-1); |
| } |
| |
| const char *mainClassName = [self mainClassName]; |
| jclass mainClass = (*env)->FindClass(env, mainClassName); |
| if (mainClass == NULL || (*env)->ExceptionOccurred(env)) { |
| NSLog(@"Main class %s not found", mainClassName); |
| (*env)->ExceptionDescribe(env); |
| exit(-1); |
| } |
| |
| jmethodID mainMethod = (*env)->GetStaticMethodID(env, mainClass, "main", "([Ljava/lang/String;)V"); |
| if (mainMethod == NULL || (*env)->ExceptionOccurred(env)) { |
| NSLog(@"Cant't find main() method"); |
| (*env)->ExceptionDescribe(env); |
| exit(-1); |
| } |
| |
| // See http://stackoverflow.com/questions/10242115/os-x-strange-psn-command-line-parameter-when-launched-from-finder |
| // about psn_ stuff |
| int arg_count = 0; |
| for (int i = 1; i < argc; i++) { |
| if (memcmp(argv[i], "-psn_", 4) != 0) arg_count++; |
| } |
| |
| jobject jni_args = (*env)->NewObjectArray(env, arg_count, string_class, NULL); |
| |
| arg_count = 0; |
| for (int i = 1; i < argc; i++) { |
| if (memcmp(argv[i], "-psn_", 4) != 0) { |
| jstring jni_arg = (*env)->NewStringUTF(env, argv[i]); |
| (*env)->SetObjectArrayElement(env, jni_args, arg_count, jni_arg); |
| arg_count++; |
| } |
| } |
| |
| (*env)->CallStaticVoidMethod(env, mainClass, mainMethod, jni_args); |
| |
| (*jvm)->DetachCurrentThread(jvm); |
| (*jvm)->DestroyJavaVM(jvm); |
| |
| [pool release]; |
| } |
| |
| @end |