/* * 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 "JNIStringUtil.h" #include "JNIUtils.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( StrBuf *strbuf, Error *e ) { JNIEnv * env = GetCurrentJNIEnv(); // // 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, (jint)e ); // // Marshall the java result back to a StrBuf // char * cResult = jstring_to_cstring( env, jResult ); strbuf->Clear(); strbuf->Set( cResult ); strbuf->AppendNull(); delete 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( Error *err ) { JNIEnv * env = GetCurrentJNIEnv(); // // 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, (jint)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( char * errBuf ) { JNIEnv * env = GetCurrentJNIEnv(); // // Find callback // jmethodID javaMethodID = GetMethod( env, "outputErrorCALLBACK", "(Ljava/lang/String;)V" ); assert( javaMethodID ); // // Call it // env->CallVoidMethod( javaPeer, javaMethodID, cstring_to_jstring( env, 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( char level, const_char * data ) { JNIEnv * env = GetCurrentJNIEnv(); // // Find callback // jmethodID javaMethodID = GetMethod( env, "outputInfoCALLBACK", "(CLjava/lang/String;)V" ); assert( javaMethodID ); // // Call it // env->CallVoidMethod( javaPeer, javaMethodID, cchar_to_jchar( env, level ), cstring_to_jstring( env, data ) ); } /*===========================================================================*\ * * Routine: OutputBinary() * * Comments: Output some data. * * Parameters: * * data - the data to output * length - how much data there is * \*===========================================================================*/ void ClientUserJavaAdapter::OutputBinary( const_char * data, int length ) { JNIEnv * env = GetCurrentJNIEnv(); // // Find callback // jmethodID javaMethodID = GetMethod( env, "outputBinaryCALLBACK", "([B)V" ); assert( javaMethodID ); // // Call it // env->CallVoidMethod( javaPeer, javaMethodID, voidptr_to_jbyteArray( env, data, length ) ); } /*===========================================================================*\ * * Routine: OutputText() * * Comments: Output a textual message. * * Parameters: * * data - the message to output * length - how long the message is * \*===========================================================================*/ void ClientUserJavaAdapter::OutputText( const_char * data, int length ) { JNIEnv * env = GetCurrentJNIEnv(); // // Find callback // jmethodID javaMethodID = GetMethod( env, "outputTextCALLBACK", "([C)V" ); assert( javaMethodID ); // // Call it // env->CallVoidMethod( javaPeer, javaMethodID, charptr_to_jcharArray( env, data, length ) ); } /*===========================================================================*\ * * Routine: OutputStat() * * Comments: Output a string dictionary of name/value pairs. * * Parameters: * * varList - the string dictionary * \*===========================================================================*/ void ClientUserJavaAdapter::OutputStat( StrDict * varList ) { JNIEnv * env = GetCurrentJNIEnv(); // // 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, (jint)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( const StrPtr &msg, StrBuf &rsp, int noEcho, Error * e ) { JNIEnv * env = GetCurrentJNIEnv(); // // 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, cstring_to_jstring( env, msg.Text() ), (jint)noEcho, (jint)e ); // // Marshall the result back into a StrBuf to be returned. // char * temp = jstring_to_cstring( env, jRsp ); rsp.Clear(); rsp.Set( temp ); free( 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( char * errBuf, Error * e ) { JNIEnv * env = GetCurrentJNIEnv(); // // Find callback // jmethodID javaMethodID = GetMethod( env, "errorPauseCALLBACK", "(Ljava/lang/String;I)V" ); assert( javaMethodID ); // // Call it // env->CallVoidMethod( javaPeer, javaMethodID, cstring_to_jstring( env, errBuf ), (jint)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( FileSys *f1, Error *e ) { JNIEnv * env = GetCurrentJNIEnv(); // // 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, (jint)f1, (jint)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( FileSys *f1, FileSys *f2, int doPage, char *diffFlags, Error *e ) { JNIEnv * env = GetCurrentJNIEnv(); // // 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, (jint)f1, (jint)f2, (jboolean)doPage, cstring_to_jstring( env, diffFlags ), (jint)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( 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( FileSys *base, FileSys *leg1, FileSys *leg2, FileSys *result, Error *e ) { JNIEnv * env = GetCurrentJNIEnv(); // // Find callback // jmethodID javaMethodID = GetMethod( env, "mergeCALLBACK", "(IIIII)V" ); assert( javaMethodID ); // // Call it // env->CallVoidMethod( javaPeer, javaMethodID, (jint)base, (jint)leg1, (jint)leg2, (jint)result, (jint)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( 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( const_char *const *help ) { JNIEnv * env = GetCurrentJNIEnv(); // // Find callback // jmethodID javaMethodID = GetMethod( env, "helpCALLBACK", "([Ljava/lang/String;)V" ); assert( javaMethodID ); // // Marshall the array of strings to a java String Array object // jobjectArray stringArray = charptrptrNullEnded_to_jStringArray( env, help ); // // 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 = GetCurrentJNIEnv(); // // 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 ) { return ::GetObjectMethod( env, javaPeer, name, signature ); }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#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. |