/* * Copyright (C) 2003, 2004, 2005, 2007, 2009 Apple Inc. All rights reserved. * Copyright 2010, The Android Open Source Project * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "JNIBridge.h" #if ENABLE(MAC_JAVA_BRIDGE) #include "CString.h" #include "JNIUtility.h" #include "JNIUtilityPrivate.h" #include "StringBuilder.h" #include "runtime_array.h" #include "runtime_object.h" #include "runtime_root.h" #include #include #ifdef NDEBUG #define JS_LOG(formatAndArgs...) ((void)0) #else #define JS_LOG(formatAndArgs...) { \ fprintf(stderr, "%s:%d -- %s: ", __FILE__, __LINE__, __FUNCTION__); \ fprintf(stderr, formatAndArgs); \ } #endif using namespace JSC; using namespace JSC::Bindings; using namespace WebCore; JavaParameter::JavaParameter(JNIEnv* env, jstring type) { m_type = JavaString(env, type); m_JNIType = JNITypeFromClassName(m_type.UTF8String()); } JavaField::JavaField(JNIEnv* env, jobject aField) { // Get field type jobject fieldType = callJNIMethod(aField, "getType", "()Ljava/lang/Class;"); jstring fieldTypeName = static_cast(callJNIMethod(fieldType, "getName", "()Ljava/lang/String;")); m_type = JavaString(env, fieldTypeName); m_JNIType = JNITypeFromClassName(m_type.UTF8String()); // Get field name jstring fieldName = static_cast(callJNIMethod(aField, "getName", "()Ljava/lang/String;")); m_name = JavaString(env, fieldName); m_field = new JObjectWrapper(aField); } JSValue JavaArray::convertJObjectToArray(ExecState* exec, jobject anObject, const char* type, PassRefPtr rootObject) { if (type[0] != '[') return jsUndefined(); return new (exec) RuntimeArray(exec, new JavaArray(anObject, type, rootObject)); } jvalue JavaField::dispatchValueFromInstance(ExecState* exec, const JavaInstance* instance, const char* name, const char* sig, JNIType returnType) const { jobject jinstance = instance->javaInstance(); jobject fieldJInstance = m_field->m_instance; JNIEnv* env = getJNIEnv(); jvalue result; bzero(&result, sizeof(jvalue)); jclass cls = env->GetObjectClass(fieldJInstance); if (cls) { jmethodID mid = env->GetMethodID(cls, name, sig); if (mid) { RootObject* rootObject = instance->rootObject(); if (rootObject && rootObject->nativeHandle()) { JSValue exceptionDescription; jvalue args[1]; args[0].l = jinstance; dispatchJNICall(exec, rootObject->nativeHandle(), fieldJInstance, false, returnType, mid, args, result, 0, exceptionDescription); if (exceptionDescription) throwError(exec, GeneralError, exceptionDescription.toString(exec)); } } } return result; } JSValue JavaField::valueFromInstance(ExecState* exec, const Instance* i) const { const JavaInstance* instance = static_cast(i); JSValue jsresult = jsUndefined(); switch (m_JNIType) { case array_type: case object_type: { jvalue result = dispatchValueFromInstance(exec, instance, "get", "(Ljava/lang/Object;)Ljava/lang/Object;", object_type); jobject anObject = result.l; const char* arrayType = type(); if (arrayType[0] == '[') jsresult = JavaArray::convertJObjectToArray(exec, anObject, arrayType, instance->rootObject()); else if (anObject) jsresult = JavaInstance::create(anObject, instance->rootObject())->createRuntimeObject(exec); } break; case boolean_type: jsresult = jsBoolean(dispatchValueFromInstance(exec, instance, "getBoolean", "(Ljava/lang/Object;)Z", boolean_type).z); break; case byte_type: case char_type: case short_type: case int_type: { jint value; jvalue result = dispatchValueFromInstance(exec, instance, "getInt", "(Ljava/lang/Object;)I", int_type); value = result.i; jsresult = jsNumber(exec, static_cast(value)); } break; case long_type: case float_type: case double_type: { jdouble value; jvalue result = dispatchValueFromInstance(exec, instance, "getDouble", "(Ljava/lang/Object;)D", double_type); value = result.i; jsresult = jsNumber(exec, static_cast(value)); } break; default: break; } JS_LOG("getting %s = %s\n", UString(name()).UTF8String().c_str(), jsresult.toString(exec).ascii()); return jsresult; } void JavaField::dispatchSetValueToInstance(ExecState* exec, const JavaInstance* instance, jvalue javaValue, const char* name, const char* sig) const { jobject jinstance = instance->javaInstance(); jobject fieldJInstance = m_field->m_instance; JNIEnv* env = getJNIEnv(); jclass cls = env->GetObjectClass(fieldJInstance); if (cls) { jmethodID mid = env->GetMethodID(cls, name, sig); if (mid) { RootObject* rootObject = instance->rootObject(); if (rootObject && rootObject->nativeHandle()) { JSValue exceptionDescription; jvalue args[2]; jvalue result; args[0].l = jinstance; args[1] = javaValue; dispatchJNICall(exec, rootObject->nativeHandle(), fieldJInstance, false, void_type, mid, args, result, 0, exceptionDescription); if (exceptionDescription) throwError(exec, GeneralError, exceptionDescription.toString(exec)); } } } } void JavaField::setValueToInstance(ExecState* exec, const Instance* i, JSValue aValue) const { const JavaInstance* instance = static_cast(i); jvalue javaValue = convertValueToJValue(exec, aValue, m_JNIType, type()); JS_LOG("setting value %s to %s\n", UString(name()).UTF8String().c_str(), aValue.toString(exec).ascii()); switch (m_JNIType) { case array_type: case object_type: { dispatchSetValueToInstance(exec, instance, javaValue, "set", "(Ljava/lang/Object;Ljava/lang/Object;)V"); } break; case boolean_type: { dispatchSetValueToInstance(exec, instance, javaValue, "setBoolean", "(Ljava/lang/Object;Z)V"); } break; case byte_type: { dispatchSetValueToInstance(exec, instance, javaValue, "setByte", "(Ljava/lang/Object;B)V"); } break; case char_type: { dispatchSetValueToInstance(exec, instance, javaValue, "setChar", "(Ljava/lang/Object;C)V"); } break; case short_type: { dispatchSetValueToInstance(exec, instance, javaValue, "setShort", "(Ljava/lang/Object;S)V"); } break; case int_type: { dispatchSetValueToInstance(exec, instance, javaValue, "setInt", "(Ljava/lang/Object;I)V"); } break; case long_type: { dispatchSetValueToInstance(exec, instance, javaValue, "setLong", "(Ljava/lang/Object;J)V"); } break; case float_type: { dispatchSetValueToInstance(exec, instance, javaValue, "setFloat", "(Ljava/lang/Object;F)V"); } break; case double_type: { dispatchSetValueToInstance(exec, instance, javaValue, "setDouble", "(Ljava/lang/Object;D)V"); } break; default: break; } } JavaMethod::JavaMethod(JNIEnv* env, jobject aMethod) { // Get return type jobject returnType = callJNIMethod(aMethod, "getReturnType", "()Ljava/lang/Class;"); jstring returnTypeName = static_cast(callJNIMethod(returnType, "getName", "()Ljava/lang/String;")); m_returnType = JavaString(env, returnTypeName); m_JNIReturnType = JNITypeFromClassName(m_returnType.UTF8String()); env->DeleteLocalRef(returnType); env->DeleteLocalRef(returnTypeName); // Get method name jstring methodName = static_cast(callJNIMethod(aMethod, "getName", "()Ljava/lang/String;")); m_name = JavaString(env, methodName); env->DeleteLocalRef(methodName); // Get parameters jarray jparameters = static_cast(callJNIMethod(aMethod, "getParameterTypes", "()[Ljava/lang/Class;")); m_numParameters = env->GetArrayLength(jparameters); m_parameters = new JavaParameter[m_numParameters]; for (int i = 0; i < m_numParameters; i++) { jobject aParameter = env->GetObjectArrayElement(static_cast(jparameters), i); jstring parameterName = static_cast(callJNIMethod(aParameter, "getName", "()Ljava/lang/String;")); m_parameters[i] = JavaParameter(env, parameterName); env->DeleteLocalRef(aParameter); env->DeleteLocalRef(parameterName); } env->DeleteLocalRef(jparameters); // Created lazily. m_signature = 0; m_methodID = 0; jclass modifierClass = env->FindClass("java/lang/reflect/Modifier"); int modifiers = callJNIMethod(aMethod, "getModifiers", "()I"); m_isStatic = static_cast(callJNIStaticMethod(modifierClass, "isStatic", "(I)Z", modifiers)); env->DeleteLocalRef(modifierClass); } JavaMethod::~JavaMethod() { if (m_signature) free(m_signature); delete[] m_parameters; }; // JNI method signatures use '/' between components of a class name, but // we get '.' between components from the reflection API. static void appendClassName(StringBuilder& builder, const char* className) { ASSERT(JSLock::lockCount() > 0); char* c = strdup(className); char* result = c; while (*c) { if (*c == '.') *c = '/'; c++; } builder.append(result); free(result); } const char* JavaMethod::signature() const { if (!m_signature) { JSLock lock(SilenceAssertionsOnly); StringBuilder signatureBuilder; signatureBuilder.append("("); for (int i = 0; i < m_numParameters; i++) { JavaParameter* aParameter = parameterAt(i); JNIType type = aParameter->getJNIType(); if (type == array_type) appendClassName(signatureBuilder, aParameter->type()); else { signatureBuilder.append(signatureFromPrimitiveType(type)); if (type == object_type) { appendClassName(signatureBuilder, aParameter->type()); signatureBuilder.append(";"); } } } signatureBuilder.append(")"); const char* returnType = m_returnType.UTF8String(); if (m_JNIReturnType == array_type) appendClassName(signatureBuilder, returnType); else { signatureBuilder.append(signatureFromPrimitiveType(m_JNIReturnType)); if (m_JNIReturnType == object_type) { appendClassName(signatureBuilder, returnType); signatureBuilder.append(";"); } } String signatureString = signatureBuilder.toString(); m_signature = strdup(signatureString.utf8().data()); } return m_signature; } JNIType JavaMethod::JNIReturnType() const { return m_JNIReturnType; } jmethodID JavaMethod::methodID(jobject obj) const { if (!m_methodID) m_methodID = getMethodID(obj, m_name.UTF8String(), signature()); return m_methodID; } JavaArray::JavaArray(jobject array, const char* type, PassRefPtr rootObject) : Array(rootObject) { m_array = new JObjectWrapper(array); // Java array are fixed length, so we can cache length. JNIEnv* env = getJNIEnv(); m_length = env->GetArrayLength(static_cast(m_array->m_instance)); m_type = strdup(type); m_rootObject = rootObject; } JavaArray::~JavaArray() { free(const_cast(m_type)); } RootObject* JavaArray::rootObject() const { return m_rootObject && m_rootObject->isValid() ? m_rootObject.get() : 0; } void JavaArray::setValueAt(ExecState* exec, unsigned index, JSValue aValue) const { JNIEnv* env = getJNIEnv(); char* javaClassName = 0; JNIType arrayType = JNITypeFromPrimitiveType(m_type[1]); if (m_type[1] == 'L') { // The type of the array will be something like: // "[Ljava.lang.string;". This is guaranteed, so no need // for extra sanity checks. javaClassName = strdup(&m_type[2]); javaClassName[strchr(javaClassName, ';')-javaClassName] = 0; } jvalue aJValue = convertValueToJValue(exec, aValue, arrayType, javaClassName); switch (arrayType) { case object_type: { env->SetObjectArrayElement(static_cast(javaArray()), index, aJValue.l); break; } case boolean_type: { env->SetBooleanArrayRegion(static_cast(javaArray()), index, 1, &aJValue.z); break; } case byte_type: { env->SetByteArrayRegion(static_cast(javaArray()), index, 1, &aJValue.b); break; } case char_type: { env->SetCharArrayRegion(static_cast(javaArray()), index, 1, &aJValue.c); break; } case short_type: { env->SetShortArrayRegion(static_cast(javaArray()), index, 1, &aJValue.s); break; } case int_type: { env->SetIntArrayRegion(static_cast(javaArray()), index, 1, &aJValue.i); break; } case long_type: { env->SetLongArrayRegion(static_cast(javaArray()), index, 1, &aJValue.j); } case float_type: { env->SetFloatArrayRegion(static_cast(javaArray()), index, 1, &aJValue.f); break; } case double_type: { env->SetDoubleArrayRegion(static_cast(javaArray()), index, 1, &aJValue.d); break; } default: break; } if (javaClassName) free(const_cast(javaClassName)); } JSValue JavaArray::valueAt(ExecState* exec, unsigned index) const { JNIEnv* env = getJNIEnv(); JNIType arrayType = JNITypeFromPrimitiveType(m_type[1]); switch (arrayType) { case object_type: { jobjectArray objectArray = static_cast(javaArray()); jobject anObject; anObject = env->GetObjectArrayElement(objectArray, index); // No object? if (!anObject) return jsNull(); // Nested array? if (m_type[1] == '[') return JavaArray::convertJObjectToArray(exec, anObject, m_type + 1, rootObject()); // or array of other object type? return JavaInstance::create(anObject, rootObject())->createRuntimeObject(exec); } case boolean_type: { jbooleanArray booleanArray = static_cast(javaArray()); jboolean aBoolean; env->GetBooleanArrayRegion(booleanArray, index, 1, &aBoolean); return jsBoolean(aBoolean); } case byte_type: { jbyteArray byteArray = static_cast(javaArray()); jbyte aByte; env->GetByteArrayRegion(byteArray, index, 1, &aByte); return jsNumber(exec, aByte); } case char_type: { jcharArray charArray = static_cast(javaArray()); jchar aChar; env->GetCharArrayRegion(charArray, index, 1, &aChar); return jsNumber(exec, aChar); break; } case short_type: { jshortArray shortArray = static_cast(javaArray()); jshort aShort; env->GetShortArrayRegion(shortArray, index, 1, &aShort); return jsNumber(exec, aShort); } case int_type: { jintArray intArray = static_cast(javaArray()); jint anInt; env->GetIntArrayRegion(intArray, index, 1, &anInt); return jsNumber(exec, anInt); } case long_type: { jlongArray longArray = static_cast(javaArray()); jlong aLong; env->GetLongArrayRegion(longArray, index, 1, &aLong); return jsNumber(exec, aLong); } case float_type: { jfloatArray floatArray = static_cast(javaArray()); jfloat aFloat; env->GetFloatArrayRegion(floatArray, index, 1, &aFloat); return jsNumber(exec, aFloat); } case double_type: { jdoubleArray doubleArray = static_cast(javaArray()); jdouble aDouble; env->GetDoubleArrayRegion(doubleArray, index, 1, &aDouble); return jsNumber(exec, aDouble); } default: break; } return jsUndefined(); } unsigned int JavaArray::getLength() const { return m_length; } #endif // ENABLE(MAC_JAVA_BRIDGE)