Back to index

lightning-sunbird  0.9+nobinonly
jsj_JavaObject.c
Go to the documentation of this file.
00001 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
00002  *
00003  * ***** BEGIN LICENSE BLOCK *****
00004  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00005  *
00006  * The contents of this file are subject to the Mozilla Public License Version
00007  * 1.1 (the "License"); you may not use this file except in compliance with
00008  * the License. You may obtain a copy of the License at
00009  * http://www.mozilla.org/MPL/
00010  *
00011  * Software distributed under the License is distributed on an "AS IS" basis,
00012  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00013  * for the specific language governing rights and limitations under the
00014  * License.
00015  *
00016  * The Original Code is Mozilla Communicator client code, released
00017  * March 31, 1998.
00018  *
00019  * The Initial Developer of the Original Code is
00020  * Netscape Communications Corporation.
00021  * Portions created by the Initial Developer are Copyright (C) 1998
00022  * the Initial Developer. All Rights Reserved.
00023  *
00024  * Contributor(s):
00025  *
00026  * Alternatively, the contents of this file may be used under the terms of
00027  * either of the GNU General Public License Version 2 or later (the "GPL"),
00028  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00029  * in which case the provisions of the GPL or the LGPL are applicable instead
00030  * of those above. If you wish to allow use of your version of this file only
00031  * under the terms of either the GPL or the LGPL, and not to allow others to
00032  * use your version of this file under the terms of the MPL, indicate your
00033  * decision by deleting the provisions above and replace them with the notice
00034  * and other provisions required by the GPL or the LGPL. If you do not delete
00035  * the provisions above, a recipient may use your version of this file under
00036  * the terms of any one of the MPL, the GPL or the LGPL.
00037  *
00038  * This Original Code has been modified by IBM Corporation. Modifications made
00039  * by IBM described herein are Copyright (c) International Business Machines
00040  * Corporation, 2000.
00041  * Modifications to Mozilla code or documentation identified per MPL Section 3.3
00042  *
00043  * Date             Modified by     Description of modification
00044  * 04/20/2000       IBM Corp.      OS/2 VisualAge build.
00045  *
00046  * ***** END LICENSE BLOCK ***** */
00047 
00048 /*
00049  * This file is part of the Java-vendor-neutral implementation of LiveConnect
00050  *
00051  * It contains the native code implementation of JS's JavaObject class.
00052  *
00053  * An instance of JavaObject is the JavaScript reflection of a Java object.
00054  *
00055  */
00056 
00057 #include <stdlib.h>
00058 #include <string.h>
00059 
00060 #include "jsobj.h"
00061 #include "jsj_private.h"      /* LiveConnect internals */
00062 #include "jsj_hash.h"         /* Hash table with Java object as key */
00063 
00064 #ifdef JSJ_THREADSAFE
00065 #include "prmon.h"
00066 #endif
00067 
00068 /*
00069  * This is a hash table that maps from Java objects to JS objects.
00070  * It is used to ensure that the same JS object is obtained when a Java
00071  * object is reflected more than once, so that JS object equality tests
00072  * work in the expected manner, i.e. the "==" and "===" operators.
00073  *
00074  * The table entry keys are Java objects (of type jobject) and the entry values
00075  * are JSObject pointers.  Because the jobject type is an opaque handle and
00076  * not necessarily a pointer, the hashing and key comparison functions must
00077  * invoke the appropriate JVM functions.
00078  *
00079  * When the corresponding JS object instance is finalized, the entry is
00080  * removed from the table, and a Java GC root for the Java object is removed.
00081  */
00082 static JSJHashTable *java_obj_reflections = NULL;
00083 
00084 #ifdef JSJ_THREADSAFE
00085 static PRMonitor *java_obj_reflections_monitor = NULL;
00086 static int java_obj_reflections_mutation_count = 0;
00087 #endif
00088 
00089 static JSBool installed_GC_callback = JS_FALSE;
00090 static JSGCCallback old_GC_callback = NULL;
00091 static JavaObjectWrapper* deferred_wrappers = NULL;
00092 
00093 static JSBool JS_DLL_CALLBACK jsj_GC_callback(JSContext *cx, JSGCStatus status)
00094 {
00095     if (status == JSGC_END && deferred_wrappers) {
00096         JNIEnv *jEnv;
00097         JSJavaThreadState *jsj_env = jsj_EnterJava(cx, &jEnv);
00098         if (jEnv) {
00099             JavaObjectWrapper* java_wrapper = deferred_wrappers;
00100             while (java_wrapper) {
00101                 deferred_wrappers = java_wrapper->u.next;
00102                 if (java_wrapper->java_obj)
00103                     (*jEnv)->DeleteGlobalRef(jEnv, java_wrapper->java_obj);
00104                 jsj_ReleaseJavaClassDescriptor(cx, jEnv, java_wrapper->class_descriptor);
00105                 JS_free(cx, java_wrapper);
00106                 java_wrapper = deferred_wrappers;
00107             }
00108             jsj_ExitJava(jsj_env);
00109         }
00110     }
00111     /* always chain to old GC callback if non-null. */
00112     return old_GC_callback ? old_GC_callback(cx, status) : JS_TRUE;
00113 }
00114 
00115 JSBool
00116 jsj_InitJavaObjReflectionsTable(void)
00117 {
00118     JS_ASSERT(!java_obj_reflections);
00119 
00120     java_obj_reflections =
00121         JSJ_NewHashTable(512, jsj_HashJavaObject, jsj_JavaObjectComparator,
00122                          NULL, NULL, NULL);
00123     if (!java_obj_reflections)
00124         return JS_FALSE;
00125 
00126 #ifdef JSJ_THREADSAFE
00127     java_obj_reflections_monitor = (struct PRMonitor *) PR_NewMonitor();
00128     if (!java_obj_reflections_monitor) {
00129         JSJ_HashTableDestroy(java_obj_reflections);
00130         return JS_FALSE;
00131     }
00132 #endif
00133 
00134     return JS_TRUE;
00135 }
00136 
00137 JSObject *
00138 jsj_WrapJavaObject(JSContext *cx,
00139                    JNIEnv *jEnv,
00140                    jobject java_obj,
00141                    jclass java_class)
00142 {
00143     JSJHashNumber hash_code;
00144     JSClass *js_class;
00145     JSObject *js_wrapper_obj;
00146     JavaObjectWrapper *java_wrapper;
00147     JavaClassDescriptor *class_descriptor;
00148     JSJHashEntry *he, **hep;
00149 
00150 #ifdef JSJ_THREADSAFE
00151     int mutation_count;
00152 #endif
00153 
00154     js_wrapper_obj = NULL;
00155 
00156     hash_code = jsj_HashJavaObject((void*)java_obj, (void*)jEnv);
00157 
00158 #ifdef JSJ_THREADSAFE
00159     PR_EnterMonitor(java_obj_reflections_monitor);
00160 #endif
00161 
00162     if (!installed_GC_callback) {
00163         /*
00164          * Hook into GC callback mechanism, so we can defer deleting global
00165          * references until it's safe.
00166          */
00167         old_GC_callback =  JS_SetGCCallback(cx, jsj_GC_callback);
00168         installed_GC_callback = JS_TRUE;
00169     }
00170 
00171     hep = JSJ_HashTableRawLookup(java_obj_reflections,
00172                                  hash_code, java_obj, (void*)jEnv);
00173     he = *hep;
00174 
00175 #ifdef JSJ_THREADSAFE
00176     /* Track mutations to hash table */
00177     mutation_count = java_obj_reflections_mutation_count;
00178 
00179     /* We must temporarily release this monitor so as to avoid
00180        deadlocks with the JS GC.  See Bugsplat #354852 */
00181     PR_ExitMonitor(java_obj_reflections_monitor);
00182 #endif
00183 
00184     if (he) {
00185         js_wrapper_obj = (JSObject *)he->value;
00186         if (js_wrapper_obj)
00187             return js_wrapper_obj;
00188     }
00189 
00190     /* No existing reflection found.  Construct a new one */
00191     class_descriptor = jsj_GetJavaClassDescriptor(cx, jEnv, java_class);
00192     if (!class_descriptor)
00193         return NULL;
00194     if (class_descriptor->type == JAVA_SIGNATURE_ARRAY) {
00195         js_class = &JavaArray_class;
00196     } else {
00197         JS_ASSERT(IS_OBJECT_TYPE(class_descriptor->type));
00198         js_class = &JavaObject_class;
00199     }
00200 
00201     /* Create new JS object to reflect Java object */
00202     js_wrapper_obj = JS_NewObject(cx, js_class, NULL, NULL);
00203     if (!js_wrapper_obj)
00204         return NULL;
00205 
00206     /* Create private, native portion of JavaObject */
00207     java_wrapper =
00208         (JavaObjectWrapper *)JS_malloc(cx, sizeof(JavaObjectWrapper));
00209     if (!java_wrapper) {
00210         jsj_ReleaseJavaClassDescriptor(cx, jEnv, class_descriptor);
00211         return NULL;
00212     }
00213     JS_SetPrivate(cx, js_wrapper_obj, java_wrapper);
00214     java_wrapper->class_descriptor = class_descriptor;
00215     java_wrapper->java_obj = NULL;
00216 
00217 #ifdef JSJ_THREADSAFE
00218     PR_EnterMonitor(java_obj_reflections_monitor);
00219 
00220     /* We may need to do the hash table lookup again, since some other
00221        thread may have updated it while the lock wasn't being held. */
00222     if (mutation_count != java_obj_reflections_mutation_count) {
00223         hep = JSJ_HashTableRawLookup(java_obj_reflections,
00224                                      hash_code, java_obj, (void*)jEnv);
00225         he = *hep;
00226         if (he) {
00227             js_wrapper_obj = (JSObject *)he->value;
00228             if (js_wrapper_obj) {
00229                 PR_ExitMonitor(java_obj_reflections_monitor);
00230                 return js_wrapper_obj;
00231             }
00232         }
00233     }
00234 
00235     java_obj_reflections_mutation_count++;
00236 
00237 #endif
00238 
00239     java_obj = (*jEnv)->NewGlobalRef(jEnv, java_obj);
00240     java_wrapper->java_obj = java_obj;
00241     if (!java_obj)
00242         goto out_of_memory;
00243 
00244     /* cache the hash code for all time. */
00245     java_wrapper->u.hash_code = hash_code;
00246 
00247     /* Add the JavaObject to the hash table */
00248     he = JSJ_HashTableRawAdd(java_obj_reflections, hep, hash_code,
00249                              java_obj, js_wrapper_obj, (void*)jEnv);
00250 #ifdef JSJ_THREADSAFE
00251     PR_ExitMonitor(java_obj_reflections_monitor);
00252 #endif
00253 
00254     if (!he) {
00255         (*jEnv)->DeleteGlobalRef(jEnv, java_obj);
00256         goto out_of_memory;
00257     }
00258 
00259     return js_wrapper_obj;
00260 
00261 out_of_memory:
00262     /* No need to free js_wrapper_obj, as it will be finalized by GC. */
00263     JS_ReportOutOfMemory(cx);
00264     return NULL;
00265 }
00266 
00267 static void
00268 remove_java_obj_reflection_from_hashtable(jobject java_obj, JSJHashNumber hash_code)
00269 {
00270     JSJHashEntry *he, **hep;
00271 
00272 #ifdef JSJ_THREADSAFE
00273     PR_EnterMonitor(java_obj_reflections_monitor);
00274 #endif
00275 
00276     hep = JSJ_HashTableRawLookup(java_obj_reflections, hash_code,
00277                                  java_obj, NULL);
00278     he = *hep;
00279 
00280     JS_ASSERT(he);
00281     if (he)
00282         JSJ_HashTableRawRemove(java_obj_reflections, hep, he, NULL);
00283 
00284 #ifdef JSJ_THREADSAFE
00285     java_obj_reflections_mutation_count++;
00286 
00287     PR_ExitMonitor(java_obj_reflections_monitor);
00288 #endif
00289 }
00290 
00291 JS_EXPORT_API(void)
00292 JavaObject_finalize(JSContext *cx, JSObject *obj)
00293 {
00294     JavaObjectWrapper *java_wrapper;
00295     jobject java_obj;
00296     JNIEnv *jEnv;
00297     JSJavaThreadState *jsj_env;
00298 
00299     java_wrapper = JS_GetPrivate(cx, obj);
00300     if (!java_wrapper)
00301         return;
00302     java_obj = java_wrapper->java_obj;
00303 
00304     if (java_obj) {
00305         remove_java_obj_reflection_from_hashtable(java_obj, java_wrapper->u.hash_code);
00306         /* defer releasing global refs until it is safe to do so. */
00307         java_wrapper->u.next = deferred_wrappers;
00308         deferred_wrappers = java_wrapper;
00309     } else {
00310         jsj_env = jsj_EnterJava(cx, &jEnv);
00311         if (jEnv) {
00312             jsj_ReleaseJavaClassDescriptor(cx, jEnv, java_wrapper->class_descriptor);
00313             JS_free(cx, java_wrapper);
00314             jsj_ExitJava(jsj_env);
00315         } else {
00316             java_wrapper->u.next = deferred_wrappers;
00317             deferred_wrappers = java_wrapper;
00318         }
00319     }
00320 }
00321 
00322 /* Trivial helper for jsj_DiscardJavaObjReflections(), below */
00323 static JSIntn
00324 enumerate_remove_java_obj(JSJHashEntry *he, JSIntn i, void *arg)
00325 {
00326     JSJavaThreadState *jsj_env = (JSJavaThreadState *)arg;
00327     JNIEnv *jEnv = jsj_env->jEnv;
00328     jobject java_obj;
00329     JavaObjectWrapper *java_wrapper;
00330     JSObject *java_wrapper_obj;
00331 
00332     java_wrapper_obj = (JSObject *)he->value;
00333 
00334     /* Warning: NULL argument may cause assertion in JS engine, but it's actually OK */
00335     java_wrapper = JS_GetPrivate(jsj_env->cx, java_wrapper_obj);
00336     java_obj = java_wrapper->java_obj;
00337     (*jEnv)->DeleteGlobalRef(jEnv, java_obj);
00338     java_wrapper->java_obj = NULL;
00339     return HT_ENUMERATE_REMOVE;
00340 }
00341 
00342 /* This shutdown routine discards all JNI references to Java objects
00343    that have been reflected into JS, even if there are still references
00344    to them from JS. */
00345 void
00346 jsj_DiscardJavaObjReflections(JNIEnv *jEnv)
00347 {
00348     JSJavaThreadState *jsj_env;
00349     char *err_msg;
00350 
00351     /* Get the per-thread state corresponding to the current Java thread */
00352     jsj_env = jsj_MapJavaThreadToJSJavaThreadState(jEnv, &err_msg);
00353     JS_ASSERT(jsj_env);
00354     if (!jsj_env)
00355         return;
00356 
00357     if (java_obj_reflections) {
00358         JSJ_HashTableEnumerateEntries(java_obj_reflections,
00359                                       enumerate_remove_java_obj,
00360                                       (void*)jsj_env);
00361         JSJ_HashTableDestroy(java_obj_reflections);
00362         java_obj_reflections = NULL;
00363     }
00364 }
00365 
00366 JSBool JS_DLL_CALLBACK
00367 JavaObject_convert(JSContext *cx, JSObject *obj, JSType type, jsval *vp)
00368 {
00369     JavaObjectWrapper *java_wrapper;
00370     JavaClassDescriptor *class_descriptor;
00371     jobject java_obj;
00372     JNIEnv *jEnv;
00373     JSJavaThreadState *jsj_env;
00374     JSBool result;
00375 
00376     java_wrapper = JS_GetPrivate(cx, obj);
00377     if (!java_wrapper) {
00378         if (type == JSTYPE_OBJECT) {
00379             *vp = OBJECT_TO_JSVAL(obj);
00380             return JS_TRUE;
00381         }
00382 
00383         JS_ReportErrorNumber(cx, jsj_GetErrorMessage, NULL,
00384                              JSJMSG_BAD_OP_JOBJECT);
00385         return JS_FALSE;
00386     }
00387 
00388     java_obj = java_wrapper->java_obj;
00389     class_descriptor = java_wrapper->class_descriptor;
00390 
00391     switch (type) {
00392     case JSTYPE_OBJECT:
00393         *vp = OBJECT_TO_JSVAL(obj);
00394         return JS_TRUE;
00395 
00396     case JSTYPE_FUNCTION:
00397         JS_ReportErrorNumber(cx, jsj_GetErrorMessage, NULL,
00398                              JSJMSG_CONVERT_TO_FUNC);
00399         return JS_FALSE;
00400 
00401     case JSTYPE_VOID:
00402     case JSTYPE_STRING:
00403         /* Get the Java per-thread environment pointer for this JSContext */
00404         jsj_env = jsj_EnterJava(cx, &jEnv);
00405         if (!jEnv)
00406             return JS_FALSE;
00407 
00408         /* Either extract a C-string from the java.lang.String object
00409            or call the Java toString() method */
00410         result = jsj_ConvertJavaObjectToJSString(cx, jEnv, class_descriptor, java_obj, vp);
00411         jsj_ExitJava(jsj_env);
00412         return result;
00413 
00414     case JSTYPE_NUMBER:
00415         /* Get the Java per-thread environment pointer for this JSContext */
00416         jsj_env = jsj_EnterJava(cx, &jEnv);
00417         if (!jEnv)
00418             return JS_FALSE;
00419 
00420         /* Call Java doubleValue() method, if applicable */
00421         result = jsj_ConvertJavaObjectToJSNumber(cx, jEnv, class_descriptor, java_obj, vp);
00422         jsj_ExitJava(jsj_env);
00423         return result;
00424 
00425     case JSTYPE_BOOLEAN:
00426         /* Get the Java per-thread environment pointer for this JSContext */
00427         jsj_env = jsj_EnterJava(cx, &jEnv);
00428         if (!jEnv)
00429             return JS_FALSE;
00430 
00431         /* Call booleanValue() method, if applicable */
00432         result = jsj_ConvertJavaObjectToJSBoolean(cx, jEnv, class_descriptor, java_obj, vp);
00433         jsj_ExitJava(jsj_env);
00434         return result;
00435 
00436     default:
00437         JS_ASSERT(0);
00438         return JS_FALSE;
00439     }
00440 }
00441 
00442 /*
00443  * Get a property from the prototype object of a native ECMA object, i.e.
00444  * return <js_constructor_name>.prototype.<member_name>
00445  * This is used to allow Java objects to inherit methods from Array.prototype
00446  * and String.prototype.
00447  */
00448 static JSBool
00449 inherit_props_from_JS_natives(JSContext *cx, const char *js_constructor_name,
00450                               const char *member_name, jsval *vp)
00451 {
00452     JSObject *global_obj, *constructor_obj, *prototype_obj;
00453     jsval constructor_val, prototype_val;
00454 
00455     global_obj = JS_GetGlobalObject(cx);
00456     JS_ASSERT(global_obj);
00457     if (!global_obj)
00458         return JS_FALSE;
00459 
00460     JS_GetProperty(cx, global_obj, js_constructor_name, &constructor_val);
00461     JS_ASSERT(JSVAL_IS_OBJECT(constructor_val));
00462     constructor_obj = JSVAL_TO_OBJECT(constructor_val);
00463 
00464     JS_GetProperty(cx, constructor_obj, "prototype", &prototype_val);
00465     JS_ASSERT(JSVAL_IS_OBJECT(prototype_val));
00466     prototype_obj = JSVAL_TO_OBJECT(prototype_val);
00467 
00468     return JS_GetProperty(cx, prototype_obj, member_name, vp) && *vp != JSVAL_VOID;
00469 }
00470 
00471 struct JSJPropertyInfo {
00472     JSBool wantProp;            /* input param tells whether prop is returned */
00473     const char* name;           /* output param, name of property (XXX ASCII) */
00474     uintN attributes;           /* output param, attributes of property */
00475     JSProperty *prop;           /* output param, if wantProp, held pointer that
00476                                    must be released via OBJ_DROP_PROPERTY */
00477 };
00478 typedef struct JSJPropertyInfo JSJPropertyInfo;
00479 
00480 static JSBool
00481 lookup_member_by_id(JSContext *cx, JNIEnv *jEnv, JSObject *obj,
00482                     JavaObjectWrapper **java_wrapperp, jsid id,
00483                     JavaMemberDescriptor **member_descriptorp,
00484                     jsval *vp, JSObject **proto_chainp,
00485                     JSJPropertyInfo *prop_infop)
00486 {
00487     jsval idval;
00488     JavaObjectWrapper *java_wrapper;
00489     JavaMemberDescriptor *member_descriptor;
00490     const char *member_name;
00491     JavaClassDescriptor *class_descriptor;
00492     JSObject *proto_chain;
00493     JSBool found_in_proto;
00494 
00495     found_in_proto = JS_FALSE;
00496     member_descriptor = NULL;
00497     java_wrapper = JS_GetPrivate(cx, obj);
00498 
00499     /* Handle accesses to prototype object */
00500     if (!java_wrapper) {
00501         if (JS_IdToValue(cx, id, &idval) && JSVAL_IS_STRING(idval) &&
00502             (member_name = JS_GetStringBytes(JSVAL_TO_STRING(idval))) != NULL) {
00503             if (!strcmp(member_name, "constructor"))
00504                 goto done;
00505         }
00506         JS_ReportErrorNumber(cx, jsj_GetErrorMessage, NULL, JSJMSG_BAD_OP_JOBJECT);
00507         return JS_FALSE;
00508     }
00509 
00510     class_descriptor = java_wrapper->class_descriptor;
00511     JS_ASSERT(IS_REFERENCE_TYPE(class_descriptor->type));
00512 
00513     member_descriptor = jsj_LookupJavaMemberDescriptorById(cx, jEnv, class_descriptor, id);
00514     if (member_descriptor)
00515         goto done;
00516 
00517     /* Instances can reference static methods and fields */
00518     member_descriptor = jsj_LookupJavaStaticMemberDescriptorById(cx, jEnv, class_descriptor, id);
00519     if (member_descriptor)
00520         goto done;
00521 
00522     /* Ensure that the property we're searching for is string-valued. */
00523     JS_IdToValue(cx, id, &idval);
00524     if (!JSVAL_IS_STRING(idval)) {
00525         JS_ReportErrorNumber(cx, jsj_GetErrorMessage, NULL, JSJMSG_BAD_JOBJECT_EXPR);
00526         return JS_FALSE;
00527     }
00528     member_name = JS_GetStringBytes(JSVAL_TO_STRING(idval));
00529 
00530     /*
00531      * A little LC3 feature magic:
00532      *   + Instances of java.lang.String "inherit" the standard ECMA string methods
00533      *     of String.prototype.  All of the ECMA string methods convert the Java
00534      *     string to a JS string before performing the string operation.  For example,
00535      *         s = new java.lang.String("foobar");
00536      *         return s.slice(2);
00537      *   + Similarly, instances of Java arrays "inherit" the standard ECMA array
00538      *     methods of Array.prototype.  (Not all of these methods work properly
00539      *     on JavaArray objects, however, since the 'length' property is read-only.)
00540      */
00541     if (vp) {
00542         if ((class_descriptor->type == JAVA_SIGNATURE_JAVA_LANG_STRING) &&
00543             inherit_props_from_JS_natives(cx, "String", member_name, vp))
00544             goto done;
00545         if ((class_descriptor->type == JAVA_SIGNATURE_ARRAY) &&
00546             inherit_props_from_JS_natives(cx, "Array", member_name, vp))
00547             goto done;
00548     }
00549 
00550     /* Check for access to magic prototype chain property */
00551     if (!strcmp(member_name, "__proto__")) {
00552         proto_chain = JS_GetPrototype(cx, obj);
00553         if (vp)
00554             *vp = OBJECT_TO_JSVAL(proto_chain);
00555         goto done;
00556     }
00557 
00558     /*
00559      * See if the property looks like the explicit resolution of an
00560      * overloaded method, e.g. "max(double,double)", first as an instance method,
00561      * then as a static method.  If we find such a method, it will be cached
00562      * so future accesses won't run this code.
00563      */
00564     member_descriptor = jsj_ResolveExplicitMethod(cx, jEnv, class_descriptor, id, JS_FALSE);
00565     if (member_descriptor)
00566         goto done;
00567     member_descriptor = jsj_ResolveExplicitMethod(cx, jEnv, class_descriptor, id, JS_TRUE);
00568     if (member_descriptor)
00569         goto done;
00570 
00571     /* Is the property defined in the prototype chain? */
00572     if (proto_chainp && prop_infop) {
00573         /* If so, follow __proto__ link to search prototype chain */
00574         proto_chain = JS_GetPrototype(cx, obj);
00575 
00576         /* Use OBJ_LOOKUP_PROPERTY to determine if and where the property
00577            actually exists in the prototype chain. */
00578         if (proto_chain) {
00579             if (!OBJ_LOOKUP_PROPERTY(cx, proto_chain, id, proto_chainp,
00580                                      &prop_infop->prop)) {
00581                 return JS_FALSE;
00582             }
00583             if (prop_infop->prop) {
00584                 if (!OBJ_GET_ATTRIBUTES(cx, *proto_chainp, id, prop_infop->prop,
00585                                         &prop_infop->attributes)) {
00586                     OBJ_DROP_PROPERTY(cx, *proto_chainp, prop_infop->prop);
00587                     return JS_FALSE;
00588                 }
00589                 if (!prop_infop->wantProp) {
00590                     OBJ_DROP_PROPERTY(cx, *proto_chainp, prop_infop->prop);
00591                     prop_infop->prop = NULL;
00592                 }
00593                 prop_infop->name = member_name;
00594                 found_in_proto = JS_TRUE;
00595                 goto done;
00596             }
00597         }
00598     }
00599 
00600     /* Report lack of Java member with the given property name */
00601     JS_ReportErrorNumber(cx, jsj_GetErrorMessage, NULL, JSJMSG_NO_INSTANCE_NAME,
00602                          class_descriptor->name, member_name);
00603     return JS_FALSE;
00604 
00605 done:
00606     /* Success.  Handle the multiple return values */
00607     if (java_wrapperp)
00608         *java_wrapperp = java_wrapper;
00609     if (member_descriptorp)
00610         *member_descriptorp = member_descriptor;
00611     if (proto_chainp && !found_in_proto)
00612         *proto_chainp = NULL;
00613     return JS_TRUE;
00614 }
00615 
00616 JS_EXPORT_API(JSBool)
00617 JavaObject_getPropertyById(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
00618 {
00619     jobject java_obj;
00620     JavaMemberDescriptor *member_descriptor;
00621     JavaObjectWrapper *java_wrapper;
00622     JNIEnv *jEnv;
00623     JSObject *funobj;
00624     jsval field_val, method_val;
00625     JSBool success;
00626     JSJavaThreadState *jsj_env;
00627     JSObject *proto_chain;
00628     JSJPropertyInfo prop_info;
00629 
00630     /* printf("In JavaObject_getProperty\n"); */
00631 
00632     /* Get the Java per-thread environment pointer for this JSContext */
00633     jsj_env = jsj_EnterJava(cx, &jEnv);
00634     if (!jEnv)
00635         return JS_FALSE;
00636 
00637     if (vp)
00638         *vp = JSVAL_VOID;
00639     prop_info.wantProp = JS_FALSE;
00640     if (!lookup_member_by_id(cx, jEnv, obj, &java_wrapper, id, &member_descriptor, vp,
00641                              &proto_chain, &prop_info)) {
00642         jsj_ExitJava(jsj_env);
00643         return JS_FALSE;
00644     }
00645 
00646     /* Handle access to special, non-Java properties of JavaObjects, e.g. the
00647        "constructor" property of the prototype object */
00648     if (!member_descriptor) {
00649         jsj_ExitJava(jsj_env);
00650         if (proto_chain)
00651             return JS_GetProperty(cx, proto_chain, prop_info.name, vp);
00652         return JS_TRUE;
00653     }
00654 
00655     java_obj = java_wrapper->java_obj;
00656     field_val = method_val = JSVAL_VOID;
00657 
00658     if (jaApplet && (*jEnv)->IsInstanceOf(jEnv, java_obj, jaApplet)) {
00659         jsj_JSIsCallingApplet = JS_TRUE;
00660     }
00661 
00662     /* If a field member, get the value of the field */
00663     if (member_descriptor->field) {
00664         success = jsj_GetJavaFieldValue(cx, jEnv, member_descriptor->field, java_obj, &field_val);
00665         if (!success) {
00666             jsj_ExitJava(jsj_env);
00667             return JS_FALSE;
00668         }
00669     }
00670 
00671     /* If a method member, build a wrapper around the Java method */
00672     if (member_descriptor->methods) {
00673         /* Create a function object with this JavaObject as its parent, so that
00674            JSFUN_BOUND_METHOD binds it as the default 'this' for the function. */
00675         funobj = JS_CloneFunctionObject(cx, member_descriptor->invoke_func_obj, obj);
00676         if (!funobj) {
00677             jsj_ExitJava(jsj_env);
00678             return JS_FALSE;
00679         }
00680         method_val = OBJECT_TO_JSVAL(funobj);
00681     }
00682 
00683 #if TEST_JAVAMEMBER
00684     /* Always create a JavaMember object, even though it's inefficient */
00685     obj = jsj_CreateJavaMember(cx, method_val, field_val);
00686     if (!obj) {
00687         jsj_ExitJava(jsj_env);
00688         return JS_FALSE;
00689     }
00690     *vp = OBJECT_TO_JSVAL(obj);
00691 #else   /* !TEST_JAVAMEMBER */
00692 
00693     if (member_descriptor->field) {
00694         if (!member_descriptor->methods) {
00695             /* Return value of Java field */
00696             *vp = field_val;
00697         } else {
00698             /* Handle special case of access to a property that could refer
00699                to either a Java field or a method that share the same name.
00700                In Java, such ambiguity is not possible because the compiler
00701                can statically determine which is being accessed. */
00702             obj = jsj_CreateJavaMember(cx, method_val, field_val);
00703             if (!obj) {
00704                 jsj_ExitJava(jsj_env);
00705                 return JS_FALSE;
00706             }
00707             *vp = OBJECT_TO_JSVAL(obj);
00708         }
00709 
00710     } else {
00711         /* Return wrapper around Java method */
00712         *vp = method_val;
00713     }
00714 
00715 #endif  /* !TEST_JAVAMEMBER */
00716 
00717     jsj_ExitJava(jsj_env);
00718     return JS_TRUE;
00719 }
00720 
00721 JS_STATIC_DLL_CALLBACK(JSBool)
00722 JavaObject_setPropertyById(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
00723 {
00724     jobject java_obj;
00725     const char *member_name;
00726     JavaObjectWrapper *java_wrapper;
00727     JavaClassDescriptor *class_descriptor;
00728     JavaMemberDescriptor *member_descriptor;
00729     jsval idval;
00730     JNIEnv *jEnv;
00731     JSJavaThreadState *jsj_env;
00732     JSObject *proto_chain;
00733     JSJPropertyInfo prop_info;
00734     JSBool result;
00735 
00736     /* printf("In JavaObject_setProperty\n"); */
00737 
00738     /* Get the Java per-thread environment pointer for this JSContext */
00739     jsj_env = jsj_EnterJava(cx, &jEnv);
00740     if (!jEnv)
00741         return JS_FALSE;
00742 
00743     prop_info.wantProp = JS_FALSE;
00744     if (!lookup_member_by_id(cx, jEnv, obj, &java_wrapper, id, &member_descriptor, NULL,
00745                              &proto_chain, &prop_info)) {
00746         jsj_ExitJava(jsj_env);
00747         return JS_FALSE;
00748     }
00749 
00750     /* Could be assignment to magic JS __proto__ property rather than a Java field */
00751     if (!member_descriptor) {
00752         if (proto_chain && (prop_info.attributes & JSPROP_SHARED)) {
00753             JS_SetProperty(cx, proto_chain, prop_info.name, vp);
00754         } else {
00755             JS_IdToValue(cx, id, &idval);
00756             if (!JSVAL_IS_STRING(idval))
00757                 goto no_such_field;
00758             member_name = JS_GetStringBytes(JSVAL_TO_STRING(idval));
00759             if (strcmp(member_name, "__proto__"))
00760                 goto no_such_field;
00761             if (!JSVAL_IS_OBJECT(*vp)) {
00762                 JS_ReportErrorNumber(cx, jsj_GetErrorMessage, NULL,
00763                                      JSJMSG_BAD_PROTO_ASSIGNMENT);
00764                 jsj_ExitJava(jsj_env);
00765                 return JS_FALSE;
00766             }
00767             JS_SetPrototype(cx, obj, JSVAL_TO_OBJECT(*vp));
00768         }
00769         jsj_ExitJava(jsj_env);
00770         return JS_TRUE;
00771     }
00772 
00773     /* Check for the case where there is a method with the given name, but no field
00774        with that name */
00775     if (!member_descriptor->field)
00776         goto no_such_field;
00777 
00778     /* Silently fail if field value is final (immutable), as required by ECMA spec */
00779     if (member_descriptor->field->modifiers & ACC_FINAL) {
00780         jsj_ExitJava(jsj_env);
00781         return JS_TRUE;
00782     }
00783 
00784     java_obj = java_wrapper->java_obj;
00785 
00786     if (jaApplet && (*jEnv)->IsInstanceOf(jEnv, java_obj, jaApplet)) {
00787         jsj_JSIsCallingApplet = JS_TRUE;
00788     }
00789 
00790     result = jsj_SetJavaFieldValue(cx, jEnv, member_descriptor->field, java_obj, *vp);
00791     jsj_ExitJava(jsj_env);
00792     return result;
00793 
00794 no_such_field:
00795     JS_IdToValue(cx, id, &idval);
00796     member_name = JS_GetStringBytes(JSVAL_TO_STRING(idval));
00797     class_descriptor = java_wrapper->class_descriptor;
00798     JS_ReportErrorNumber(cx, jsj_GetErrorMessage, NULL,
00799                          JSJMSG_NO_NAME_IN_CLASS,
00800                          member_name, class_descriptor->name);
00801     jsj_ExitJava(jsj_env);
00802     return JS_FALSE;
00803 }
00804 
00805 JS_STATIC_DLL_CALLBACK(JSBool)
00806 JavaObject_lookupProperty(JSContext *cx, JSObject *obj, jsid id,
00807                          JSObject **objp, JSProperty **propp)
00808 {
00809     JNIEnv *jEnv;
00810     JSErrorReporter old_reporter;
00811     jsval dummy_val;
00812     JSObject *proto_chain;
00813     JSJPropertyInfo prop_info;
00814     JSJavaThreadState *jsj_env;
00815 
00816     /* printf("In JavaObject_lookupProperty()\n"); */
00817 
00818     /* Get the Java per-thread environment pointer for this JSContext */
00819     jsj_env = jsj_EnterJava(cx, &jEnv);
00820     if (!jEnv)
00821         return JS_FALSE;
00822 
00823     old_reporter = JS_SetErrorReporter(cx, NULL);
00824     prop_info.wantProp = JS_TRUE;
00825     if (lookup_member_by_id(cx, jEnv, obj, NULL, id, NULL, &dummy_val,
00826                             &proto_chain, &prop_info)) {
00827         /* signify that the property is in the prototype chain or the object itself. */
00828         if (proto_chain) {
00829             *objp = proto_chain;
00830             *propp = prop_info.prop;
00831         } else {
00832             *objp = obj;
00833             *propp = (JSProperty*)1;
00834         }
00835     } else {
00836         *objp = NULL;
00837         *propp = NULL;
00838     }
00839 
00840     JS_SetErrorReporter(cx, old_reporter);
00841     jsj_ExitJava(jsj_env);
00842     return JS_TRUE;
00843 }
00844 
00845 JS_STATIC_DLL_CALLBACK(JSBool)
00846 JavaObject_defineProperty(JSContext *cx, JSObject *obj, jsid id, jsval value,
00847                          JSPropertyOp getter, JSPropertyOp setter,
00848                          uintN attrs, JSProperty **propp)
00849 {
00850     JS_ReportErrorNumber(cx, jsj_GetErrorMessage, NULL,
00851                          JSJMSG_JOBJECT_PROP_DEFINE);
00852     return JS_FALSE;
00853 }
00854 
00855 JS_STATIC_DLL_CALLBACK(JSBool)
00856 JavaObject_getAttributes(JSContext *cx, JSObject *obj, jsid id,
00857                         JSProperty *prop, uintN *attrsp)
00858 {
00859     /* We don't maintain JS property attributes for Java class members */
00860     *attrsp = JSPROP_PERMANENT|JSPROP_ENUMERATE;
00861     return JS_TRUE;
00862 }
00863 
00864 JS_STATIC_DLL_CALLBACK(JSBool)
00865 JavaObject_setAttributes(JSContext *cx, JSObject *obj, jsid id,
00866                         JSProperty *prop, uintN *attrsp)
00867 {
00868     /* We don't maintain JS property attributes for Java class members */
00869     if (*attrsp != (JSPROP_PERMANENT|JSPROP_ENUMERATE)) {
00870         JS_ASSERT(0);
00871         return JS_FALSE;
00872     }
00873 
00874     /* Silently ignore all setAttribute attempts */
00875     return JS_TRUE;
00876 }
00877 
00878 JS_STATIC_DLL_CALLBACK(JSBool)
00879 JavaObject_deleteProperty(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
00880 {
00881     JSVersion version = JS_GetVersion(cx);
00882 
00883     *vp = JSVAL_FALSE;
00884 
00885     if (!JSVERSION_IS_ECMA(version)) {
00886         JS_ReportErrorNumber(cx, jsj_GetErrorMessage, NULL,
00887                                         JSJMSG_JOBJECT_PROP_DELETE);
00888         return JS_FALSE;
00889     } else {
00890         /* Attempts to delete permanent properties are silently ignored
00891            by ECMAScript. */
00892         return JS_TRUE;
00893     }
00894 }
00895 
00896 JS_STATIC_DLL_CALLBACK(JSBool)
00897 JavaObject_defaultValue(JSContext *cx, JSObject *obj, JSType type, jsval *vp)
00898 {
00899     /* printf("In JavaObject_defaultValue()\n"); */
00900     return JavaObject_convert(cx, obj, type, vp);
00901 }
00902 
00903 JS_STATIC_DLL_CALLBACK(JSBool)
00904 JavaObject_newEnumerate(JSContext *cx, JSObject *obj, JSIterateOp enum_op,
00905                         jsval *statep, jsid *idp)
00906 {
00907     JavaObjectWrapper *java_wrapper;
00908     JavaMemberDescriptor *member_descriptor;
00909     JavaClassDescriptor *class_descriptor;
00910     JNIEnv *jEnv;
00911     JSJavaThreadState *jsj_env;
00912 
00913     java_wrapper = JS_GetPrivate(cx, obj);
00914     /* Check for prototype object */
00915     if (!java_wrapper) {
00916         *statep = JSVAL_NULL;
00917         if (idp)
00918             *idp = INT_TO_JSVAL(0);
00919         return JS_TRUE;
00920     }
00921 
00922     class_descriptor = java_wrapper->class_descriptor;
00923 
00924     switch(enum_op) {
00925     case JSENUMERATE_INIT:
00926 
00927         /* Get the Java per-thread environment pointer for this JSContext */
00928         jsj_env = jsj_EnterJava(cx, &jEnv);
00929         if (!jEnv)
00930             return JS_FALSE;
00931 
00932         member_descriptor = jsj_GetClassInstanceMembers(cx, jEnv, class_descriptor);
00933         *statep = PRIVATE_TO_JSVAL(member_descriptor);
00934         if (idp)
00935             *idp = INT_TO_JSVAL(class_descriptor->num_instance_members);
00936         jsj_ExitJava(jsj_env);
00937         return JS_TRUE;
00938 
00939     case JSENUMERATE_NEXT:
00940         member_descriptor = JSVAL_TO_PRIVATE(*statep);
00941         if (member_descriptor) {
00942 
00943             /* Don't enumerate explicit-signature methods, i.e. enumerate toValue,
00944                but not toValue(int), toValue(double), etc. */
00945             while (member_descriptor->methods && member_descriptor->methods->is_alias) {
00946                 member_descriptor = member_descriptor->next;
00947                 if (!member_descriptor) {
00948                     *statep = JSVAL_NULL;
00949                     return JS_TRUE;
00950                 }
00951             }
00952 
00953             *idp = member_descriptor->id;
00954             *statep = PRIVATE_TO_JSVAL(member_descriptor->next);
00955             return JS_TRUE;
00956         }
00957 
00958         /* Fall through ... */
00959 
00960     case JSENUMERATE_DESTROY:
00961         *statep = JSVAL_NULL;
00962         return JS_TRUE;
00963 
00964     default:
00965         JS_ASSERT(0);
00966         return JS_FALSE;
00967     }
00968 }
00969 
00970 JS_STATIC_DLL_CALLBACK(JSBool)
00971 JavaObject_checkAccess(JSContext *cx, JSObject *obj, jsid id,
00972                       JSAccessMode mode, jsval *vp, uintN *attrsp)
00973 {
00974     switch (mode) {
00975     case JSACC_WATCH:
00976         JS_ReportErrorNumber(cx, jsj_GetErrorMessage, NULL,
00977                              JSJMSG_JOBJECT_PROP_WATCH);
00978         return JS_FALSE;
00979 
00980     case JSACC_IMPORT:
00981         JS_ReportErrorNumber(cx, jsj_GetErrorMessage, NULL,
00982                              JSJMSG_JOBJECT_PROP_EXPORT);
00983         return JS_FALSE;
00984 
00985     default:
00986         return JS_TRUE;
00987     }
00988 }
00989 
00990 #define JSJ_SLOT_COUNT (JSSLOT_PRIVATE+1)
00991 
00992 JSObjectMap * JS_DLL_CALLBACK
00993 jsj_wrapper_newObjectMap(JSContext *cx, jsrefcount nrefs, JSObjectOps *ops,
00994                          JSClass *clasp, JSObject *obj)
00995 {
00996     JSObjectMap * map;
00997 
00998     map = (JSObjectMap *) JS_malloc(cx, sizeof(JSObjectMap));
00999     if (map) {
01000         map->nrefs = nrefs;
01001         map->ops = ops;
01002         map->nslots = JSJ_SLOT_COUNT;
01003         map->freeslot = JSJ_SLOT_COUNT;
01004     }
01005     return map;
01006 }
01007 
01008 void JS_DLL_CALLBACK
01009 jsj_wrapper_destroyObjectMap(JSContext *cx, JSObjectMap *map)
01010 {
01011     JS_free(cx, map);
01012 }
01013 
01014 jsval JS_DLL_CALLBACK
01015 jsj_wrapper_getRequiredSlot(JSContext *cx, JSObject *obj, uint32 slot)
01016 {
01017     JS_ASSERT(slot < JSJ_SLOT_COUNT);
01018     JS_ASSERT(obj->slots);
01019     JS_ASSERT(obj->map->nslots == JSJ_SLOT_COUNT);
01020     JS_ASSERT(obj->map->freeslot == JSJ_SLOT_COUNT);
01021     return obj->slots[slot];
01022 }
01023 
01024 JSBool JS_DLL_CALLBACK
01025 jsj_wrapper_setRequiredSlot(JSContext *cx, JSObject *obj, uint32 slot, jsval v)
01026 {
01027     JS_ASSERT(slot < JSJ_SLOT_COUNT);
01028     JS_ASSERT(obj->slots);
01029     JS_ASSERT(obj->map->nslots == JSJ_SLOT_COUNT);
01030     JS_ASSERT(obj->map->freeslot == JSJ_SLOT_COUNT);
01031     obj->slots[slot] = v;
01032     return JS_TRUE;
01033 }
01034 
01035 JSObjectOps JavaObject_ops = {
01036     /* Mandatory non-null function pointer members. */
01037     jsj_wrapper_newObjectMap,       /* newObjectMap */
01038     jsj_wrapper_destroyObjectMap,   /* destroyObjectMap */
01039     JavaObject_lookupProperty,
01040     JavaObject_defineProperty,
01041     JavaObject_getPropertyById,     /* getProperty */
01042     JavaObject_setPropertyById,     /* setProperty */
01043     JavaObject_getAttributes,
01044     JavaObject_setAttributes,
01045     JavaObject_deleteProperty,
01046     JavaObject_defaultValue,
01047     JavaObject_newEnumerate,
01048     JavaObject_checkAccess,
01049 
01050     /* Optionally non-null members start here. */
01051     NULL,                           /* thisObject */
01052     NULL,                           /* dropProperty */
01053     NULL,                           /* call */
01054     NULL,                           /* construct */
01055     NULL,                           /* xdrObject */
01056     NULL,                           /* hasInstance */
01057     NULL,                           /* setProto */
01058     NULL,                           /* setParent */
01059     NULL,                           /* mark */
01060     NULL,                           /* clear */
01061     jsj_wrapper_getRequiredSlot,    /* getRequiredSlot */
01062     jsj_wrapper_setRequiredSlot     /* setRequiredSlot */
01063 };
01064 
01065 JS_STATIC_DLL_CALLBACK(JSObjectOps *)
01066 JavaObject_getObjectOps(JSContext *cx, JSClass *clazz)
01067 {
01068     return &JavaObject_ops;
01069 }
01070 
01071 JSClass JavaObject_class = {
01072     "JavaObject", JSCLASS_HAS_PRIVATE,
01073     NULL, NULL, NULL, NULL,
01074     NULL, NULL, JavaObject_convert, JavaObject_finalize,
01075 
01076     /* Optionally non-null members start here. */
01077     JavaObject_getObjectOps,
01078     NULL,                       /* checkAccess */
01079     NULL,                       /* call */
01080     NULL,                       /* construct */
01081     NULL,                       /* xdrObject */
01082     NULL,                       /* hasInstance */
01083     NULL,                       /* mark */
01084     0,                          /* spare */
01085 };
01086 
01087 extern JS_IMPORT_DATA(JSObjectOps) js_ObjectOps;
01088 
01089 JSBool
01090 jsj_init_JavaObject(JSContext *cx, JSObject *global_obj)
01091 {
01092     return JS_InitClass(cx, global_obj,
01093                         0, &JavaObject_class, 0, 0,
01094                         0, 0,
01095                         0, 0) != 0;
01096 }