/* * Copyright 2000 Perforce Software. All rights reserved. * * This file is part of Perforce - the FAST SCM System. * * Author: Michael Bishop * * Tabs: 4 spaces * * ClientUserJavaAdapter is a class which is an instance of ClientUser, * but directs all of its virtual methods back to the Java peer, * "com.perforce.client.api.ClientUser" with which it is tightly * bound. * * DESIGN ISSUES: * * One of the difficulties of writing this object is that * when we get called back from the server, we no longer have * a JNIEnv * with which to make calls back into the JVM. * We can always use GetCurrentJNIEnv() to get the current * JNIEnv * but where do we get the java object that we should call * back to? * * I solved this by saving off the java object in the native object * so that it can be referenced later. It must be passed into the * constructor of the native object. However, because jobject * references are only valid for the local stack and below and the * current thread, we must make a global reference that can be retained * outside of those restrictions. JNI provides this through * NewGlobalRef(), but the reference count on the java object is * incremented by one. This means that it will *never* be garbage * collected, unless that reference is freed by the native code. * * So, how do we know when to free the reference? I suppose we * could do it in the finalize() method of the java object, however * we contain a reference to it and so it will never be destroyed, * the finalize() method will never be called and we will never * relinquish the reference. It's a chicken and egg problem. * The solution to this (although if you have other ideas on how to * do this, please email me) is for the java side to explicitly free * the native reference to the java object. It can either do this * explicitly with a call to ClientUser.releaseNativeGlobalRef() * (which calls our ReleaseGlobalRef()), or they can set the native * object to free it when the Finished() method is called, signaling * that no more callbacks will be issued, until this object is used * in another p4 command. * * The rule of thumb is: if you make a new ClientUser for every * p4 call you make, don't even worry about this. It will clean * up the global reference by default after finished() is called. * If you want to reuse ClientUsers, construct it with "false" as * the parameter. This turns off automatic destruction of the * global reference allowing you to use the ClientUser for multiple * calls. However, to avoid memory leaks, you must explicitly * free the global reference yourself through * ClientUser.releaseNativeGlobalRef() * */ #include "ClientUserJavaAdapter.h" #include <assert.h> /*===========================================================================*\ * * Routine: ClientUserJavaAdapter() * * Comments: Creates a ClientUserJavaAdapter to be used primarily to * interface with Java. * * Parameters: * * env - a JNIEnv pointer used to access the object reference * peer - the java reference to the java ClientUser object to * be tied to this native object * \*===========================================================================*/ ClientUserJavaAdapter::ClientUserJavaAdapter( JNIEnv * env, jobject peer ) : ClientUser() { // // We need the java object to stick around even if there // is no java reference to it. We need to make a reference // to it that keeps it around for us to call back to. // javaPeer = env->NewGlobalRef( peer ); } /*===========================================================================*\ * * Routine: InputData() * * Comments: Asks java to get some data to pass back to the server. * * Parameters: * * strbuf - the buffer to return the data in * e - a reference to an error to be returned * \*===========================================================================*/ void ClientUserJavaAdapter::InputData(JNIEnv * env, StrBuf *strbuf, Error *e ) { // // Find the inputDataCALLBACK method to call. // Check out the JNI Spec 1.1, pg.23 "Type Signatures" // (http://java.sun.com/products/jdk/1.1/docs/guide/jni/spec/types.doc.html#16432) // to find out what that weird encoding that describes // the parameters is. // jmethodID javaMethodID = GetMethod( env, "inputDataCALLBACK", "(I)Ljava/lang/StringBuffer;" ); assert( javaMethodID ); // // call it // jstring jResult = (jstring)env->CallObjectMethod( javaPeer, javaMethodID, (jlong)e ); // // Marshall the java result back to a StrBuf // const char * cResult = env->GetStringUTFChars(jResult, NULL); strbuf->Clear(); strbuf->Set( cResult ); strbuf->Terminate(); env->ReleaseStringUTFChars(jResult, cResult); } /*===========================================================================*\ * * Routine: HandleError() * * Comments: Asks java to get some data to pass back to the server. * XXX - Why do we have both an OutputError and a HandleError()? * * Parameters: * * err - a reference to an error to be returned * \*===========================================================================*/ void ClientUserJavaAdapter::HandleError(JNIEnv * env, Error *err ) { // // Find callback // jmethodID javaMethodID = GetMethod( env, "handleErrorCALLBACK", "(I)V" ); assert( javaMethodID ); // // Call the callback passing an integer that points to the native // Error instance. It will be reconstructed on the java side. // env->CallVoidMethod( javaPeer, javaMethodID, (jlong)err ); } /*===========================================================================*\ * * Routine: OutputError() * * Comments: Output a string that is an error message. * XXX- Why doesn't OutputError() take an Error as a parameter? * * Parameters: * * errBuf - the string to be output * \*===========================================================================*/ void ClientUserJavaAdapter::OutputError(JNIEnv * env, char * errBuf ) { // // Find callback // jmethodID outputError = GetMethod(env, "outputErrorCALLBACK", "(Ljava/lang/String;)V" ); assert( outputError ); // // Call it // env->CallVoidMethod(javaPeer, outputError, env->NewStringUTF(errBuf)); } /*===========================================================================*\ * * Routine: OutputInfo() * * Comments: Output some kind of message. * XXX - How is OutputInfo() different from OutpuText()? * * Parameters: * * level - XXX * data - the info to output * \*===========================================================================*/ void ClientUserJavaAdapter::OutputInfo(JNIEnv * env, char level, const_char * data ) { // // Find callback // jmethodID javaMethodID = GetMethod( env, "outputInfoCALLBACK", "(CLjava/lang/String;)V" ); assert( javaMethodID ); // // Call it // env->CallVoidMethod( javaPeer, javaMethodID, (jchar) level, // XXX should do UTF conversion env->NewStringUTF(data)); } /*===========================================================================*\ * * Routine: OutputBinary() * * Comments: Output some data. * * Parameters: * * data - the data to output * length - how much data there is * \*===========================================================================*/ void ClientUserJavaAdapter::OutputBinary(JNIEnv * env, const_char * data, int length ) { // // Find callback // jmethodID javaMethodID = GetMethod( env, "outputBinaryCALLBACK", "([B)V" ); assert( javaMethodID ); // // Call it // if (data == NULL) return env->CallVoidMethod(javaPeer, javaMethodID, NULL); else { jbyteArray jarray = env->NewByteArray(length); env->SetByteArrayRegion(jarray, 0, length, (const jbyte *) data); return env->CallVoidMethod(javaPeer, javaMethodID, jarray); } } /*===========================================================================*\ * * Routine: OutputText() * * Comments: Output a textual message. * * Parameters: * * data - the message to output * length - how long the message is * \*===========================================================================*/ void ClientUserJavaAdapter::OutputText(JNIEnv * env, const_char * data, int length ) { // // Find callback // jmethodID outputText = GetMethod( env, "outputTextCALLBACK", "([C)V" ); jcharArray jdata = env->NewCharArray(length); assert(outputText); assert(jdata); env->SetCharArrayRegion(jdata, 0, length, (const jchar*) data); // // Call it // env->CallVoidMethod(javaPeer, outputText, jdata); } /*===========================================================================*\ * * Routine: OutputStat() * * Comments: Output a string dictionary of name/value pairs. * * Parameters: * * varList - the string dictionary * \*===========================================================================*/ void ClientUserJavaAdapter::OutputStat(JNIEnv * env, StrDict * varList ) { // // Find callback // jmethodID javaMethodID = GetMethod( env, "outputStatCALLBACK", "(I)V" ); assert( javaMethodID ); // // Call the callback passing an integer that points to the native // StrDict instance. It will be reconstructed on the java side. // env->CallVoidMethod( javaPeer, javaMethodID, (jlong)varList ); } /*===========================================================================*\ * * Routine: Prompt() * * Comments: Ask the user a question. * XXX - Why have a Prompt() method? Why not OutputText() * followed by a InputData()? * * Parameters: * * msg - What to prompt with * rsp - the response back * noEcho - XXX - should this be a boolean? * e - a reference to an Error * \*===========================================================================*/ void ClientUserJavaAdapter::Prompt( JNIEnv * env, const StrPtr &msg, StrBuf &rsp, int noEcho, Error * e ) { // // Find callback // jmethodID javaMethodID = GetMethod( env, "promptCALLBACK", "(Ljava/lang/String;II)Ljava/lang/String;" ); assert( javaMethodID ); // // Call it // jstring jRsp = (jstring)env->CallObjectMethod( javaPeer, javaMethodID, env->NewStringUTF(msg.Text()), (jlong)noEcho, (jlong)e ); // // Marshall the result back into a StrBuf to be returned. // const char * temp = env->GetStringUTFChars(jRsp, NULL); rsp.Clear(); rsp.Set( temp ); env->ReleaseStringUTFChars(jRsp, temp); } /*===========================================================================*\ * * Routine: ErrorPause() * * Comments: XXX - What is this for? * * Parameters: * * errBuf - Error to output * XXX - why provide this *and* an error? Why not just let the * error talk? * e - the Error that occurred. * \*===========================================================================*/ void ClientUserJavaAdapter::ErrorPause(JNIEnv * env, char * errBuf, Error * e ) { // // Find callback // jmethodID javaMethodID = GetMethod( env, "errorPauseCALLBACK", "(Ljava/lang/String;I)V" ); assert( javaMethodID ); // // Call it // env->CallVoidMethod( javaPeer, javaMethodID, env->NewStringUTF(errBuf), (jlong)e ); } /*===========================================================================*\ * * Routine: Edit() * * Comments: The server is asking to edit a file. * * Parameters: * * f1 - the file to edit. * e - use to signal if an error occurred. * \*===========================================================================*/ void ClientUserJavaAdapter::Edit(JNIEnv * env, FileSys *f1, Error *e ) { // // Find callback // jmethodID javaMethodID = GetMethod( env, "editCALLBACK", "(II)V" ); assert( javaMethodID ); // // Call it // Keep in mind that if the java Edit() method is not // overridden, it will instead call InteritedEdit() and the // native side will instead be used. // env->CallVoidMethod( javaPeer, javaMethodID, (jlong)f1, (jlong)e ); } /*===========================================================================*\ * * Routine: InheritedEdit() * * Comments: Exists so that if the Java side is not overidden, * there is an implementation of the native superclass method * for it to call. In this way, the java class essentially * inherits the native default implementation. * * Parameters: * * f1 - the file to edit. * e - use to signal if an error occurred. * \*===========================================================================*/ void ClientUserJavaAdapter::InheritedEdit( FileSys *f1, Error *e ) { // // Justs call the inherited method // ClientUser::Edit( f1, e ); } /*===========================================================================*\ * * Routine: Diff() * * Comments: The server is asking to display a diff of a file. * * Parameters: * * f1 - the first file in the diff. * f2 - the second file in the diff. * doPage - XXX * diffFlags - XXX * e - use to signal if an error occurred. * \*===========================================================================*/ void ClientUserJavaAdapter::Diff( JNIEnv * env, FileSys *f1, FileSys *f2, int doPage, char *diffFlags, Error *e ) { // // Find callback // jmethodID javaMethodID = GetMethod( env, "diffCALLBACK", "(IIILjava/lang/String;I)V" ); assert( javaMethodID ); // // Call it // Keep in mind that if the java Diff() method is not // overridden, it will instead call InheritedDiff() and the // native side will instead be used. // env->CallVoidMethod( javaPeer, javaMethodID, (jlong)f1, (jlong)f2, (jboolean)doPage, env->NewStringUTF(diffFlags), (jlong)e ); } /*===========================================================================*\ * * Routine: InheritedDiff() * * Comments: Exists so that if the Java side is not overidden, * there is an implementation of the native superclass method * for it to call. In this way, the java class essentially * inherits the native default implementation. * * Parameters: * * f1 - the file to edit. * e - use to signal if an error occurred. * \*===========================================================================*/ void ClientUserJavaAdapter::InheritedDiff( JNIEnv * env, FileSys *f1, FileSys *f2, int doPage, char *diffFlags, Error *e ) { // // Call inherited method // ClientUser::Diff(f1, f2, doPage, diffFlags, e ); } /*===========================================================================*\ * * Routine: Merge() * * Comments: The server is asking to perform a three-way merge of a * fileset. * * Parameters: * * base - the common ancestor. * leg1 - the first file in the diff. * leg2 - the second file in the diff * result - the resulting file * e - use to signal if an error occurred. * \*===========================================================================*/ void ClientUserJavaAdapter::Merge( JNIEnv * env, FileSys *base, FileSys *leg1, FileSys *leg2, FileSys *result, Error *e ) { // // Find callback // jmethodID javaMethodID = GetMethod( env, "mergeCALLBACK", "(IIIII)V" ); assert( javaMethodID ); // // Call it // env->CallVoidMethod( javaPeer, javaMethodID, (jlong)base, (jlong)leg1, (jlong)leg2, (jlong)result, (jlong)e ); } /*===========================================================================*\ * * Routine: InheritedMerge() * * Comments: Exists so that if the Java side is not overidden, * there is an implementation of the native superclass method * for it to call. In this way, the java class essentially * inherits the native default implementation. * * Parameters: * * base - the common ancestor. * leg1 - the first file in the diff. * leg2 - the second file in the diff * result - the resulting file * e - use to signal if an error occurred. * \*===========================================================================*/ void ClientUserJavaAdapter::InheritedMerge( JNIEnv * env, FileSys *base, FileSys *leg1, FileSys *leg2, FileSys *result, Error *e ) { // // Call inherited method // ClientUser::Merge(base, leg1, leg2, result, e ); } /*===========================================================================*\ * * Routine: Help() * * Comments: The server is asking to output a multi-line help message. * XXX - Why do we need another method for this? * * Parameters: * * help - a null-delimited array of null-terminated strings as the * help to display. * XXX - are all the other output*() series of methods outputting * only one line? If they put \n in their output, why don't we do * that here? Why *don't* we do the multi-line option there? * \*===========================================================================*/ void ClientUserJavaAdapter::Help(JNIEnv * env, const_char *const *help ) { // // Find callback // jmethodID javaMethodID = GetMethod( env, "helpCALLBACK", "([Ljava/lang/String;)V" ); assert( javaMethodID ); // // Marshall the array of strings to a java String Array object // int length = 0; while (help[length] != NULL) length++; jclass c = env->FindClass("java/lang/String"); jobjectArray stringArray = env->NewObjectArray(length, c, NULL); for (int i = 0; i < length; i++) { env->SetObjectArrayElement(stringArray, i, env->NewStringUTF(help[i])); } // // Call it // env->CallVoidMethod(javaPeer, javaMethodID, stringArray ); } /*===========================================================================*\ * * Routine: Finished() * * Comments: Release our reference to the java object that is a front * for this native object. * * AFTER RELEASING THIS, YOU CAN NO LONGER MAKE CALLBACKS * TO JAVA. * * Parameters: * * env - JNIEnv we can use to release it. * \*===========================================================================*/ void ClientUserJavaAdapter::Finished(JNIEnv * env) { // // Find callback // jmethodID javaMethodID = GetMethod( env, "finishedCALLBACK", "()V" ); assert( javaMethodID ); // // Call it // env->CallVoidMethod( javaPeer, javaMethodID ); } /*===========================================================================*\ * * Routine: ReleaseGlobalRef() * * Comments: Release our reference to the java object that is a front * for this native object. * * AFTER RELEASING THIS, YOU CAN NO LONGER MAKE CALLBACKS * TO JAVA. * * You should just let Java call this when it needs to clean up. * * Parameters: * * env - JNIEnv we can use to release it. * \*===========================================================================*/ void ClientUserJavaAdapter::ReleaseGlobalRef( JNIEnv * env ) { env->DeleteGlobalRef( this->javaPeer ); this->javaPeer = NULL; } /*===========================================================================*\ * * Routine: GetMethod() * * Comments: A shortcut that we use to get java methods. It always passes * in our java peer as the object to get the method for. * * Parameters: * * env - JNIEnv we can use to find it. * name - the name of the method. * signature - the signature of the method parameters. * \*===========================================================================*/ jmethodID ClientUserJavaAdapter::GetMethod( JNIEnv * env, char * name, char * signature ) { jclass c = env->GetObjectClass(javaPeer); return env->GetMethodID(c, name, signature); }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#5 | 4109 | Paul Krause | fix env | ||
#4 | 4102 | Paul Krause | ditch JNIStringUtil | ||
#3 | 4101 | Paul Krause | ditch JNIUtils | ||
#2 | 4100 | Paul Krause | fix type problems | ||
#1 | 4092 | Paul Krause | Copy JNI wrapper to C++ API from //guest/michael_bishop/P4APIForJava/c-cpp | ||
//guest/michael_bishop/P4APIForJava/c-cpp/ClientUserJavaAdapter.cpp | |||||
#1 | 430 | Michael Bishop |
Initial checkin. Seems to work. Not very much testing. Not very much documentation. Some more commenting needs to take place. But, it's there to experiment with. |