1/*
2 * Copyright (C) 2003, 2008, 2010 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "JavaInstanceJSC.h"
28
29#if ENABLE(JAVA_BRIDGE)
30
31#include "JavaRuntimeObject.h"
32#include "JNIUtilityPrivate.h"
33#include "JSDOMBinding.h"
34#include "JavaArrayJSC.h"
35#include "JavaClassJSC.h"
36#include "JavaMethod.h"
37#include "JavaString.h"
38#include "Logging.h"
39#include "jni_jsobject.h"
40#include "runtime_method.h"
41#include "runtime_object.h"
42#include "runtime_root.h"
43#include <runtime/ArgList.h>
44#include <runtime/Error.h>
45#include <runtime/FunctionPrototype.h>
46#include <runtime/JSLock.h>
47
48using namespace JSC::Bindings;
49using namespace JSC;
50using namespace WebCore;
51
52JavaInstance::JavaInstance(jobject instance, PassRefPtr<RootObject> rootObject)
53    : Instance(rootObject)
54{
55    m_instance = new JobjectWrapper(instance);
56    m_class = 0;
57}
58
59JavaInstance::~JavaInstance()
60{
61    delete m_class;
62}
63
64RuntimeObject* JavaInstance::newRuntimeObject(ExecState* exec)
65{
66    return new (exec) JavaRuntimeObject(exec, exec->lexicalGlobalObject(), this);
67}
68
69#define NUM_LOCAL_REFS 64
70
71void JavaInstance::virtualBegin()
72{
73    getJNIEnv()->PushLocalFrame(NUM_LOCAL_REFS);
74}
75
76void JavaInstance::virtualEnd()
77{
78    getJNIEnv()->PopLocalFrame(0);
79}
80
81Class* JavaInstance::getClass() const
82{
83    if (!m_class)
84        m_class = new JavaClass (m_instance->m_instance);
85    return m_class;
86}
87
88JSValue JavaInstance::stringValue(ExecState* exec) const
89{
90    JSLock lock(SilenceAssertionsOnly);
91
92    jstring stringValue = (jstring)callJNIMethod<jobject>(m_instance->m_instance, "toString", "()Ljava/lang/String;");
93
94    // Should throw a JS exception, rather than returning ""? - but better than a null dereference.
95    if (!stringValue)
96        return jsString(exec, UString());
97
98    JNIEnv* env = getJNIEnv();
99    const jchar* c = getUCharactersFromJStringInEnv(env, stringValue);
100    UString u((const UChar*)c, (int)env->GetStringLength(stringValue));
101    releaseUCharactersForJStringInEnv(env, stringValue, c);
102    return jsString(exec, u);
103}
104
105JSValue JavaInstance::numberValue(ExecState*) const
106{
107    jdouble doubleValue = callJNIMethod<jdouble>(m_instance->m_instance, "doubleValue", "()D");
108    return jsNumber(doubleValue);
109}
110
111JSValue JavaInstance::booleanValue() const
112{
113    jboolean booleanValue = callJNIMethod<jboolean>(m_instance->m_instance, "booleanValue", "()Z");
114    return jsBoolean(booleanValue);
115}
116
117class JavaRuntimeMethod : public RuntimeMethod {
118public:
119    JavaRuntimeMethod(ExecState* exec, JSGlobalObject* globalObject, const Identifier& name, Bindings::MethodList& list)
120        // FIXME: deprecatedGetDOMStructure uses the prototype off of the wrong global object
121        // We need to pass in the right global object for "i".
122        : RuntimeMethod(exec, globalObject, WebCore::deprecatedGetDOMStructure<JavaRuntimeMethod>(exec), name, list)
123    {
124        ASSERT(inherits(&s_info));
125    }
126
127    static Structure* createStructure(JSGlobalData& globalData, JSValue prototype)
128    {
129        return Structure::create(globalData, prototype, TypeInfo(ObjectType, StructureFlags), AnonymousSlotCount, &s_info);
130    }
131
132    static const ClassInfo s_info;
133};
134
135const ClassInfo JavaRuntimeMethod::s_info = { "JavaRuntimeMethod", &RuntimeMethod::s_info, 0, 0 };
136
137JSValue JavaInstance::getMethod(ExecState* exec, const Identifier& propertyName)
138{
139    MethodList methodList = getClass()->methodsNamed(propertyName, this);
140    return new (exec) JavaRuntimeMethod(exec, exec->lexicalGlobalObject(), propertyName, methodList);
141}
142
143JSValue JavaInstance::invokeMethod(ExecState* exec, RuntimeMethod* runtimeMethod)
144{
145    if (!asObject(runtimeMethod)->inherits(&JavaRuntimeMethod::s_info))
146        return throwError(exec, createTypeError(exec, "Attempt to invoke non-Java method on Java object."));
147
148    const MethodList& methodList = *runtimeMethod->methods();
149
150    int i;
151    int count = exec->argumentCount();
152    JSValue resultValue;
153    Method* method = 0;
154    size_t numMethods = methodList.size();
155
156    // Try to find a good match for the overloaded method.  The
157    // fundamental problem is that JavaScript doesn't have the
158    // notion of method overloading and Java does.  We could
159    // get a bit more sophisticated and attempt to does some
160    // type checking as we as checking the number of parameters.
161    for (size_t methodIndex = 0; methodIndex < numMethods; methodIndex++) {
162        Method* aMethod = methodList[methodIndex];
163        if (aMethod->numParameters() == count) {
164            method = aMethod;
165            break;
166        }
167    }
168    if (!method) {
169        LOG(LiveConnect, "JavaInstance::invokeMethod unable to find an appropiate method");
170        return jsUndefined();
171    }
172
173    const JavaMethod* jMethod = static_cast<const JavaMethod*>(method);
174    LOG(LiveConnect, "JavaInstance::invokeMethod call %s %s on %p", UString(jMethod->name().impl()).utf8().data(), jMethod->signature(), m_instance->m_instance);
175
176    Vector<jvalue> jArgs(count);
177
178    for (i = 0; i < count; i++) {
179        CString javaClassName = jMethod->parameterAt(i).utf8();
180        jArgs[i] = convertValueToJValue(exec, m_rootObject.get(), exec->argument(i), javaTypeFromClassName(javaClassName.data()), javaClassName.data());
181        LOG(LiveConnect, "JavaInstance::invokeMethod arg[%d] = %s", i, exec->argument(i).toString(exec).ascii().data());
182    }
183
184    jvalue result;
185
186    // Try to use the JNI abstraction first, otherwise fall back to
187    // normal JNI.  The JNI dispatch abstraction allows the Java plugin
188    // to dispatch the call on the appropriate internal VM thread.
189    RootObject* rootObject = this->rootObject();
190    if (!rootObject)
191        return jsUndefined();
192
193    bool handled = false;
194    if (rootObject->nativeHandle()) {
195        jobject obj = m_instance->m_instance;
196        JSValue exceptionDescription;
197        const char *callingURL = 0; // FIXME, need to propagate calling URL to Java
198        jmethodID methodId = getMethodID(obj, jMethod->name().utf8().data(), jMethod->signature());
199        handled = dispatchJNICall(exec, rootObject->nativeHandle(), obj, jMethod->isStatic(), jMethod->returnType(), methodId, jArgs.data(), result, callingURL, exceptionDescription);
200        if (exceptionDescription) {
201            throwError(exec, createError(exec, exceptionDescription.toString(exec)));
202            return jsUndefined();
203        }
204    }
205
206// This is a deprecated code path which should not be required on Android.
207// Remove this guard once Bug 39476 is fixed.
208#if PLATFORM(ANDROID) || defined(BUILDING_ON_TIGER)
209    if (!handled)
210        result = callJNIMethod(m_instance->m_instance, jMethod->returnType(), jMethod->name().utf8().data(), jMethod->signature(), jArgs.data());
211#endif
212
213    switch (jMethod->returnType()) {
214    case JavaTypeVoid:
215        {
216            resultValue = jsUndefined();
217        }
218        break;
219
220    case JavaTypeObject:
221        {
222            if (result.l) {
223                // FIXME: JavaTypeArray return type is handled below, can we actually get an array here?
224                const char* arrayType = jMethod->returnTypeClassName();
225                if (arrayType[0] == '[')
226                    resultValue = JavaArray::convertJObjectToArray(exec, result.l, arrayType, rootObject);
227                else {
228                    jobject classOfInstance = callJNIMethod<jobject>(result.l, "getClass", "()Ljava/lang/Class;");
229                    jstring className = static_cast<jstring>(callJNIMethod<jobject>(classOfInstance, "getName", "()Ljava/lang/String;"));
230                    if (!strcmp(JavaString(className).utf8(), "sun.plugin.javascript.webkit.JSObject")) {
231                        // Pull the nativeJSObject value from the Java instance.  This is a pointer to the JSObject.
232                        JNIEnv* env = getJNIEnv();
233                        jfieldID fieldID = env->GetFieldID(static_cast<jclass>(classOfInstance), "nativeJSObject", "J");
234                        jlong nativeHandle = env->GetLongField(result.l, fieldID);
235                        // FIXME: Handling of undefined values differs between functions in JNIUtilityPrivate.cpp and those in those in jni_jsobject.mm,
236                        // and so it does between different versions of LiveConnect spec. There should not be multiple code paths to do the same work.
237                        if (nativeHandle == 1 /* UndefinedHandle */)
238                            return jsUndefined();
239                        return static_cast<JSObject*>(jlong_to_ptr(nativeHandle));
240                    } else
241                        return JavaInstance::create(result.l, rootObject)->createRuntimeObject(exec);
242                }
243            } else
244                return jsUndefined();
245        }
246        break;
247
248    case JavaTypeBoolean:
249        {
250            resultValue = jsBoolean(result.z);
251        }
252        break;
253
254    case JavaTypeByte:
255        {
256            resultValue = jsNumber(result.b);
257        }
258        break;
259
260    case JavaTypeChar:
261        {
262            resultValue = jsNumber(result.c);
263        }
264        break;
265
266    case JavaTypeShort:
267        {
268            resultValue = jsNumber(result.s);
269        }
270        break;
271
272    case JavaTypeInt:
273        {
274            resultValue = jsNumber(result.i);
275        }
276        break;
277
278    case JavaTypeLong:
279        {
280            resultValue = jsNumber(result.j);
281        }
282        break;
283
284    case JavaTypeFloat:
285        {
286            resultValue = jsNumber(result.f);
287        }
288        break;
289
290    case JavaTypeDouble:
291        {
292            resultValue = jsNumber(result.d);
293        }
294        break;
295
296    case JavaTypeArray:
297        {
298            const char* arrayType = jMethod->returnTypeClassName();
299            ASSERT(arrayType[0] == '[');
300            resultValue = JavaArray::convertJObjectToArray(exec, result.l, arrayType, rootObject);
301        }
302        break;
303
304    case JavaTypeInvalid:
305        {
306            resultValue = jsUndefined();
307        }
308        break;
309    }
310
311    return resultValue;
312}
313
314JSValue JavaInstance::defaultValue(ExecState* exec, PreferredPrimitiveType hint) const
315{
316    if (hint == PreferString)
317        return stringValue(exec);
318    if (hint == PreferNumber)
319        return numberValue(exec);
320    JavaClass* aClass = static_cast<JavaClass*>(getClass());
321    if (aClass->isStringClass())
322        return stringValue(exec);
323    if (aClass->isNumberClass())
324        return numberValue(exec);
325    if (aClass->isBooleanClass())
326        return booleanValue();
327    return valueOf(exec);
328}
329
330JSValue JavaInstance::valueOf(ExecState* exec) const
331{
332    return stringValue(exec);
333}
334
335#endif // ENABLE(JAVA_BRIDGE)
336