/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Mozilla Communicator client code, released * March 31, 1998. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1998 * the Initial Developer. All Rights Reserved. * * Contributor(s): * * Alternatively, the contents of this file may be used under the terms of * either of the GNU General Public License Version 2 or later (the "GPL"), * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ /* This file is part of the Java-vendor-neutral implementation of LiveConnect * * It contains the native code implementation of JS's JavaClass class. * * A JavaClass is JavaScript's representation of a Java class. * Its parent JS object is always a JavaPackage object. A JavaClass is not an * exact reflection of Java's corresponding java.lang.Class object. Rather, * the properties of a JavaClass are the static methods and properties of the * corresponding Java class. * * Note that there is no runtime equivalent to the JavaClass class in Java. * (Although there are instances of java.lang.String and there are static * methods of java.lang.String that can be invoked, there's no such thing as * a first-class object that can be referenced simply as "java.lang.String".) */ #include #include #include "jsj_private.h" /* LiveConnect internals */ #include "jscntxt.h" /* for error reporting */ JS_STATIC_DLL_CALLBACK(JSBool) JavaClass_convert(JSContext *cx, JSObject *obj, JSType type, jsval *vp) { char *name; JSString *str; JavaClassDescriptor *class_descriptor; class_descriptor = JS_GetPrivate(cx, obj); if (!class_descriptor) return JS_FALSE; switch(type) { case JSTYPE_STRING: /* Convert '/' to '.' so that it looks like Java language syntax. */ if (!class_descriptor->name) break; name = JS_smprintf("[JavaClass %s]", class_descriptor->name); if (!name) { JS_ReportOutOfMemory(cx); return JS_FALSE; } str = JS_NewString(cx, name, strlen(name)); if (!str) { free(name); /* It's not necessary to call JS_ReportOutOfMemory(), as JS_NewString() will do so on failure. */ return JS_FALSE; } *vp = STRING_TO_JSVAL(str); return JS_TRUE; default: break; } return JS_TRUE; } static JSBool lookup_static_member_by_id(JSContext *cx, JNIEnv *jEnv, JSObject *obj, JavaClassDescriptor **class_descriptorp, jsid id, JavaMemberDescriptor **memberp) { jsval idval; JavaMemberDescriptor *member_descriptor; const char *member_name; JavaClassDescriptor *class_descriptor; class_descriptor = JS_GetPrivate(cx, obj); if (!class_descriptor) { *class_descriptorp = NULL; *memberp = NULL; return JS_TRUE; } if (class_descriptorp) *class_descriptorp = class_descriptor; member_descriptor = jsj_LookupJavaStaticMemberDescriptorById(cx, jEnv, class_descriptor, id); if (!member_descriptor) { JS_IdToValue(cx, id, &idval); if (!JSVAL_IS_STRING(idval)) { JS_ReportErrorNumber(cx, jsj_GetErrorMessage, NULL, JSJMSG_BAD_JCLASS_EXPR); return JS_FALSE; } member_name = JS_GetStringBytes(JSVAL_TO_STRING(idval)); /* * See if the property looks like the explicit resolution of an * overloaded method, e.g. "max(double,double)". */ member_descriptor = jsj_ResolveExplicitMethod(cx, jEnv, class_descriptor, id, JS_TRUE); if (member_descriptor) goto done; /* Why do we have to do this ? */ if (!strcmp(member_name, "prototype")) { *memberp = NULL; return JS_TRUE; } JS_ReportErrorNumber(cx, jsj_GetErrorMessage, NULL, JSJMSG_MISSING_NAME, class_descriptor->name, member_name); return JS_FALSE; } done: if (memberp) *memberp = member_descriptor; return JS_TRUE; } JS_STATIC_DLL_CALLBACK(JSBool) JavaClass_getPropertyById(JSContext *cx, JSObject *obj, jsid id, jsval *vp) { jsval idval; jclass java_class; const char *member_name; JavaClassDescriptor *class_descriptor; JavaMemberDescriptor *member_descriptor; JNIEnv *jEnv; JSJavaThreadState *jsj_env; JSBool result; /* printf("In JavaClass_getProperty\n"); */ /* Get the Java per-thread environment pointer for this JSContext */ jsj_env = jsj_EnterJava(cx, &jEnv); if (!jEnv) return JS_FALSE; if (!lookup_static_member_by_id(cx, jEnv, obj, &class_descriptor, id, &member_descriptor)) { jsj_ExitJava(jsj_env); return JS_FALSE; } if (!member_descriptor) { *vp = JSVAL_VOID; jsj_ExitJava(jsj_env); return JS_TRUE; } java_class = class_descriptor->java_class; if (member_descriptor->field) { if (!member_descriptor->methods) { result = jsj_GetJavaFieldValue(cx, jEnv, member_descriptor->field, java_class, vp); jsj_ExitJava(jsj_env); return result; } else { JS_ASSERT(0); } } else { JSFunction *function; /* TODO - eliminate JSFUN_BOUND_METHOD */ if (member_descriptor->methods->is_alias) { /* If this is an explicit resolution of an overloaded method, use the fully-qualified method name as the name of the resulting JS function, i.e. "myMethod(int,long)" */ JS_IdToValue(cx, id, &idval); member_name = JS_GetStringBytes(JSVAL_TO_STRING(idval)); } else { /* Either not explicit resolution of overloaded method or explicit resolution was unnecessary because method was not overloaded. */ member_name = member_descriptor->name; } function = JS_NewFunction(cx, jsj_JavaStaticMethodWrapper, 0, JSFUN_BOUND_METHOD, obj, member_name); if (!function) { jsj_ExitJava(jsj_env); return JS_FALSE; } *vp = OBJECT_TO_JSVAL(JS_GetFunctionObject(function)); } jsj_ExitJava(jsj_env); return JS_TRUE; } JS_STATIC_DLL_CALLBACK(JSBool) JavaClass_setPropertyById(JSContext *cx, JSObject *obj, jsid id, jsval *vp) { jclass java_class; const char *member_name; JavaClassDescriptor *class_descriptor; JavaMemberDescriptor *member_descriptor; jsval idval; JNIEnv *jEnv; JSJavaThreadState *jsj_env; JSBool result; /* printf("In JavaClass_setProperty\n"); */ /* Get the Java per-thread environment pointer for this JSContext */ jsj_env = jsj_EnterJava(cx, &jEnv); if (!jEnv) return JS_FALSE; if (!lookup_static_member_by_id(cx, jEnv, obj, &class_descriptor, id, &member_descriptor)) { jsj_ExitJava(jsj_env); return JS_FALSE; } /* Check for the case where there is a method with the given name, but no field with that name */ if (!member_descriptor->field) goto no_such_field; /* Silently fail if field value is final (immutable), as required by ECMA spec */ if (member_descriptor->field->modifiers & ACC_FINAL) { jsj_ExitJava(jsj_env); return JS_TRUE; } java_class = class_descriptor->java_class; result = jsj_SetJavaFieldValue(cx, jEnv, member_descriptor->field, java_class, *vp); jsj_ExitJava(jsj_env); return result; no_such_field: JS_IdToValue(cx, id, &idval); member_name = JS_GetStringBytes(JSVAL_TO_STRING(idval)); JS_ReportErrorNumber(cx, jsj_GetErrorMessage, NULL, JSJMSG_MISSING_STATIC, member_name, class_descriptor->name); jsj_ExitJava(jsj_env); return JS_FALSE; } /* * Free the private native data associated with the JavaPackage object. */ JS_STATIC_DLL_CALLBACK(void) JavaClass_finalize(JSContext *cx, JSObject *obj) { JNIEnv *jEnv; JSJavaThreadState *jsj_env; JavaClassDescriptor *class_descriptor = JS_GetPrivate(cx, obj); if (!class_descriptor) return; /* Get the Java per-thread environment pointer for this JSContext */ jsj_env = jsj_EnterJava(cx, &jEnv); if (!jEnv) return; /* printf("Finalizing %s\n", class_descriptor->name); */ jsj_ReleaseJavaClassDescriptor(cx, jEnv, class_descriptor); jsj_ExitJava(jsj_env); } JS_STATIC_DLL_CALLBACK(JSBool) JavaClass_lookupProperty(JSContext *cx, JSObject *obj, jsid id, JSObject **objp, JSProperty **propp) { JNIEnv *jEnv; JSErrorReporter old_reporter; JSJavaThreadState *jsj_env; /* printf("In JavaClass_lookupProperty()\n"); */ /* Get the Java per-thread environment pointer for this JSContext */ jsj_env = jsj_EnterJava(cx, &jEnv); if (!jEnv) return JS_FALSE; old_reporter = JS_SetErrorReporter(cx, NULL); if (lookup_static_member_by_id(cx, jEnv, obj, NULL, id, NULL)) { *objp = obj; *propp = (JSProperty*)1; js_SetObjectWeakRoot(cx, obj); } else { *objp = NULL; *propp = NULL; } JS_SetErrorReporter(cx, old_reporter); jsj_ExitJava(jsj_env); return JS_TRUE; } JS_STATIC_DLL_CALLBACK(JSBool) JavaClass_defineProperty(JSContext *cx, JSObject *obj, jsid id, jsval value, JSPropertyOp getter, JSPropertyOp setter, uintN attrs, JSProperty **propp) { JavaClassDescriptor *class_descriptor; class_descriptor = JS_GetPrivate(cx, obj); /* Check for prototype JavaClass object */ if (!class_descriptor) return JS_TRUE; JS_ReportErrorNumber(cx, jsj_GetErrorMessage, NULL, JSJMSG_JCLASS_PROP_DEFINE); return JS_FALSE; } JS_STATIC_DLL_CALLBACK(JSBool) JavaClass_getAttributes(JSContext *cx, JSObject *obj, jsid id, JSProperty *prop, uintN *attrsp) { /* We don't maintain JS property attributes for Java class members */ *attrsp = JSPROP_PERMANENT|JSPROP_ENUMERATE; return JS_FALSE; } JS_STATIC_DLL_CALLBACK(JSBool) JavaClass_setAttributes(JSContext *cx, JSObject *obj, jsid id, JSProperty *prop, uintN *attrsp) { /* We don't maintain JS property attributes for Java class members */ if (*attrsp != (JSPROP_PERMANENT|JSPROP_ENUMERATE)) { JS_ASSERT(0); return JS_FALSE; } /* Silently ignore all setAttribute attempts */ return JS_TRUE; } JS_STATIC_DLL_CALLBACK(JSBool) JavaClass_deleteProperty(JSContext *cx, JSObject *obj, jsid id, jsval *vp) { JSVersion version = JS_GetVersion(cx); *vp = JSVAL_FALSE; if (!JSVERSION_IS_ECMA(version)) { JS_ReportErrorNumber(cx, jsj_GetErrorMessage, NULL, JSJMSG_JCLASS_PROP_DELETE); return JS_FALSE; } else { /* Attempts to delete permanent properties are silently ignored by ECMAScript. */ return JS_TRUE; } } JS_STATIC_DLL_CALLBACK(JSBool) JavaClass_defaultValue(JSContext *cx, JSObject *obj, JSType type, jsval *vp) { /* printf("In JavaClass_defaultValue()\n"); */ return JavaClass_convert(cx, obj, JSTYPE_STRING, vp); } JS_STATIC_DLL_CALLBACK(JSBool) JavaClass_newEnumerate(JSContext *cx, JSObject *obj, JSIterateOp enum_op, jsval *statep, jsid *idp) { JavaMemberDescriptor *member_descriptor; JavaClassDescriptor *class_descriptor; JNIEnv *jEnv; JSJavaThreadState *jsj_env; class_descriptor = JS_GetPrivate(cx, obj); /* Check for prototype JavaClass object */ if (!class_descriptor) { *statep = JSVAL_NULL; if (idp) *idp = INT_TO_JSVAL(0); return JS_TRUE; } switch(enum_op) { case JSENUMERATE_INIT: /* Get the Java per-thread environment pointer for this JSContext */ jsj_env = jsj_EnterJava(cx, &jEnv); if (!jEnv) return JS_FALSE; member_descriptor = jsj_GetClassStaticMembers(cx, jEnv, class_descriptor); *statep = PRIVATE_TO_JSVAL(member_descriptor); if (idp) *idp = INT_TO_JSVAL(class_descriptor->num_instance_members); jsj_ExitJava(jsj_env); return JS_TRUE; case JSENUMERATE_NEXT: member_descriptor = JSVAL_TO_PRIVATE(*statep); if (member_descriptor) { /* Don't enumerate explicit-signature methods, i.e. enumerate toValue, but not toValue(int), toValue(double), etc. */ while (member_descriptor->methods && member_descriptor->methods->is_alias) { member_descriptor = member_descriptor->next; if (!member_descriptor) { *statep = JSVAL_NULL; return JS_TRUE; } } *idp = member_descriptor->id; *statep = PRIVATE_TO_JSVAL(member_descriptor->next); return JS_TRUE; } /* Fall through ... */ case JSENUMERATE_DESTROY: *statep = JSVAL_NULL; return JS_TRUE; default: JS_ASSERT(0); return JS_FALSE; } } JS_STATIC_DLL_CALLBACK(JSBool) JavaClass_checkAccess(JSContext *cx, JSObject *obj, jsid id, JSAccessMode mode, jsval *vp, uintN *attrsp) { switch (mode) { case JSACC_WATCH: JS_ReportErrorNumber(cx, jsj_GetErrorMessage, NULL, JSJMSG_JCLASS_PROP_WATCH); return JS_FALSE; case JSACC_IMPORT: JS_ReportErrorNumber(cx, jsj_GetErrorMessage, NULL, JSJMSG_JCLASS_PROP_EXPORT); return JS_FALSE; default: return JS_TRUE; } } /* * Implement the JavaScript instanceof operator for JavaClass objects by using * the equivalent Java instanceof operation. */ JS_STATIC_DLL_CALLBACK(JSBool) JavaClass_hasInstance(JSContext *cx, JSObject *obj, jsval candidate_jsval, JSBool *has_instancep) { JavaClassDescriptor *class_descriptor; JavaObjectWrapper *java_wrapper; JSClass *js_class; JSBool has_instance; JSObject *candidate_obj; jclass java_class; jobject java_obj; JNIEnv *jEnv; JSJavaThreadState *jsj_env; has_instance = JS_FALSE; class_descriptor = JS_GetPrivate(cx, obj); if (!class_descriptor) { JS_ReportErrorNumber(cx, jsj_GetErrorMessage, NULL, JSJMSG_BAD_OP_JCLASS); return JS_FALSE; } /* * Make sure that the thing to the left of the instanceof operator is a * Java object. */ if (!JSVAL_IS_OBJECT(candidate_jsval)) goto done; candidate_obj = JSVAL_TO_OBJECT(candidate_jsval); #ifdef JS_THREADSAFE js_class = JS_GetClass(cx, candidate_obj); #else js_class = JS_GetClass(candidate_obj); #endif if ((js_class != &JavaObject_class) && (js_class != &JavaArray_class)) goto done; java_class = class_descriptor->java_class; java_wrapper = JS_GetPrivate(cx, candidate_obj); if (!java_wrapper) { JS_ReportErrorNumber(cx, jsj_GetErrorMessage, NULL, JSJMSG_BAD_OP_PROTO); return JS_FALSE; } java_obj = java_wrapper->java_obj; /* Get JNI pointer */ jsj_env = jsj_EnterJava(cx, &jEnv); has_instance = (*jEnv)->IsInstanceOf(jEnv, java_obj, java_class); jsj_ExitJava(jsj_env); done: *has_instancep = has_instance; return JS_TRUE; } JSObjectOps JavaClass_ops = { /* Mandatory non-null function pointer members. */ jsj_wrapper_newObjectMap, /* newObjectMap */ jsj_wrapper_destroyObjectMap, /* destroyObjectMap */ JavaClass_lookupProperty, JavaClass_defineProperty, JavaClass_getPropertyById, /* getProperty */ JavaClass_setPropertyById, /* setProperty */ JavaClass_getAttributes, JavaClass_setAttributes, JavaClass_deleteProperty, JavaClass_defaultValue, JavaClass_newEnumerate, JavaClass_checkAccess, /* Optionally non-null members start here. */ NULL, /* thisObject */ NULL, /* dropProperty */ jsj_JavaConstructorWrapper, /* call */ jsj_JavaConstructorWrapper, /* construct */ NULL, /* xdrObject */ JavaClass_hasInstance, /* hasInstance */ NULL, /* setProto */ NULL, /* setParent */ NULL, /* mark */ NULL, /* clear */ jsj_wrapper_getRequiredSlot, /* getRequiredSlot */ jsj_wrapper_setRequiredSlot /* setRequiredSlot */ }; JS_STATIC_DLL_CALLBACK(JSObjectOps *) JavaClass_getObjectOps(JSContext *cx, JSClass *clazz) { return &JavaClass_ops; } JSClass JavaClass_class = { "JavaClass", JSCLASS_HAS_PRIVATE, NULL, NULL, NULL, NULL, NULL, NULL, JavaClass_convert, JavaClass_finalize, /* Optionally non-null members start here. */ JavaClass_getObjectOps, NULL, NULL, NULL, NULL, NULL, NULL, 0, }; static JSObject * jsj_new_JavaClass(JSContext *cx, JNIEnv *jEnv, JSObject* parent_obj, JavaClassDescriptor *class_descriptor) { JSObject *JavaClass_obj; JavaClass_obj = JS_NewObject(cx, &JavaClass_class, 0, parent_obj); if (!JavaClass_obj) return NULL; JS_SetPrivate(cx, JavaClass_obj, (void *)class_descriptor); #ifdef DEBUG /* printf("JavaClass \'%s\' created\n", class_descriptor->name); */ #endif return JavaClass_obj; } JSObject * jsj_define_JavaClass(JSContext *cx, JNIEnv *jEnv, JSObject* parent_obj, const char *simple_class_name, jclass java_class) { JavaClassDescriptor *class_descriptor; JSObject *JavaClass_obj; class_descriptor = jsj_GetJavaClassDescriptor(cx, jEnv, java_class); if (!class_descriptor) return NULL; JavaClass_obj = jsj_new_JavaClass(cx, jEnv, parent_obj, class_descriptor); if (!JavaClass_obj) return NULL; if (!JS_DefineProperty(cx, parent_obj, simple_class_name, OBJECT_TO_JSVAL(JavaClass_obj), 0, 0, JSPROP_PERMANENT|JSPROP_READONLY|JSPROP_ENUMERATE)) return NULL; return JavaClass_obj; } /* * The getClass() native JS method is defined as a property of the global * object. Given a JavaObject it returns the corresponding JavaClass. This * is useful for accessing static methods and fields. * * js> getClass(new java.lang.String("foo")) * [JavaClass java.lang.String] */ JS_STATIC_DLL_CALLBACK(JSBool) getClass(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { JSObject *obj_arg, *JavaClass_obj; JavaObjectWrapper *java_wrapper; JavaClassDescriptor *class_descriptor; JNIEnv *jEnv; JSJavaThreadState *jsj_env; if (argc != 1 || !JSVAL_IS_OBJECT(argv[0]) || !(obj_arg = JSVAL_TO_OBJECT(argv[0])) || (!JS_InstanceOf(cx, obj_arg, &JavaObject_class, 0) && !JS_InstanceOf(cx, obj_arg, &JavaArray_class, 0))) { JS_ReportErrorNumber(cx, jsj_GetErrorMessage, NULL, JSJMSG_NEED_JOBJECT_ARG); return JS_FALSE; } java_wrapper = JS_GetPrivate(cx, obj_arg); if (!java_wrapper) { JS_ReportErrorNumber(cx, jsj_GetErrorMessage, NULL, JSJMSG_PROTO_GETCLASS); return JS_FALSE; } jsj_env = jsj_EnterJava(cx, &jEnv); if (!jEnv) return JS_FALSE; class_descriptor = java_wrapper->class_descriptor; JavaClass_obj = jsj_new_JavaClass(cx, jEnv, NULL, class_descriptor); if (!JavaClass_obj) { jsj_ExitJava(jsj_env); return JS_FALSE; } *rval = OBJECT_TO_JSVAL(JavaClass_obj); jsj_ExitJava(jsj_env); return JS_TRUE; } JS_STATIC_DLL_CALLBACK(JSBool) JavaClass_construct(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { JSObject *obj_arg, *JavaClass_obj; JavaObjectWrapper *java_wrapper; JavaClassDescriptor *class_descriptor; JNIEnv *jEnv; JSJavaThreadState *jsj_env; if (argc != 1 || !JSVAL_IS_OBJECT(argv[0]) || !(obj_arg = JSVAL_TO_OBJECT(argv[0])) || !JS_InstanceOf(cx, obj_arg, &JavaObject_class, 0) || ((java_wrapper = JS_GetPrivate(cx, obj_arg)) == NULL)) { JS_ReportErrorNumber(cx, jsj_GetErrorMessage, NULL, JSJMSG_NEED_JCLASS_ARG); return JS_FALSE; } jsj_env = jsj_EnterJava(cx, &jEnv); if (!jEnv) return JS_FALSE; class_descriptor = java_wrapper->class_descriptor; if (!(*jEnv)->IsSameObject(jEnv, class_descriptor->java_class, jlClass)) { JS_ReportErrorNumber(cx, jsj_GetErrorMessage, NULL, JSJMSG_NEED_JCLASS_ARG); jsj_ExitJava(jsj_env); return JS_FALSE; } class_descriptor = jsj_GetJavaClassDescriptor(cx, jEnv, java_wrapper->java_obj); JavaClass_obj = jsj_new_JavaClass(cx, jEnv, NULL, class_descriptor); if (!JavaClass_obj) { jsj_ExitJava(jsj_env); return JS_FALSE; } *rval = OBJECT_TO_JSVAL(JavaClass_obj); jsj_ExitJava(jsj_env); return JS_TRUE; } extern JS_IMPORT_DATA(JSObjectOps) js_ObjectOps; JSBool jsj_init_JavaClass(JSContext *cx, JSObject *global_obj) { /* Define JavaClass class */ if (!JS_InitClass(cx, global_obj, 0, &JavaClass_class, JavaClass_construct, 0, 0, 0, 0, 0)) return JS_FALSE; if (!JS_DefineFunction(cx, global_obj, "getClass", getClass, 0, JSPROP_READONLY)) return JS_FALSE; return jsj_InitJavaClassReflectionsTable(); }