/* * Copyright (c) 2013-2016 Chukong Technologies Inc. * Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include "scripting/js-bindings/manual/JavaScriptJavaBridge.h" #include "platform/android/jni/JniHelper.h" #include "cocos/scripting/js-bindings/jswrapper/SeApi.h" #include "cocos/scripting/js-bindings/manual/jsb_conversions.hpp" #include "cocos/base/ccUTF8.h" #include #include #include #ifdef LOG_TAG #undef LOG_TAG #endif #define LOG_TAG "JavaScriptJavaBridge" #ifndef ORG_JAVABRIDGE_CLASS_NAME #define ORG_JAVABRIDGE_CLASS_NAME org_cocos2dx_lib_Cocos2dxJavascriptJavaBridge #endif #define JNI_JSJAVABRIDGE(FUNC) JNI_METHOD1(ORG_JAVABRIDGE_CLASS_NAME,FUNC) extern "C" { JNIEXPORT jint JNICALL JNI_JSJAVABRIDGE(evalString) (JNIEnv *env, jclass cls, jstring value) { if (!se::ScriptEngine::getInstance()->isValid()) { CCLOG("ScriptEngine has not been initialized"); return 0; } se::AutoHandleScope hs; bool strFlag = false; std::string strValue = cocos2d::StringUtils::getStringUTFCharsJNI(env, value, &strFlag); if (!strFlag) { CCLOG("JavaScriptJavaBridge_evalString error, invalid string code"); return 0; } se::ScriptEngine::getInstance()->evalString(strValue.c_str()); return 1; } } // extern "C" #define JSJ_ERR_OK (0) #define JSJ_ERR_TYPE_NOT_SUPPORT (-1) #define JSJ_ERR_INVALID_SIGNATURES (-2) #define JSJ_ERR_METHOD_NOT_FOUND (-3) #define JSJ_ERR_EXCEPTION_OCCURRED (-4) #define JSJ_ERR_VM_THREAD_DETACHED (-5) #define JSJ_ERR_VM_FAILURE (-6) #define JSJ_ERR_CLASS_NOT_FOUND (-7) class JavaScriptJavaBridge { public: enum class ValueType: char { INVALID, VOID, INTEGER, LONG, FLOAT, BOOLEAN, STRING, VECTOR, FUNCTION }; typedef std::vector ValueTypes; typedef union { int intValue; long longValue; float floatValue; int boolValue; std::string *stringValue; } ReturnValue; class CallInfo { public: CallInfo(const char *className, const char *methodName, const char *methodSig) : m_valid(false) , m_error(JSJ_ERR_OK) , m_className(className) , m_methodName(methodName) , m_methodSig(methodSig) , m_returnType(ValueType::VOID) , m_argumentsCount(0) , m_retjstring(NULL) , m_env(NULL) , m_classID(NULL) , m_methodID(NULL) { memset(&m_ret, 0, sizeof(m_ret)); m_valid = validateMethodSig() && getMethodInfo(); } ~CallInfo(); bool isValid() { return m_valid; } int getErrorCode() { return m_error; } JNIEnv *getEnv() { return m_env; } ValueType argumentTypeAtIndex(size_t index) { return m_argumentsType.at(index); } int getArgumentsCount(){ return m_argumentsCount; } ValueType getReturnValueType(){ return m_returnType; } ReturnValue getReturnValue(){ return m_ret; } bool execute(); bool executeWithArgs(jvalue *args); private: bool m_valid; int m_error; std::string m_className; std::string m_methodName; std::string m_methodSig; int m_argumentsCount; ValueTypes m_argumentsType; ValueType m_returnType; ReturnValue m_ret; jstring m_retjstring; JNIEnv *m_env; jclass m_classID; jmethodID m_methodID; bool validateMethodSig(); bool getMethodInfo(); ValueType checkType(const std::string& sig, size_t *pos); }; static bool convertReturnValue(ReturnValue retValue, ValueType type, se::Value* ret); }; JavaScriptJavaBridge::CallInfo::~CallInfo() { m_env->DeleteLocalRef(m_classID); if (m_returnType == ValueType::STRING && m_ret.stringValue) { m_env->DeleteLocalRef(m_retjstring); delete m_ret.stringValue; } } bool JavaScriptJavaBridge::CallInfo::execute() { switch (m_returnType) { case JavaScriptJavaBridge::ValueType::VOID: m_env->CallStaticVoidMethod(m_classID, m_methodID); break; case JavaScriptJavaBridge::ValueType::INTEGER: m_ret.intValue = m_env->CallStaticIntMethod(m_classID, m_methodID); break; case JavaScriptJavaBridge::ValueType::LONG: m_ret.longValue = m_env->CallStaticLongMethod(m_classID, m_methodID); break; case JavaScriptJavaBridge::ValueType::FLOAT: m_ret.floatValue = m_env->CallStaticFloatMethod(m_classID, m_methodID); break; case JavaScriptJavaBridge::ValueType::BOOLEAN: m_ret.boolValue = m_env->CallStaticBooleanMethod(m_classID, m_methodID); break; case JavaScriptJavaBridge::ValueType::STRING: { m_retjstring = (jstring)m_env->CallStaticObjectMethod(m_classID, m_methodID); if(m_env->ExceptionCheck()) { m_env->ExceptionDescribe(); m_env->ExceptionClear(); m_retjstring = nullptr; } if (m_retjstring) { std::string strValue = cocos2d::StringUtils::getStringUTFCharsJNI(m_env, m_retjstring); m_ret.stringValue = new std::string(strValue); } else m_ret.stringValue = nullptr; break; } default: m_error = JSJ_ERR_TYPE_NOT_SUPPORT; SE_LOGD("Return type '%d' is not supported", static_cast(m_returnType)); return false; } if (m_env->ExceptionCheck() == JNI_TRUE) { m_env->ExceptionDescribe(); m_env->ExceptionClear(); m_error = JSJ_ERR_EXCEPTION_OCCURRED; return false; } return true; } bool JavaScriptJavaBridge::CallInfo::executeWithArgs(jvalue *args) { switch (m_returnType) { case JavaScriptJavaBridge::ValueType::VOID: m_env->CallStaticVoidMethodA(m_classID, m_methodID, args); break; case JavaScriptJavaBridge::ValueType::INTEGER: m_ret.intValue = m_env->CallStaticIntMethodA(m_classID, m_methodID, args); break; case JavaScriptJavaBridge::ValueType::LONG: m_ret.longValue = m_env->CallStaticIntMethodA(m_classID, m_methodID, args); break; case JavaScriptJavaBridge::ValueType::FLOAT: m_ret.floatValue = m_env->CallStaticFloatMethodA(m_classID, m_methodID, args); break; case JavaScriptJavaBridge::ValueType::BOOLEAN: m_ret.boolValue = m_env->CallStaticBooleanMethodA(m_classID, m_methodID, args); break; case JavaScriptJavaBridge::ValueType::STRING: { m_retjstring = (jstring)m_env->CallStaticObjectMethodA(m_classID, m_methodID, args); std::string strValue = cocos2d::StringUtils::getStringUTFCharsJNI(m_env, m_retjstring); m_ret.stringValue = new std::string(strValue); break; } default: m_error = JSJ_ERR_TYPE_NOT_SUPPORT; SE_LOGD("Return type '%d' is not supported", static_cast(m_returnType)); return false; } if (m_env->ExceptionCheck() == JNI_TRUE) { m_env->ExceptionDescribe(); m_env->ExceptionClear(); m_error = JSJ_ERR_EXCEPTION_OCCURRED; return false; } return true; } bool JavaScriptJavaBridge::CallInfo::validateMethodSig() { size_t len = m_methodSig.length(); if (len < 3 || m_methodSig[0] != '(') // min sig is "()V" { m_error = JSJ_ERR_INVALID_SIGNATURES; return false; } size_t pos = 1; while (pos < len && m_methodSig[pos] != ')') { JavaScriptJavaBridge::ValueType type = checkType(m_methodSig, &pos); if (type == ValueType::INVALID) return false; m_argumentsCount++; m_argumentsType.push_back(type); pos++; } if (pos >= len || m_methodSig[pos] != ')') { m_error = JSJ_ERR_INVALID_SIGNATURES; return false; } pos++; m_returnType = checkType(m_methodSig, &pos); return true; } JavaScriptJavaBridge::ValueType JavaScriptJavaBridge::CallInfo::checkType(const std::string& sig, size_t *pos) { switch (sig[*pos]) { case 'I': return JavaScriptJavaBridge::ValueType::INTEGER; case 'J': return JavaScriptJavaBridge::ValueType::LONG; case 'F': return JavaScriptJavaBridge::ValueType::FLOAT; case 'Z': return JavaScriptJavaBridge::ValueType::BOOLEAN; case 'V': return JavaScriptJavaBridge::ValueType::VOID; case 'L': size_t pos2 = sig.find_first_of(';', *pos + 1); if (pos2 == std::string::npos) { m_error = JSJ_ERR_INVALID_SIGNATURES; return ValueType::INVALID; } const std::string t = sig.substr(*pos, pos2 - *pos + 1); if (t.compare("Ljava/lang/String;") == 0) { *pos = pos2; return ValueType::STRING; } else if (t.compare("Ljava/util/Vector;") == 0) { *pos = pos2; return ValueType::VECTOR; } else { m_error = JSJ_ERR_TYPE_NOT_SUPPORT; return ValueType::INVALID; } } m_error = JSJ_ERR_TYPE_NOT_SUPPORT; return ValueType::INVALID; } bool JavaScriptJavaBridge::CallInfo::getMethodInfo() { m_methodID = 0; m_env = 0; JavaVM* jvm = cocos2d::JniHelper::getJavaVM(); jint ret = jvm->GetEnv((void**)&m_env, JNI_VERSION_1_4); switch (ret) { case JNI_OK: break; case JNI_EDETACHED : if (jvm->AttachCurrentThread(&m_env, NULL) < 0) { SE_LOGD("%s", "Failed to get the environment using AttachCurrentThread()"); m_error = JSJ_ERR_VM_THREAD_DETACHED; return false; } break; case JNI_EVERSION : default : SE_LOGD("%s", "Failed to get the environment using GetEnv()"); m_error = JSJ_ERR_VM_FAILURE; return false; } jstring _jstrClassName = m_env->NewStringUTF(m_className.c_str()); m_classID = (jclass) m_env->CallObjectMethod(cocos2d::JniHelper::classloader, cocos2d::JniHelper::loadclassMethod_methodID, _jstrClassName); if (NULL == m_classID) { SE_LOGD("Classloader failed to find class of %s", m_className.c_str()); m_env->DeleteLocalRef(_jstrClassName); m_env->ExceptionClear(); m_error = JSJ_ERR_CLASS_NOT_FOUND; return false; } m_env->DeleteLocalRef(_jstrClassName); m_methodID = m_env->GetStaticMethodID(m_classID, m_methodName.c_str(), m_methodSig.c_str()); if (!m_methodID) { m_env->ExceptionClear(); SE_LOGD("Failed to find method id of %s.%s %s", m_className.c_str(), m_methodName.c_str(), m_methodSig.c_str()); m_error = JSJ_ERR_METHOD_NOT_FOUND; return false; } return true; } bool JavaScriptJavaBridge::convertReturnValue(ReturnValue retValue, ValueType type, se::Value* ret) { assert(ret != nullptr); switch (type) { case JavaScriptJavaBridge::ValueType::INTEGER: ret->setInt32(retValue.intValue); break; case JavaScriptJavaBridge::ValueType::LONG: ret->setLong(retValue.longValue); break; case JavaScriptJavaBridge::ValueType::FLOAT: ret->setFloat(retValue.floatValue); break; case JavaScriptJavaBridge::ValueType::BOOLEAN: ret->setBoolean(retValue.boolValue); break; case JavaScriptJavaBridge::ValueType::STRING: if (retValue.stringValue) ret->setString(*retValue.stringValue); else ret->setNull(); break; default: ret->setUndefined(); break; } return true; } se::Class* __jsb_JavaScriptJavaBridge_class = nullptr; static bool JavaScriptJavaBridge_finalize(se::State& s) { JavaScriptJavaBridge* cobj = (JavaScriptJavaBridge*)s.nativeThisObject(); delete cobj; return true; } SE_BIND_FINALIZE_FUNC(JavaScriptJavaBridge_finalize) static bool JavaScriptJavaBridge_constructor(se::State& s) { JavaScriptJavaBridge* cobj = new (std::nothrow) JavaScriptJavaBridge(); s.thisObject()->setPrivateData(cobj); return true; } SE_BIND_CTOR(JavaScriptJavaBridge_constructor, __jsb_JavaScriptJavaBridge_class, JavaScriptJavaBridge_finalize) static bool JavaScriptJavaBridge_callStaticMethod(se::State& s) { const auto& args = s.args(); int argc = (int)args.size(); if (argc == 3) { bool ok = false; std::string clsName, methodName, methodSig; ok = seval_to_std_string(args[0], &clsName); SE_PRECONDITION2(ok, false, "Converting class name failed!"); ok = seval_to_std_string(args[1], &methodName); SE_PRECONDITION2(ok, false, "Converting method name failed!"); ok = seval_to_std_string(args[2], &methodSig); SE_PRECONDITION2(ok, false, "Converting method signature failed!"); JavaScriptJavaBridge::CallInfo call(clsName.c_str(), methodName.c_str(), methodSig.c_str()); if (call.isValid()) { ok = call.execute(); int errorCode = call.getErrorCode(); if (!ok || errorCode < 0) { SE_REPORT_ERROR("call result code: %d", call.getErrorCode()); return false; } JavaScriptJavaBridge::convertReturnValue(call.getReturnValue(), call.getReturnValueType(), &s.rval()); return true; } SE_REPORT_ERROR("JavaScriptJavaBridge::CallInfo isn't valid!"); return false; } else if (argc > 3) { bool ok = false; std::string clsName, methodName, methodSig; ok = seval_to_std_string(args[0], &clsName); SE_PRECONDITION2(ok, false, "Converting class name failed!"); ok = seval_to_std_string(args[1], &methodName); SE_PRECONDITION2(ok, false, "Converting method name failed!"); ok = seval_to_std_string(args[2], &methodSig); SE_PRECONDITION2(ok, false, "Converting method signature failed!"); JavaScriptJavaBridge::CallInfo call(clsName.c_str(), methodName.c_str(), methodSig.c_str()); if (call.isValid() && call.getArgumentsCount() == (argc - 3)) { int count = argc - 3; jvalue* jargs = new jvalue[count]; std::vector toReleaseObjects; for (int i = 0; i < count; ++i) { int index = i + 3; switch (call.argumentTypeAtIndex(i)) { case JavaScriptJavaBridge::ValueType::INTEGER: { int integer = 0; seval_to_int32(args[index], &integer); jargs[i].i = integer; break; } case JavaScriptJavaBridge::ValueType::LONG: { long longVal = 0L; seval_to_long(args[index], &longVal); jargs[i].j = longVal; break; } case JavaScriptJavaBridge::ValueType::FLOAT: { float floatNumber = 0.0f; seval_to_float(args[index], &floatNumber); jargs[i].f = floatNumber; break; } case JavaScriptJavaBridge::ValueType::BOOLEAN: { jargs[i].z = args[index].isBoolean() && args[index].toBoolean() ? JNI_TRUE : JNI_FALSE; break; } case JavaScriptJavaBridge::ValueType::STRING: { const auto &arg = args[index]; if (arg.isNull() || arg.isUndefined()) jargs[i].l = nullptr; else { std::string str; seval_to_std_string(args[index], &str); jargs[i].l = call.getEnv()->NewStringUTF(str.c_str()); toReleaseObjects.push_back(jargs[i].l); } break; } default: SE_REPORT_ERROR("Unsupport type of parameter %d", i); break; } } ok = call.executeWithArgs(jargs); for (const auto& obj : toReleaseObjects) { call.getEnv()->DeleteLocalRef(obj); } if (jargs) delete[] jargs; int errorCode = call.getErrorCode(); if (!ok || errorCode < 0) { SE_REPORT_ERROR("js_JSJavaBridge : call result code: %d", errorCode); return false; } JavaScriptJavaBridge::convertReturnValue(call.getReturnValue(), call.getReturnValueType(), &s.rval()); return true; } SE_REPORT_ERROR("call valid: %d, call.getArgumentsCount()= %d", call.isValid(), call.getArgumentsCount()); return false; } SE_REPORT_ERROR("wrong number of arguments: %d, was expecting >=3", argc); return false; } SE_BIND_FUNC(JavaScriptJavaBridge_callStaticMethod) bool register_javascript_java_bridge(se::Object* obj) { se::Class* cls = se::Class::create("JavascriptJavaBridge", obj, nullptr, _SE(JavaScriptJavaBridge_constructor)); cls->defineFinalizeFunction(_SE(JavaScriptJavaBridge_finalize)); cls->defineFunction("callStaticMethod", _SE(JavaScriptJavaBridge_callStaticMethod)); cls->install(); __jsb_JavaScriptJavaBridge_class = cls; se::ScriptEngine::getInstance()->clearException(); return true; }