Back to index

lightning-sunbird  0.9+nobinonly
MRJSession.cpp
Go to the documentation of this file.
00001 /* ***** BEGIN LICENSE BLOCK *****
00002  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00003  *
00004  * The contents of this file are subject to the Mozilla Public License Version
00005  * 1.1 (the "License"); you may not use this file except in compliance with
00006  * the License. You may obtain a copy of the License at
00007  * http://www.mozilla.org/MPL/
00008  *
00009  * Software distributed under the License is distributed on an "AS IS" basis,
00010  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00011  * for the specific language governing rights and limitations under the
00012  * License.
00013  *
00014  * The Original Code is the MRJ Carbon OJI Plugin.
00015  *
00016  * The Initial Developer of the Original Code is
00017  * Netscape Communications Corp.
00018  * Portions created by the Initial Developer are Copyright (C) 2001
00019  * the Initial Developer. All Rights Reserved.
00020  *
00021  * Contributor(s):
00022  *   Patrick C. Beard <beard@netscape.com>
00023  *
00024  * Alternatively, the contents of this file may be used under the terms of
00025  * either of the GNU General Public License Version 2 or later (the "GPL"),
00026  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00027  * in which case the provisions of the GPL or the LGPL are applicable instead
00028  * of those above. If you wish to allow use of your version of this file only
00029  * under the terms of either the GPL or the LGPL, and not to allow others to
00030  * use your version of this file under the terms of the MPL, indicate your
00031  * decision by deleting the provisions above and replace them with the notice
00032  * and other provisions required by the GPL or the LGPL. If you do not delete
00033  * the provisions above, a recipient may use your version of this file under
00034  * the terms of any one of the MPL, the GPL or the LGPL.
00035  *
00036  * ***** END LICENSE BLOCK ***** */
00037 
00038 /*
00039        MRJSession.cpp
00040 
00041        Encapsulates a session with the MacOS Runtime for Java.
00042        
00043        by Patrick C. Beard.
00044  */
00045 
00046 #include "MRJSession.h"
00047 #include "MRJPlugin.h"
00048 #include "MRJContext.h"
00049 #include "MRJConsole.h"
00050 #include "MRJMonitor.h"
00051 #include "TimedMessage.h"
00052 
00053 #include <ControlDefinitions.h>
00054 #include <string.h>
00055 #include <Memory.h>
00056 #include <Files.h>
00057 #include <Dialogs.h>
00058 #include <Appearance.h>
00059 #include <Resources.h>
00060 #include <Gestalt.h>
00061 #include <Folders.h>
00062 #include <Script.h>
00063 
00064 #include <JavaControl.h>
00065 #include <CFString.h>
00066 
00067 #include <string>
00068 
00069 extern MRJConsole* theConsole;
00070 extern short thePluginRefnum;
00071 extern FSSpec thePluginSpec;
00072 
00073 static MRJSession* theSession;
00074 
00075 #if REDIRECT_VFPRINTF
00076 
00091 //
00092 //     This function allocates a block of CFM glue code which contains the instructions to call CFM routines
00093 //
00094 static void* NewMachOFunctionPointer(void* cfmfp)
00095 {
00096 #if TARGET_RT_MAC_CFM
00097     static UInt32 CFM_glue[6] = {0x3D800000, 0x618C0000, 0x800C0000, 0x804C0004, 0x7C0903A6, 0x4E800420};
00098     UInt32    *mfp = (UInt32*) NewPtr(sizeof(CFM_glue));              //     Must later dispose of allocated memory
00099     if (mfp) {
00100            BlockMoveData(CFM_glue, mfp, sizeof(CFM_glue));
00101            mfp[0] |= ((UInt32)cfmfp >> 16);
00102            mfp[1] |= ((UInt32)cfmfp & 0xFFFF);
00103            MakeDataExecutable(mfp, sizeof(CFM_glue));
00104        }
00105        return mfp;
00106 #elif TARGET_RT_MAC_MACHO
00107     return cfmfp;
00108 #endif
00109 }
00110 
00111 inline jobject ToGlobalRef(JNIEnv* env, jobject localRef)
00112 {
00113     jobject globalRef = env->NewGlobalRef(localRef);
00114     env->DeleteLocalRef(localRef);
00115     return globalRef;
00116 }
00117 
00118 static jobject Get_System_out(JNIEnv* env)
00119 {
00120     jclass java_lang_System = env->FindClass("java/lang/System");
00121     if (java_lang_System) {
00122         jfieldID outID = env->GetStaticFieldID(java_lang_System, "out", "Ljava/io/PrintStream;");
00123         jobject out = (outID ? env->GetStaticObjectField(java_lang_System, outID) : NULL);
00124         env->DeleteLocalRef(java_lang_System);
00125         return ToGlobalRef(env, out);
00126     }
00127     return NULL;
00128 }
00129 
00130 static jmethodID GetObjectMethodID(JNIEnv* env, jobject object, const char* name, const char* sig)
00131 {
00132     jclass clazz = env->GetObjectClass(object);
00133     if (clazz) {
00134         jmethodID result = env->GetMethodID(clazz, name, sig);
00135         env->DeleteLocalRef(clazz);
00136         return result;
00137     }
00138     return NULL;
00139 }
00140 
00141 static void System_out_print(JNIEnv* env, const jchar* chars, jsize len)
00142 {
00143     static jobject java_lang_System_out = Get_System_out(env);
00144     static jmethodID java_io_PrintStream_print = GetObjectMethodID(env, java_lang_System_out, "print", "([C)V");
00145     jcharArray array = env->NewCharArray(len);
00146     if (array) {
00147         env->SetCharArrayRegion(array, 0, len, (jchar*) chars);
00148         env->CallVoidMethod(java_lang_System_out, java_io_PrintStream_print, array);
00149         env->DeleteLocalRef(array);
00150     }
00151 }
00152 
00159 class ConsoleMessage : public TimedMessage {
00160     CFStringRef mMessage;
00161 
00162 public:
00163     ConsoleMessage(CFStringRef message)
00164         :   mMessage(message)
00165     {
00166         ::CFRetain(mMessage);
00167     }
00168     
00169     ~ConsoleMessage()
00170     {
00171         ::CFRelease(mMessage);
00172     }
00173     
00174     virtual void execute();
00175 };
00176 
00177 void ConsoleMessage::execute()
00178 {
00179     if (theSession) {
00180         jsize len = ::CFStringGetLength(mMessage);
00181         jchar* buffer = new jchar[len];
00182         CFRange range = { 0, len };
00183         CFStringGetCharacters(mMessage, range, buffer);
00184         System_out_print(theSession->getCurrentEnv(), buffer, len);
00185         delete[] buffer;
00186     }
00187 }
00188 
00189 static jint JNICALL java_vfprintf(FILE *fp, const char *format, va_list args)
00190 {
00191     jint result = 0;
00192     CFStringRef formatRef = CFStringCreateWithCString(NULL, format, kCFStringEncodingASCII);
00193     if (formatRef) {
00194         CFStringRef text = CFStringCreateWithFormatAndArguments(NULL, NULL, formatRef, args);
00195         CFRelease(formatRef);
00196         if (text) {
00197             ConsoleMessage* message = new ConsoleMessage(text);
00198             if (message) {
00199                 if (message->send() != noErr)
00200                     delete message;
00201             }
00202             result = ::CFStringGetLength(text);
00203             ::CFRelease(text);
00204         }
00205     }
00206     return result;
00207 }
00208 
00209 #endif /* REDIRECT_VFPRINTF */
00210 
00211 MRJSession::MRJSession()
00212        :      mStatus(noErr), mMainEnv(NULL), mJavaVM(NULL), mSession(NULL),
00213            mFirst(NULL), mLast(NULL), mMessageMonitor(NULL), mLockCount(0)
00214 {
00215 }
00216 
00217 MRJSession::~MRJSession()
00218 {
00219     close();
00220 }
00221 
00222 OSStatus MRJSession::open(const char* consolePath)
00223 {
00224     // Use vanilla JNI invocation API to fire up a fresh JVM.
00225     string classPath = getClassPath();
00226     string pluginHome = getPluginHome();
00227     JavaVMOption theOptions[] = {
00228        { (char*) classPath.c_str() },
00229        { (char*) pluginHome.c_str() },
00230 #if REDIRECT_VFPRINTF
00231        { "vfprintf", NewMachOFunctionPointer(&java_vfprintf) }
00232 #endif
00233     };
00234 
00235     JavaVMInitArgs theInitArgs = {
00236        JNI_VERSION_1_2,
00237        sizeof(theOptions) / sizeof(JavaVMOption),
00238        theOptions,
00239        JNI_TRUE
00240     };
00241 
00242     mStatus = ::JNI_CreateJavaVM(&mJavaVM, (void**) &mMainEnv, &theInitArgs);
00243     
00244     if (mStatus == noErr) {
00245               // create a monitor for the message queue to unblock Java threads.
00246               mMessageMonitor = new MRJMonitor(this);
00247     }
00248 
00249     JNIEnv* env = mMainEnv;
00250     jclass session = env->FindClass("netscape/oji/MRJSession");
00251     if (session) {
00252         mSession = (jclass) env->NewGlobalRef(session);
00253         jmethodID openMethod = env->GetStaticMethodID(session, "open", "(Ljava/lang/String;)V");
00254         if (openMethod) {
00255             jstring path = env->NewStringUTF(consolePath);
00256             if (path) {
00257                 env->CallStaticVoidMethod(session, openMethod, path);
00258                 if (env->ExceptionCheck())
00259                     env->ExceptionClear();
00260                 env->DeleteLocalRef(path);
00261             }
00262         } else {
00263             env->ExceptionClear();
00264         }
00265         env->DeleteLocalRef(session);
00266     } else {
00267         env->ExceptionClear();
00268     }
00269 
00270     if (mStatus == noErr)
00271         theSession = this;
00272 
00273 #if REDIRECT_VFPRINTF
00274     // XXX test the vfprintf function.
00275     jclass notThere = env->FindClass("class/not/Found");
00276     if (env->ExceptionCheck()) {
00277         env->ExceptionDescribe();
00278         env->ExceptionClear();
00279     }
00280 #endif
00281 
00282     return mStatus;
00283 }
00284 
00285 OSStatus MRJSession::close()
00286 {
00287     theSession = NULL;
00288 
00289     if (mJavaVM) {
00290        if (mMessageMonitor != NULL) {
00291               mMessageMonitor->notifyAll();
00292               delete mMessageMonitor;
00293               mMessageMonitor = NULL;
00294        }
00295        
00296        if (mSession) {
00297            jclass session = mSession;
00298            JNIEnv* env = mMainEnv;
00299             jmethodID closeMethod = env->GetStaticMethodID(session, "close", "()V");
00300             if (closeMethod)
00301                 env->CallStaticVoidMethod(session, closeMethod);
00302             else
00303                 env->ExceptionClear();
00304            env->DeleteGlobalRef(mSession);
00305            mSession = NULL;
00306         }
00307 
00308 #if !TARGET_RT_MAC_MACHO // XXX right now, this hangs Chimera...
00309         mJavaVM->DestroyJavaVM();
00310 #endif
00311         mJavaVM = NULL;
00312     }
00313     return noErr;
00314 }
00315 
00316 JNIEnv* MRJSession::getCurrentEnv()
00317 {
00318        JNIEnv* env;
00319        if (mJavaVM->GetEnv((void**)&env, JNI_VERSION_1_2) == JNI_OK)
00320               return env;
00321     return NULL;
00322 }
00323 
00324 JNIEnv* MRJSession::getMainEnv()
00325 {
00326        return mMainEnv;
00327 }
00328 
00329 JavaVM* MRJSession::getJavaVM()
00330 {
00331     return mJavaVM;
00332 }
00333 
00334 Boolean MRJSession::onMainThread()
00335 {
00336        return (getCurrentEnv() == mMainEnv);
00337 }
00338 
00339 inline StringPtr c2p(const char* cstr, StringPtr pstr)
00340 {
00341        pstr[0] = (unsigned char)strlen(cstr);
00342        ::BlockMoveData(cstr, pstr + 1, pstr[0]);
00343        return pstr;
00344 }
00345 
00346 Boolean MRJSession::addToClassPath(const FSSpec& fileSpec)
00347 {
00348     // if the Java VM has started already, it's too late to do this (for now).
00349     if (mJavaVM)
00350         return false;
00351 
00352     // keep accumulating paths.
00353     FSRef ref;
00354     OSStatus status = FSpMakeFSRef(&fileSpec, &ref);
00355     if (status == noErr)
00356         mClassPath.push_back(ref);
00357 
00358     return true;
00359 }
00360 
00361 Boolean MRJSession::addToClassPath(const char* dirPath)
00362 {
00363        // Need to convert the path into an FSSpec, and add it MRJ's class path.
00364        Str255 path;
00365        FSSpec pathSpec;
00366        OSStatus status = ::FSMakeFSSpec(0, 0, c2p(dirPath, path), &pathSpec);
00367        if (status == noErr)
00368               return addToClassPath(pathSpec);
00369        return false;
00370 }
00371 
00372 Boolean MRJSession::addURLToClassPath(const char* fileURL)
00373 {
00374     Boolean success = false;
00375     // Use CFURL, FSRef and FSSpec?
00376     CFURLRef fileURLRef = CFURLCreateWithBytes(NULL, (UInt8*)fileURL, strlen(fileURL),
00377                                                kCFStringEncodingUTF8, NULL);
00378     if (fileURLRef) {
00379         FSRef fsRef;
00380         if (CFURLGetFSRef(fileURLRef, &fsRef)) {
00381             mClassPath.push_back(fsRef);
00382             success = true;
00383         }
00384         CFRelease(fileURLRef);
00385     }
00386     return success;
00387 }
00388 
00389 char* MRJSession::getProperty(const char* propertyName)
00390 {
00391        char* result = NULL;
00392 #if !TARGET_CARBON
00393        OSStatus status = noErr;
00394        JMTextRef nameRef = NULL, valueRef = NULL;
00395        status = ::JMNewTextRef(mSession, &nameRef, kTextEncodingMacRoman, propertyName, strlen(propertyName));
00396        if (status == noErr) {
00397               status = ::JMGetSessionProperty(mSession, nameRef, &valueRef);
00398               ::JMDisposeTextRef(nameRef);
00399               if (status == noErr && valueRef != NULL) {
00400                      UInt32 valueLength = 0;
00401                      status = ::JMGetTextLengthInBytes(valueRef, kTextEncodingMacRoman, &valueLength);
00402                      if (status == noErr) {
00403                             result = new char[valueLength + 1];
00404                             if (result != NULL) {
00405                                    UInt32 actualLength;
00406                                    status = ::JMGetTextBytes(valueRef, kTextEncodingMacRoman, result, valueLength, &actualLength);
00407                                    result[valueLength] = '\0';
00408                             }
00409                             ::JMDisposeTextRef(valueRef);
00410                      }
00411               }
00412        }
00413 #endif
00414        return result;
00415 }
00416 
00417 void MRJSession::setStatus(OSStatus status)
00418 {
00419        mStatus = status;
00420 }
00421 
00422 OSStatus MRJSession::getStatus()
00423 {
00424        return mStatus;
00425 }
00426 
00427 void MRJSession::idle(UInt32 milliseconds)
00428 {
00429        // Each call to idle processes a single message.
00430        dispatchMessage();
00431 
00432 #if !TARGET_CARBON
00433        // Guard against entering the VM multiple times.
00434        if (mLockCount == 0) {
00435               lock();
00436               mStatus = ::JMIdle(mSession, milliseconds);
00437               unlock();
00438        }
00439 #endif
00440 }
00441 
00442 void MRJSession::sendMessage(NativeMessage* message, Boolean async)
00443 {
00444        // can't block the main env, otherwise messages will never be processed!
00445        if (onMainThread()) {
00446               message->execute();
00447        } else {
00448               postMessage(message);
00449               if (!async)
00450                      mMessageMonitor->wait();
00451        }
00452 }
00453 
00457 void MRJSession::postMessage(NativeMessage* message)
00458 {
00459        if (mFirst == NULL) {
00460               mFirst = mLast = message;
00461        } else {
00462               mLast->setNext(message);
00463               mLast = message;
00464        }
00465        message->setNext(NULL);
00466 }
00467 
00468 void MRJSession::dispatchMessage()
00469 {
00470        if (mFirst != NULL) {
00471               NativeMessage* message = mFirst;
00472               mFirst = message->getNext();
00473               if (mFirst == NULL) mLast = NULL;
00474               
00475               message->setNext(NULL);
00476               message->execute();
00477               mMessageMonitor->notify();
00478        }
00479 }
00480 
00481 void MRJSession::lock()
00482 {
00483        ++mLockCount;
00484 }
00485 
00486 void MRJSession::unlock()
00487 {
00488        --mLockCount;
00489 }
00490 
00491 static OSStatus ref2path(const FSRef& ref, char* path, UInt32 maxPathSize)
00492 {
00493     return FSRefMakePath(&ref, (UInt8*)path, maxPathSize);
00494 }
00495 
00496 static OSStatus spec2path(const FSSpec& spec, char* path, UInt32 maxPathSize)
00497 {
00498     FSRef ref;
00499     OSStatus status = FSpMakeFSRef(&spec, &ref);
00500     if (status == noErr)
00501         status = ref2path(ref, path, maxPathSize);
00502     return status;
00503 }
00504 
00505 string MRJSession::getClassPath()
00506 {
00507     // to work around problem in Mac OS X 10.2 (Jaguar) (bugzilla #164712),
00508     // put our classes in the boot class path.
00509     string classPath("-Xbootclasspath/p:");
00510     
00511     // keep appending paths make from FSSpecs.
00512     MRJClassPath::const_iterator i = mClassPath.begin();
00513     if (i != mClassPath.end()) {
00514         char path[1024];
00515         if (ref2path(*i, path, sizeof(path)) == noErr)
00516             classPath += path;
00517         ++i;
00518         while (i != mClassPath.end()) {
00519             if (ref2path(*i, path, sizeof(path)) == noErr) {
00520                 classPath += ":";
00521                 classPath += path;
00522             }    
00523             ++i;
00524         }
00525     }
00526 
00527     return classPath;
00528 }
00529 
00530 string MRJSession::getPluginHome()
00531 {
00532     string pluginHome("-Dnetscape.oji.plugin.home=");
00533 
00534     char path[1024];
00535     if (spec2path(thePluginSpec, path, sizeof(path)) == noErr) {
00536         char* lastSlash = strrchr(path, '/');
00537         if (lastSlash) {
00538             *lastSlash = '\0';
00539             pluginHome += path;
00540         }
00541     }
00542 
00543     return pluginHome;
00544 }