From 50592245dce1909588ac6fa3c0f02db6d46a7a3c Mon Sep 17 00:00:00 2001 From: Nathan Glenn Date: Wed, 30 Aug 2023 18:47:32 -0500 Subject: [PATCH] Use nicer handler API's in Java SML wrapper Delete tons of commented-out duplicate. Merge ClientMessage and RhsFunction handler code, as they are exactly the same. Add test for client messages to the Java SML test. Also print nice emoji from SML Java test. Tell SCons to compile UTF-8 source in case the system default encoding is different. --- Core/ClientSMLSWIG/Java/JavaCallbackByHand.h | 594 +++--------------- Java/SMLJava/SConscript | 3 +- .../main/java/edu/umich/soar/TestJavaSML.java | 36 +- 3 files changed, 132 insertions(+), 501 deletions(-) diff --git a/Core/ClientSMLSWIG/Java/JavaCallbackByHand.h b/Core/ClientSMLSWIG/Java/JavaCallbackByHand.h index 670e41a90d..d565ded0c5 100644 --- a/Core/ClientSMLSWIG/Java/JavaCallbackByHand.h +++ b/Core/ClientSMLSWIG/Java/JavaCallbackByHand.h @@ -114,14 +114,15 @@ int jUpdateCallback = pKernel.RegisterForSystemEvent(pKernel, smlEventId.smlEVEN // A native thread cannot be attached simultaneously to two Java VMs. // When a thread is attached to the VM, the context class loader is the bootstrap loader. + +#include +#include + #ifdef __cplusplus // We expose the public methods in a DLL with C naming (not C++ mangled names) extern "C" { #endif -#include -#include - class JavaCallbackData { // Making these public as this is basically just a struct. @@ -190,6 +191,104 @@ class JavaCallbackData } } ; +#ifdef __cplusplus +} // extern "C" +#endif + +// This is the C++ handler which will be called by clientSML when the event fires. +// Then from here we need to call back to Java to pass back the message. +static std::string StringEventHandler(sml::smlStringEventId id, void* pUserData, sml::Kernel* pKernel, char const* pData) +{ + // The user data is the class we declared above, where we store the Java data to use in the callback. + JavaCallbackData* pJavaData = (JavaCallbackData*)pUserData ; + + // Now try to call back to Java + JNIEnv *jenv = pJavaData->GetEnv() ; + + // Create the string to return to the caller + jstring data = pData != NULL ? jenv->NewStringUTF(pData) : 0 ; + + // Make the method call. + jstring result = (jstring)jenv->CallObjectMethod(pJavaData->m_HandlerObject, pJavaData->m_Mid, (int)id, pJavaData->m_CallbackData, pJavaData->m_KernelObject, data); + + // Get the returned string + std::string resultStr = "" ; + + if (result != 0) + { + // Get the C string + char const* pResult = jenv->GetStringUTFChars(result, 0); + + // Copy it into our std::string + resultStr = pResult ; + + // Release the Java string + jenv->ReleaseStringUTFChars(result, pResult); + } + + // Cleaning up the local references just in case + jenv->DeleteLocalRef(data); + + // Return the result + return resultStr ; + +} + +static const std::string handleRhsEvent(sml::smlRhsEventId id, void *pUserData, sml::Agent *pAgent, + char const *pFunctionName, char const *pArgument) +{ + // The user data is the class we declared above, where we store the Java data to use in the callback. + JavaCallbackData *pJavaData = (JavaCallbackData *)pUserData; + + // Now try to call back to Java + JNIEnv *jenv = pJavaData->GetEnv(); + + // Convert our C++ strings to Java strings + jstring agentName = pAgent != NULL ? jenv->NewStringUTF(pAgent->GetAgentName()) : 0; + jstring functionName = pFunctionName != NULL ? jenv->NewStringUTF(pFunctionName) : 0; + jstring argument = pArgument != NULL ? jenv->NewStringUTF(pArgument) : 0; + + // Make the method call. + jstring result = (jstring)jenv->CallObjectMethod(pJavaData->m_HandlerObject, pJavaData->m_Mid, (int)id, pJavaData->m_CallbackData, agentName, functionName, argument); + + // Cleaning up the local references just in case + jenv->DeleteLocalRef(agentName); + jenv->DeleteLocalRef(functionName); + jenv->DeleteLocalRef(argument); + + // Get the returned string + std::string resultStr = ""; + + if (result != 0) + { + // Get the C string + char const *pResult = jenv->GetStringUTFChars(result, 0); + + // Copy it into our std::string + resultStr = pResult; + + // Release the Java string + jenv->ReleaseStringUTFChars(result, pResult); + } + return resultStr; +} + +// This is the C++ handler which will be called by clientSML when the event fires. +// Then from here we need to call back to Java to pass back the message. +static const sml::RhsEventHandlerCpp getRhsEventHandler(void *pUserData) +{ + return [pUserData](sml::smlRhsEventId id, sml::Agent *pAgent, char const *pFunctionName, char const *pArgument) + { + return handleRhsEvent(id, pUserData, pAgent, pFunctionName, pArgument); + }; +} + + +#ifdef __cplusplus +// We expose the public methods in a DLL with C naming (not C++ mangled names) +extern "C" { +#endif + std::list callbackdatas; void ReleaseCallbackData(JavaCallbackData* pJavaData) { @@ -224,10 +323,6 @@ static JavaCallbackData* CreateJavaCallbackData(bool storeAgent, JNIEnv *jenv, j jobject jglobal4 = jenv->NewGlobalRef(jarg4) ; // The Java object which will handle this callback jobject jglobal6 = jenv->NewGlobalRef(jarg6) ; // Arbitrary object passed back to the caller - // Get the method name from the Java string - // We used to pass this in as a parameter to the callback, but now its imbedded in the interface definition. - //const char *pMethodName = jenv->GetStringUTFChars(jarg5, 0); - // Record the virtual machine we are using JavaVM vm ; JavaVM* pvm = &vm ; @@ -244,9 +339,6 @@ static JavaCallbackData* CreateJavaCallbackData(bool storeAgent, JNIEnv *jenv, j // Save the callback data so we can free it later callbackdatas.push_back(pJavaData); - // Release the string we got from Java - //jenv->ReleaseStringUTFChars(jarg5, pMethodName); - return pJavaData ; } @@ -256,36 +348,7 @@ static void RunEventHandler(sml::smlRunEventId id, void* pUserData, sml::Agent* { // The user data is the class we declared above, where we store the Java data to use in the callback. JavaCallbackData* pJavaData = (JavaCallbackData*)pUserData ; - -// // Now try to call back to Java -// JNIEnv *jenv = pJavaData->GetEnv(); -// -// // We start from the Java object whose method we wish to call. -// jobject jobj = pJavaData->m_HandlerObject ; -// jclass cls = jenv->GetObjectClass(jobj) ; -// -// if (cls == 0) -// { -// printf("Failed to get Java class\n") ; -// return ; -// } -// -// // Look up the Java method we want to call. -// // The method name is passed in by the user (and needs to match exactly, including case). -// // The method should be owned by the m_HandlerObject that the user also passed in. -// // Any slip here and you get a NoSuchMethod exception and my Java VM shuts down. -// jmethodID mid = jenv->GetMethodID(cls, pJavaData->m_HandlerMethod.c_str(), "(ILjava/lang/Object;Lsml/Agent;I)V") ; -// -// if (mid == 0) -// { -// printf("Failed to get Java method\n") ; -// return ; -// } -// -// //Make the method call. -// jenv->CallVoidMethod(jobj, mid, (int)id, pJavaData->m_CallbackData, pJavaData->m_AgentObject, (int)phase); pJavaData->GetEnv()->CallVoidMethod(pJavaData->m_HandlerObject, pJavaData->m_Mid, (int)id, pJavaData->m_CallbackData, pJavaData->m_AgentObject, (int)phase); - } // This is the hand-written JNI method for registering a callback. @@ -333,37 +396,7 @@ static void OutputNotificationHandler(void* pUserData, sml::Agent* pAgent) { // The user data is the class we declared above, where we store the Java data to use in the callback. JavaCallbackData* pJavaData = (JavaCallbackData*)pUserData ; - -// // Now try to call back to Java -// JNIEnv *jenv = pJavaData->GetEnv(); -// -// // We start from the Java object whose method we wish to call. -// jobject jobj = pJavaData->m_HandlerObject ; -// jclass cls = jenv->GetObjectClass(jobj) ; -// -// if (cls == 0) -// { -// printf("Failed to get Java class\n") ; -// return ; -// } -// -// // Look up the Java method we want to call. -// // The method name is passed in by the user (and needs to match exactly, including case). -// // The method should be owned by the m_HandlerObject that the user also passed in. -// // Any slip here and you get a NoSuchMethod exception and my Java VM shuts down. -// jmethodID mid = jenv->GetMethodID(cls, pJavaData->m_HandlerMethod.c_str(), "(Ljava/lang/Object;Lsml/Agent;)V") ; -// -// if (mid == 0) -// { -// printf("Failed to get Java method\n") ; -// return ; -// } -// -// // Make the method call. -// jenv->CallVoidMethod(jobj, mid, pJavaData->m_CallbackData, pJavaData->m_AgentObject); - pJavaData->GetEnv()->CallVoidMethod(pJavaData->m_HandlerObject, pJavaData->m_Mid, pJavaData->m_CallbackData, pJavaData->m_AgentObject); - } JNIEXPORT jlong JNICALL Java_sml_smlJNI_Agent_1RegisterForOutputNotification(JNIEnv *jenv, jclass jcls, jlong jarg1, jobject jarg3, jobject jarg4, jobject jarg6) @@ -411,35 +444,12 @@ static void ProductionEventHandler(sml::smlProductionEventId id, void* pUserData // Now try to call back to Java JNIEnv *jenv = pJavaData->GetEnv() ; -// -// // We start from the Java object whose method we wish to call. -// jobject jobj = pJavaData->m_HandlerObject ; -// jclass cls = jenv->GetObjectClass(jobj) ; -// -// if (cls == 0) -// { -// printf("Failed to get Java class\n") ; -// return ; -// } -// -// // Look up the Java method we want to call. -// // The method name is passed in by the user (and needs to match exactly, including case). -// // The method should be owned by the m_HandlerObject that the user also passed in. -// // Any slip here and you get a NoSuchMethod exception and my Java VM shuts down. -// jmethodID mid = jenv->GetMethodID(cls, pJavaData->m_HandlerMethod.c_str(), "(ILjava/lang/Object;Lsml/Agent;Ljava/lang/String;Ljava/lang/String;)V") ; -// -// if (mid == 0) -// { -// printf("Failed to get Java method\n") ; -// return ; -// } // Convert our C++ strings to Java strings jstring prod = pProdName != NULL ? jenv->NewStringUTF(pProdName) : 0 ; jstring inst = pInstantiation != NULL ? jenv->NewStringUTF(pInstantiation) : 0 ; // Make the method call. -// jenv->CallVoidMethod(jobj, mid, (int)id, pJavaData->m_CallbackData, pJavaData->m_AgentObject, prod, inst); jenv->CallVoidMethod(pJavaData->m_HandlerObject, pJavaData->m_Mid, (int)id, pJavaData->m_CallbackData, pJavaData->m_AgentObject, prod, inst); // Cleaning up the local references just in case @@ -498,33 +508,10 @@ static void PrintEventHandler(sml::smlPrintEventId id, void* pUserData, sml::Age // Now try to call back to Java JNIEnv *jenv = pJavaData->GetEnv() ; -// // We start from the Java object whose method we wish to call. -// jobject jobj = pJavaData->m_HandlerObject ; -// jclass cls = jenv->GetObjectClass(jobj) ; -// -// if (cls == 0) -// { -// printf("Failed to get Java class\n") ; -// return ; -// } -// -// // Look up the Java method we want to call. -// // The method name is passed in by the user (and needs to match exactly, including case). -// // The method should be owned by the m_HandlerObject that the user also passed in. -// // Any slip here and you get a NoSuchMethod exception and my Java VM shuts down. -// jmethodID mid = jenv->GetMethodID(cls, pJavaData->m_HandlerMethod.c_str(), "(ILjava/lang/Object;Lsml/Agent;Ljava/lang/String;)V") ; -// -// if (mid == 0) -// { -// printf("Failed to get Java method\n") ; -// return ; -// } - // Convert our C++ strings to Java strings jstring message = pMessage != NULL ? jenv->NewStringUTF(pMessage) : 0 ; // Make the method call. -// jenv->CallVoidMethod(jobj, mid, (int)id, pJavaData->m_CallbackData, pJavaData->m_AgentObject, message); jenv->CallVoidMethod(pJavaData->m_HandlerObject, pJavaData->m_Mid, (int)id, pJavaData->m_CallbackData, pJavaData->m_AgentObject, message); // Cleaning up the local references just in case @@ -581,28 +568,6 @@ static void XMLEventHandler(sml::smlXMLEventId id, void* pUserData, sml::Agent* // Now try to call back to Java JNIEnv *jenv = pJavaData->GetEnv() ; -// // We start from the Java object whose method we wish to call. -// jobject jobj = pJavaData->m_HandlerObject ; -// jclass cls = jenv->GetObjectClass(jobj) ; -// -// if (cls == 0) -// { -// printf("Failed to get Java class\n") ; -// return ; -// } -// -// // Look up the Java method we want to call. -// // The method name is passed in by the user (and needs to match exactly, including case). -// // The method should be owned by the m_HandlerObject that the user also passed in. -// // Any slip here and you get a NoSuchMethod exception and my Java VM shuts down. -// jmethodID mid = jenv->GetMethodID(cls, pJavaData->m_HandlerMethod.c_str(), "(ILjava/lang/Object;Lsml/Agent;Lsml/ClientXML;)V") ; -// -// if (mid == 0) -// { -// printf("Failed to get Java method\n") ; -// return ; -// } - // Wrap our C++ ClientXML object with a SWIG sml/ClientXML Java object so we can // pass it back to Java. // OK, time to roll up our JNI sleeves and get dirty. We need to create a new Java object. @@ -644,7 +609,6 @@ static void XMLEventHandler(sml::smlXMLEventId id, void* pUserData, sml::Agent* } // Make the method call. -// jenv->CallVoidMethod(jobj, mid, (int)id, pJavaData->m_CallbackData, pJavaData->m_AgentObject, jNewObject); jenv->CallVoidMethod(pJavaData->m_HandlerObject, pJavaData->m_Mid, (int)id, pJavaData->m_CallbackData, pJavaData->m_AgentObject, jNewObject); } @@ -699,30 +663,6 @@ static void OutputEventHandler(void* pUserData, sml::Agent* pAgent, char const* // Now try to call back to Java JNIEnv *jenv = pJavaData->GetEnv() ; -// // We start from the Java object whose method we wish to call. -// jobject jobj = pJavaData->m_HandlerObject ; -// jclass cls = jenv->GetObjectClass(jobj) ; -// -// if (cls == 0) -// { -// printf("Failed to get Java class\n") ; -// return ; -// } -// -// // Look up the Java method we want to call. -// // The method name is passed in by the user (and needs to match exactly, including case). -// // The method should be owned by the m_HandlerObject that the user also passed in. -// // Any slip here and you get a NoSuchMethod exception and my Java VM shuts down. -// // Method sig here is: -// // Object userData, String agentName, String attributeName, WMElement* wmeAdded -// jmethodID mid = jenv->GetMethodID(cls, pJavaData->m_HandlerMethod.c_str(), "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;Lsml/WMElement;)V") ; -// -// if (mid == 0) -// { -// printf("Failed to get Java method\n") ; -// return ; -// } - // Convert our C++ strings to Java strings jstring agentName = pAgent != NULL ? jenv->NewStringUTF(pAgent->GetAgentName()) : 0 ; jstring attributeName = pAttributeName != NULL ? jenv->NewStringUTF(pAttributeName) : 0 ; @@ -782,7 +722,6 @@ static void OutputEventHandler(void* pUserData, sml::Agent* pAgent, char const* } // Make the method call. -// jenv->CallVoidMethod(jobj, mid, pJavaData->m_CallbackData, agentName, attributeName, jNewObject) ; jenv->CallVoidMethod(pJavaData->m_HandlerObject, pJavaData->m_Mid, pJavaData->m_CallbackData, agentName, attributeName, jNewObject); // Cleaning up the local references just in case @@ -840,30 +779,7 @@ static void SystemEventHandler(sml::smlSystemEventId id, void* pUserData, sml::K // Now try to call back to Java JNIEnv *jenv = pJavaData->GetEnv() ; -// // We start from the Java object whose method we wish to call. -// jobject jobj = pJavaData->m_HandlerObject ; -// jclass cls = jenv->GetObjectClass(jobj) ; -// -// if (cls == 0) -// { -// printf("Failed to get Java class\n") ; -// return ; -// } -// -// // Look up the Java method we want to call. -// // The method name is passed in by the user (and needs to match exactly, including case). -// // The method should be owned by the m_HandlerObject that the user also passed in. -// // Any slip here and you get a NoSuchMethod exception and my Java VM shuts down. -// jmethodID mid = jenv->GetMethodID(cls, pJavaData->m_HandlerMethod.c_str(), "(ILjava/lang/Object;Lsml/Kernel;)V") ; -// -// if (mid == 0) -// { -// printf("Failed to get Java method\n") ; -// return ; -// } - // Make the method call. -// jenv->CallVoidMethod(jobj, mid, (int)id, pJavaData->m_CallbackData, pJavaData->m_KernelObject); jenv->CallVoidMethod(pJavaData->m_HandlerObject, pJavaData->m_Mid, (int)id, pJavaData->m_CallbackData, pJavaData->m_KernelObject); } @@ -917,30 +833,7 @@ static void UpdateEventHandler(sml::smlUpdateEventId id, void* pUserData, sml::K // Now try to call back to Java JNIEnv *jenv = pJavaData->GetEnv() ; -// // We start from the Java object whose method we wish to call. -// jobject jobj = pJavaData->m_HandlerObject ; -// jclass cls = jenv->GetObjectClass(jobj) ; -// -// if (cls == 0) -// { -// printf("Failed to get Java class\n") ; -// return ; -// } -// -// // Look up the Java method we want to call. -// // The method name is passed in by the user (and needs to match exactly, including case). -// // The method should be owned by the m_HandlerObject that the user also passed in. -// // Any slip here and you get a NoSuchMethod exception and my Java VM shuts down. -// jmethodID mid = jenv->GetMethodID(cls, pJavaData->m_HandlerMethod.c_str(), "(ILjava/lang/Object;Lsml/Kernel;I)V") ; -// -// if (mid == 0) -// { -// printf("Failed to get Java method\n") ; -// return ; -// } - // Make the method call. -// jenv->CallVoidMethod(jobj, mid, (int)id, pJavaData->m_CallbackData, pJavaData->m_KernelObject, runFlags); jenv->CallVoidMethod(pJavaData->m_HandlerObject, pJavaData->m_Mid, (int)id, pJavaData->m_CallbackData, pJavaData->m_KernelObject, runFlags); } @@ -985,84 +878,6 @@ JNIEXPORT bool JNICALL Java_sml_smlJNI_Kernel_1UnregisterForUpdateEvent(JNIEnv * return result ; } -// This is a bit ugly. We compile this header with extern "C" around it so that the public methods can be -// exposed in a DLL with C naming (not C++ mangled names). However, StringEventHandler (below) returns a std::string -// which won't compile under "C"...even though it's a static function and hence won't appear in the DLL anyway. -// The solution is to turn off extern "C" for this method and turn it back on afterwards. -#ifdef __cplusplus -} -#endif - -// This is the C++ handler which will be called by clientSML when the event fires. -// Then from here we need to call back to Java to pass back the message. -static std::string StringEventHandler(sml::smlStringEventId id, void* pUserData, sml::Kernel* pKernel, char const* pData) -{ - // The user data is the class we declared above, where we store the Java data to use in the callback. - JavaCallbackData* pJavaData = (JavaCallbackData*)pUserData ; - - // Now try to call back to Java - JNIEnv *jenv = pJavaData->GetEnv() ; - -// // We start from the Java object whose method we wish to call. -// jobject jobj = pJavaData->m_HandlerObject ; -// jclass cls = jenv->GetObjectClass(jobj) ; -// -// if (cls == 0) -// { -// printf("Failed to get Java class\n") ; -// return "Error -- failed to get Java class"; -// } -// -// // Look up the Java method we want to call. -// // The method name is passed in by the user (and needs to match exactly, including case). -// // The method should be owned by the m_HandlerObject that the user also passed in. -// // Any slip here and you get a NoSuchMethod exception and my Java VM shuts down. -// jmethodID mid = jenv->GetMethodID(cls, pJavaData->m_HandlerMethod.c_str(), "(ILjava/lang/Object;Lsml/Kernel;Ljava/lang/String;)Ljava/lang/String;") ; -// -// if (mid == 0) -// { -// printf("Failed to get Java method\n") ; -// return "Error -- failed to get Java method"; -// } - - // Create the string to return to the caller - jstring data = pData != NULL ? jenv->NewStringUTF(pData) : 0 ; - - // Make the method call. -// jstring result = (jstring)jenv->CallObjectMethod(jobj, mid, (int)id, pJavaData->m_CallbackData, pJavaData->m_KernelObject, data); - jstring result = (jstring)jenv->CallObjectMethod(pJavaData->m_HandlerObject, pJavaData->m_Mid, (int)id, pJavaData->m_CallbackData, pJavaData->m_KernelObject, data); - - // Get the returned string - std::string resultStr = "" ; - - if (result != 0) - { - // Get the C string - char const* pResult = jenv->GetStringUTFChars(result, 0); - - // Copy it into our std::string - resultStr = pResult ; - - // Release the Java string - jenv->ReleaseStringUTFChars(result, pResult); - } - - // Cleaning up the local references just in case - jenv->DeleteLocalRef(data); - - // Return the result - return resultStr ; - -} - -// This is a bit ugly. We compile this header with extern "C" around it so that the public methods can be -// exposed in a DLL with C naming (not C++ mangled names). However, StringEventHandler (above) returns a std::string -// which won't compile under "C"...even though it's a static function and hence won't appear in the DLL anyway. -// The solution is to turn off extern "C" for this method and turn it back on afterwards. -#ifdef __cplusplus -extern "C" { -#endif - // This is the hand-written JNI method for registering a callback. // I'm going to model it after the existing SWIG JNI methods so hopefully it'll be easier to patch this into SWIG eventually. JNIEXPORT jlong JNICALL Java_sml_smlJNI_Kernel_1RegisterForStringEvent(JNIEnv *jenv, jclass jcls, jlong jarg1, jint jarg2, jobject jarg3, jobject jarg4, jobject jarg6) @@ -1103,184 +918,6 @@ JNIEXPORT bool JNICALL Java_sml_smlJNI_Kernel_1UnregisterForStringEvent(JNIEnv * return result ; } -// This is the C++ handler which will be called by clientSML when the event fires. -// Then from here we need to call back to Java to pass back the message. -static const char *RhsEventHandler(sml::smlRhsEventId id, void* pUserData, sml::Agent* pAgent, - char const* pFunctionName, char const* pArgument, int *bufSize, char *buf) -{ - // Previous result was cached, meaning client should be calling again to get it - // return that result and clear the cache - static std::string prevResult; - if ( !prevResult.empty() ) - { - strncpy( buf, prevResult.c_str(), *bufSize ); - - prevResult = ""; - - return buf; - } - - // The user data is the class we declared above, where we store the Java data to use in the callback. - JavaCallbackData* pJavaData = (JavaCallbackData*)pUserData ; - - // Now try to call back to Java - JNIEnv *jenv = pJavaData->GetEnv() ; - -// // We start from the Java object whose method we wish to call. -// jobject jobj = pJavaData->m_HandlerObject ; -// jclass cls = jenv->GetObjectClass(jobj) ; -// -// if (cls == 0) -// { -// printf("Failed to get Java class\n") ; -// return "Error -- failed to get Java class" ; -// } -// -// // Look up the Java method we want to call. -// // The method name is passed in by the user (and needs to match exactly, including case). -// // The method should be owned by the m_HandlerObject that the user also passed in. -// // Any slip here and you get a NoSuchMethod exception and my Java VM shuts down. -// // Method sig here is: -// // Int eventID, Object userData, String agentName, String functionName, String argument returning a String. -// jmethodID mid = jenv->GetMethodID(cls, pJavaData->m_HandlerMethod.c_str(), "(ILjava/lang/Object;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;") ; -// -// if (mid == 0) -// { -// printf("Failed to get Java method\n") ; -// return "Error -- failed to get Java method" ; -// } - - // Convert our C++ strings to Java strings - jstring agentName = pAgent != NULL ? jenv->NewStringUTF(pAgent->GetAgentName()) : 0 ; - jstring functionName = pFunctionName != NULL ? jenv->NewStringUTF(pFunctionName) : 0 ; - jstring argument = pArgument != NULL ? jenv->NewStringUTF(pArgument) : 0 ; - - // Make the method call. -// jstring result = (jstring)jenv->CallObjectMethod(jobj, mid, (int)id, pJavaData->m_CallbackData, agentName, functionName, argument) ; - jstring result = (jstring)jenv->CallObjectMethod(pJavaData->m_HandlerObject, pJavaData->m_Mid, (int)id, pJavaData->m_CallbackData, agentName, functionName, argument) ; - - // Cleaning up the local references just in case - jenv->DeleteLocalRef(agentName); - jenv->DeleteLocalRef(functionName); - jenv->DeleteLocalRef(argument); - - // Get the returned string - std::string resultStr = "" ; - - if (result != 0) - { - // Get the C string - char const* pResult = jenv->GetStringUTFChars(result, 0); - - // Copy it into our std::string - resultStr = pResult ; - - // Release the Java string - jenv->ReleaseStringUTFChars(result, pResult); - } - - // Too long to fit in the buffer; cache result and signal client with - // NULL return value to call again with a larger buffer - if ( resultStr.length() + 1 > *bufSize ) - { - *bufSize = resultStr.length() + 1; - prevResult = resultStr; - return NULL; - } - strcpy( buf, resultStr.c_str() ); - - return buf; -} - -// This is the C++ handler which will be called by clientSML when the event fires. -// Then from here we need to call back to Java to pass back the message. -static const char *ClientMessageHandler(sml::smlRhsEventId id, void* pUserData, sml::Agent* pAgent, - char const* pFunctionName, char const* pArgument, int *bufSize, char *buf) -{ - // Previous result was cached, meaning client should be calling again to get it - // return that result and clear the cache - static std::string prevResult; - if ( !prevResult.empty() ) - { - strncpy( buf, prevResult.c_str(), *bufSize ); - - prevResult = ""; - - return buf; - } - - // The user data is the class we declared above, where we store the Java data to use in the callback. - JavaCallbackData* pJavaData = (JavaCallbackData*)pUserData ; - - // Now try to call back to Java - JNIEnv *jenv = pJavaData->GetEnv() ; - -// // We start from the Java object whose method we wish to call. -// jobject jobj = pJavaData->m_HandlerObject ; -// jclass cls = jenv->GetObjectClass(jobj) ; -// -// if (cls == 0) -// { -// printf("Failed to get Java class\n") ; -// return "Error -- failed to get Java class" ; -// } -// -// // Look up the Java method we want to call. -// // The method name is passed in by the user (and needs to match exactly, including case). -// // The method should be owned by the m_HandlerObject that the user also passed in. -// // Any slip here and you get a NoSuchMethod exception and my Java VM shuts down. -// // Method sig here is: -// // Int eventID, Object userData, String agentName, String functionName, String argument returning a String. -// jmethodID mid = jenv->GetMethodID(cls, pJavaData->m_HandlerMethod.c_str(), "(ILjava/lang/Object;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;") ; -// -// if (mid == 0) -// { -// printf("Failed to get Java method\n") ; -// return "Error -- failed to get Java method" ; -// } - - // Convert our C++ strings to Java strings - jstring agentName = pAgent != NULL ? jenv->NewStringUTF(pAgent->GetAgentName()) : 0 ; - jstring functionName = pFunctionName != NULL ? jenv->NewStringUTF(pFunctionName) : 0 ; - jstring argument = pArgument != NULL ? jenv->NewStringUTF(pArgument) : 0 ; - - // Make the method call. -// jstring result = (jstring)jenv->CallObjectMethod(jobj, mid, (int)id, pJavaData->m_CallbackData, agentName, functionName, argument) ; - jstring result = (jstring)jenv->CallObjectMethod(pJavaData->m_HandlerObject, pJavaData->m_Mid, (int)id, pJavaData->m_CallbackData, agentName, functionName, argument) ; - - // Cleaning up the local references just in case - jenv->DeleteLocalRef(agentName); - jenv->DeleteLocalRef(functionName); - jenv->DeleteLocalRef(argument); - - // Get the returned string - std::string resultStr = "" ; - - if (result != 0) - { - // Get the C string - char const* pResult = jenv->GetStringUTFChars(result, 0); - - // Copy it into our std::string - resultStr = pResult ; - - // Release the Java string - jenv->ReleaseStringUTFChars(result, pResult); - } - - // Too long to fit in the buffer; cache result and signal client with - // NULL return value to call again with a larger buffer - if ( resultStr.length() + 1 > *bufSize ) - { - *bufSize = resultStr.length() + 1; - prevResult = resultStr; - return NULL; - } - strcpy( buf, resultStr.c_str() ); - - return buf; -} - // This is the hand-written JNI method for registering a callback. // I'm going to model it after the existing SWIG JNI methods so hopefully it'll be easier to patch this into SWIG eventually. @@ -1296,7 +933,7 @@ JNIEXPORT jlong JNICALL Java_sml_smlJNI_Kernel_1AddRhsFunction(JNIEnv *jenv, jcl JavaCallbackData* pJavaData = CreateJavaCallbackData(false, jenv, jcls, jarg1, 0, jarg3, jarg4, "rhsFunctionHandler", "(ILjava/lang/Object;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;", jarg6) ; // Register our handler. When this is called we'll call back to the Java method. - pJavaData->m_CallbackID = arg1->AddRhsFunction(pFunctionName, &RhsEventHandler, pJavaData) ; + pJavaData->m_CallbackID = arg1->AddRhsFunction(pFunctionName, getRhsEventHandler(pJavaData)) ; // Release the string we got from Java jenv->ReleaseStringUTFChars(jarg2, pFunctionName); @@ -1339,7 +976,7 @@ JNIEXPORT jlong JNICALL Java_sml_smlJNI_Kernel_1RegisterForClientMessageEvent(JN JavaCallbackData* pJavaData = CreateJavaCallbackData(false, jenv, jcls, jarg1, 0, jarg3, jarg4, "clientMessageHandler", "(ILjava/lang/Object;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;", jarg6) ; // Register our handler. When this is called we'll call back to the Java method. - pJavaData->m_CallbackID = arg1->RegisterForClientMessageEvent(pFunctionName, &ClientMessageHandler, pJavaData) ; + pJavaData->m_CallbackID = arg1->RegisterForClientMessageEvent(pFunctionName, getRhsEventHandler(pJavaData)) ; // Release the string we got from Java jenv->ReleaseStringUTFChars(jarg2, pFunctionName); @@ -1380,33 +1017,10 @@ static void AgentEventHandler(sml::smlAgentEventId id, void* pUserData, sml::Age // Now try to call back to Java JNIEnv *jenv = pJavaData->GetEnv() ; -// // We start from the Java object whose method we wish to call. -// jobject jobj = pJavaData->m_HandlerObject ; -// jclass cls = jenv->GetObjectClass(jobj) ; -// -// if (cls == 0) -// { -// printf("Failed to get Java class\n") ; -// return ; -// } -// -// // Look up the Java method we want to call. -// // The method name is passed in by the user (and needs to match exactly, including case). -// // The method should be owned by the m_HandlerObject that the user also passed in. -// // Any slip here and you get a NoSuchMethod exception and my Java VM shuts down. -// jmethodID mid = jenv->GetMethodID(cls, pJavaData->m_HandlerMethod.c_str(), "(ILjava/lang/Object;Ljava/lang/String;)V") ; -// -// if (mid == 0) -// { -// printf("Failed to get Java method\n") ; -// return ; -// } - // Convert our C++ strings to Java strings jstring agentName = pAgent != NULL ? jenv->NewStringUTF(pAgent->GetAgentName()) : 0 ; // Make the method call. -// jenv->CallVoidMethod(jobj, mid, (int)id, pJavaData->m_CallbackData, agentName); jenv->CallVoidMethod(pJavaData->m_HandlerObject, pJavaData->m_Mid, (int)id, pJavaData->m_CallbackData, agentName); // Cleaning up the local references just in case diff --git a/Java/SMLJava/SConscript b/Java/SMLJava/SConscript index 390cb0a011..38969115b5 100644 --- a/Java/SMLJava/SConscript +++ b/Java/SMLJava/SConscript @@ -6,9 +6,10 @@ Import('java_env', 'AddResources') clone = java_env.Clone() deps = [clone['SML']] clone['JAVACLASSPATH'] = clone['SML'].abspath +clone['ENV']['LANG'] = 'en_US.UTF-8' jar_name = 'soar-smljava.jar' -classes = clone.Java(target = 'classes', source = 'src/main/java') +classes = clone.Java(target = 'classes', source = 'src/main/java', encoding = "UTF-8") clone.Depends(classes, clone['SML']) temp = clone.Jar( target = 'temp.jar', diff --git a/Java/SMLJava/src/main/java/edu/umich/soar/TestJavaSML.java b/Java/SMLJava/src/main/java/edu/umich/soar/TestJavaSML.java index 951294d4e6..cd66715f95 100644 --- a/Java/SMLJava/src/main/java/edu/umich/soar/TestJavaSML.java +++ b/Java/SMLJava/src/main/java/edu/umich/soar/TestJavaSML.java @@ -18,12 +18,12 @@ /******************************************************************************************** * TestJavaSML.java - * - * Description: - * + * + * Description: + * * Created on Nov 6, 2004 * @author Douglas Pearson - * + * * Developed by ThreePenny Software www.threepenny.net ********************************************************************************************/ @@ -39,7 +39,8 @@ public static class EventListener implements Agent.RunEventInterface, Agent.PrintEventInterface, Agent.ProductionEventInterface, Agent.xmlEventInterface, Agent.OutputEventInterface, Agent.OutputNotificationInterface, Kernel.AgentEventInterface, - Kernel.SystemEventInterface, Kernel.RhsFunctionInterface + Kernel.SystemEventInterface, Kernel.RhsFunctionInterface, + Kernel.ClientMessageInterface { // We'll test to make sure we can keep a ClientXML object that we're @@ -129,6 +130,13 @@ public String rhsFunctionHandler(int eventID, Object data, return "My rhs result " + argument; } + public String clientMessageHandler(int eventID, Object data, + String agentName, String functionName, String argument) { + System.out.println("Received client message event in Java for function: " + + functionName + "(" + argument + ")"); + return "My client message result " + argument; + } + public void outputEventHandler(Object data, String agentName, String attributeName, WMElement pWmeAdded) { @@ -267,6 +275,8 @@ private void Test() smlAgentEventId.smlEVENT_BEFORE_AGENT_REINITIALIZED, listener, this); long jRhsCallback = pKernel.AddRhsFunction("test-rhs", listener, this); + long jClientMessageCallback = pKernel.RegisterForClientMessageEvent( + "test-client-message", listener, this); long jTraceCallback = pAgent.RegisterForXMLEvent( smlXMLEventId.smlEVENT_XML_TRACE_OUTPUT, listener, this); long jOutputCallback = pAgent.AddOutputHandler("move", listener, this); @@ -276,6 +286,12 @@ private void Test() // Trigger an agent event by doing init-soar pAgent.InitSoar(); + String messageResult = pKernel.SendClientMessage( + pAgent, "test-client-message", "test-client-message-arg"); + if (!messageResult.equals("My client message result test-client-message-arg")) { + throw new IllegalStateException("Client message function failed"); + } + // pAgent.ExecuteCommandLine("watch 5") ; // Now we should match (if we really loaded the tictactoe example rules) @@ -390,15 +406,15 @@ public void reportResult(String testName, boolean success, String msg) if (success) { - outfile.write("Tests SUCCEEDED"); + outfile.write("✅ Tests SUCCEEDED"); outfile.write(msg); - System.out.println("Tests SUCCEEDED"); + System.out.println("✅ Tests SUCCEEDED"); } else { - outfile.write("ERROR *** Tests FAILED"); + outfile.write("❌ ERROR *** Tests FAILED"); outfile.write(msg); - System.out.println("ERROR *** Tests FAILED"); + System.out.println("❌ ERROR *** Tests FAILED"); System.out.println(msg); } @@ -502,7 +518,7 @@ else if (listener) } finally { - // reportResult("testjavasml", success, msg) ; + reportResult("testjavasml", success, "") ; System.exit(success ? 0 : 1); } }