583 lines
17 KiB
C
583 lines
17 KiB
C
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
|
*
|
|
* The contents of this file are subject to the Netscape Public License
|
|
* Version 1.0 (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/NPL/
|
|
*
|
|
* 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.
|
|
*
|
|
* The Initial Developer of the Original Code is Netscape Communications
|
|
* Corporation. Portions created by Netscape are Copyright (C) 1998
|
|
* Netscape Communications Corporation. All Rights Reserved.
|
|
*/
|
|
|
|
/* 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 <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "prtypes.h"
|
|
#include "prprintf.h"
|
|
#include "prassert.h"
|
|
|
|
#include "jsj_private.h" /* LiveConnect internals */
|
|
|
|
static 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 = PR_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:
|
|
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_ReportError(cx, "invalid JavaClass property expression. "
|
|
"(methods and fields of a JavaClass object can only be identified by their name)");
|
|
return JS_FALSE;
|
|
}
|
|
|
|
member_name = JS_GetStringBytes(JSVAL_TO_STRING(idval));
|
|
|
|
/* Why do we have to do this ? */
|
|
if (!strcmp(member_name, "prototype")) {
|
|
*memberp = NULL;
|
|
return JS_TRUE;
|
|
}
|
|
|
|
JS_ReportError(cx, "Java class %s has no public static field or method named \"%s\"",
|
|
class_descriptor->name, member_name);
|
|
return JS_FALSE;
|
|
}
|
|
if (memberp)
|
|
*memberp = member_descriptor;
|
|
return JS_TRUE;
|
|
}
|
|
|
|
PR_STATIC_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;
|
|
|
|
/* printf("In JavaClass_getProperty\n"); */
|
|
|
|
/* Get the Java per-thread environment pointer for this JSContext */
|
|
jsj_MapJSContextToJSJThread(cx, &jEnv);
|
|
if (!jEnv)
|
|
return JS_FALSE;
|
|
|
|
if (!lookup_static_member_by_id(cx, jEnv, obj, &class_descriptor, id, &member_descriptor))
|
|
return JS_FALSE;
|
|
if (!member_descriptor) {
|
|
*vp = JSVAL_VOID;
|
|
return JS_TRUE;
|
|
}
|
|
|
|
java_class = class_descriptor->java_class;
|
|
|
|
if (member_descriptor->field) {
|
|
if (!member_descriptor->methods) {
|
|
return jsj_GetJavaFieldValue(cx, jEnv, member_descriptor->field, java_class, vp);
|
|
} else {
|
|
PR_ASSERT(0);
|
|
}
|
|
} else {
|
|
JSFunction *function;
|
|
|
|
/* TODO - eliminate JSFUN_BOUND_METHOD */
|
|
JS_IdToValue(cx, id, &idval);
|
|
member_name = JS_GetStringBytes(JSVAL_TO_STRING(idval));
|
|
function = JS_NewFunction(cx, jsj_JavaStaticMethodWrapper, 0,
|
|
JSFUN_BOUND_METHOD, obj, member_name);
|
|
if (!function)
|
|
return JS_FALSE;
|
|
|
|
*vp = OBJECT_TO_JSVAL(JS_GetFunctionObject(function));
|
|
return JS_TRUE;
|
|
}
|
|
}
|
|
|
|
PR_STATIC_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;
|
|
|
|
/* printf("In JavaClass_setProperty\n"); */
|
|
|
|
/* Get the Java per-thread environment pointer for this JSContext */
|
|
jsj_MapJSContextToJSJThread(cx, &jEnv);
|
|
if (!jEnv)
|
|
return JS_FALSE;
|
|
|
|
if (!lookup_static_member_by_id(cx, jEnv, obj, &class_descriptor, id, &member_descriptor))
|
|
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)
|
|
return JS_TRUE;
|
|
|
|
java_class = class_descriptor->java_class;
|
|
return jsj_SetJavaFieldValue(cx, jEnv, member_descriptor->field, java_class, *vp);
|
|
|
|
no_such_field:
|
|
JS_IdToValue(cx, id, &idval);
|
|
member_name = JS_GetStringBytes(JSVAL_TO_STRING(idval));
|
|
JS_ReportError(cx, "No static field named \"%s\" in Java class %s",
|
|
member_name, class_descriptor->name);
|
|
return JS_FALSE;
|
|
}
|
|
|
|
/*
|
|
* Free the private native data associated with the JavaPackage object.
|
|
*/
|
|
PR_STATIC_CALLBACK(void)
|
|
JavaClass_finalize(JSContext *cx, JSObject *obj)
|
|
{
|
|
JNIEnv *jEnv;
|
|
|
|
JavaClassDescriptor *class_descriptor = JS_GetPrivate(cx, obj);
|
|
if (!class_descriptor)
|
|
return;
|
|
|
|
/* Get the Java per-thread environment pointer for this JSContext */
|
|
jsj_MapJSContextToJSJThread(cx, &jEnv);
|
|
if (!jEnv)
|
|
return;
|
|
|
|
printf("Finalizing %s\n", class_descriptor->name);
|
|
jsj_ReleaseJavaClassDescriptor(cx, jEnv, class_descriptor);
|
|
}
|
|
|
|
|
|
static JSBool
|
|
JavaClass_lookupProperty(JSContext *cx, JSObject *obj, jsid id,
|
|
JSObject **objp, JSProperty **propp
|
|
#if defined JS_THREADSAFE && defined DEBUG
|
|
, const char *file, uintN line
|
|
#endif
|
|
)
|
|
{
|
|
JNIEnv *jEnv;
|
|
|
|
/* printf("In JavaClass_lookupProperty()\n"); */
|
|
|
|
/* Get the Java per-thread environment pointer for this JSContext */
|
|
jsj_MapJSContextToJSJThread(cx, &jEnv);
|
|
if (!jEnv)
|
|
return JS_FALSE;
|
|
|
|
if (!lookup_static_member_by_id(cx, jEnv, obj, NULL, id, NULL))
|
|
return JS_FALSE;
|
|
*objp = obj;
|
|
*propp = (JSProperty*)1;
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static JSBool
|
|
JavaClass_defineProperty(JSContext *cx, JSObject *obj, jsid id, jsval value,
|
|
JSPropertyOp getter, JSPropertyOp setter,
|
|
uintN attrs, JSProperty **propp)
|
|
{
|
|
JS_ReportError(cx, "Cannot define a new property in a JavaClass");
|
|
return JS_FALSE;
|
|
}
|
|
|
|
static 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;
|
|
}
|
|
|
|
static 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) {
|
|
PR_ASSERT(0);
|
|
return JS_FALSE;
|
|
}
|
|
|
|
/* Silently ignore all setAttribute attempts */
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static 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_ReportError(cx, "Properties of JavaClass objects may not be deleted");
|
|
return JS_FALSE;
|
|
} else {
|
|
/* Attempts to delete permanent properties are silently ignored
|
|
by ECMAScript. */
|
|
return JS_TRUE;
|
|
}
|
|
}
|
|
|
|
static JSBool
|
|
JavaClass_defaultValue(JSContext *cx, JSObject *obj, JSType type, jsval *vp)
|
|
{
|
|
/* printf("In JavaClass_defaultValue()\n"); */
|
|
return JavaClass_convert(cx, obj, JSTYPE_STRING, vp);
|
|
}
|
|
|
|
static JSBool
|
|
JavaClass_newEnumerate(JSContext *cx, JSObject *obj, JSIterateOp enum_op,
|
|
jsval *statep, jsid *idp)
|
|
{
|
|
JavaMemberDescriptor *member_descriptor;
|
|
JavaClassDescriptor *class_descriptor;
|
|
JNIEnv *jEnv;
|
|
|
|
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_MapJSContextToJSJThread(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);
|
|
return JS_TRUE;
|
|
|
|
case JSENUMERATE_NEXT:
|
|
member_descriptor = JSVAL_TO_PRIVATE(*statep);
|
|
if (member_descriptor) {
|
|
*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:
|
|
PR_ASSERT(0);
|
|
return JS_FALSE;
|
|
}
|
|
}
|
|
|
|
static JSBool
|
|
JavaClass_checkAccess(JSContext *cx, JSObject *obj, jsid id,
|
|
JSAccessMode mode, jsval *vp, uintN *attrsp)
|
|
{
|
|
switch (mode) {
|
|
case JSACC_WATCH:
|
|
JS_ReportError(cx, "Cannot place watchpoints on JavaClass object properties");
|
|
return JS_FALSE;
|
|
|
|
case JSACC_IMPORT:
|
|
JS_ReportError(cx, "Cannot export a JavaClass object's properties");
|
|
return JS_FALSE;
|
|
|
|
default:
|
|
return JS_TRUE;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Implement the JavaScript instanceof operator for JavaClass objects by using
|
|
* the equivalent Java instanceof operation.
|
|
*/
|
|
static 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;
|
|
|
|
has_instance = JS_FALSE;
|
|
class_descriptor = JS_GetPrivate(cx, obj);
|
|
if (!class_descriptor) {
|
|
JS_ReportError(cx, "illegal operation on JavaClass prototype object");
|
|
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);
|
|
js_class = JS_GetClass(candidate_obj);
|
|
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_ReportError(cx, "illegal operation on prototype object");
|
|
return JS_FALSE;
|
|
}
|
|
java_obj = java_wrapper->java_obj;
|
|
/* Get JNI pointer */
|
|
jsj_MapJSContextToJSJThread(cx, &jEnv);
|
|
has_instance = (*jEnv)->IsInstanceOf(jEnv, java_obj, java_class);
|
|
|
|
done:
|
|
*has_instancep = has_instance;
|
|
return JS_TRUE;
|
|
}
|
|
|
|
JSObjectOps JavaClass_ops = {
|
|
/* Mandatory non-null function pointer members. */
|
|
NULL, /* newObjectMap */
|
|
NULL, /* 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 */
|
|
};
|
|
|
|
static 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,
|
|
JavaClass_getObjectOps,
|
|
};
|
|
|
|
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]
|
|
*/
|
|
static 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;
|
|
|
|
jsj_MapJSContextToJSJThread(cx, &jEnv);
|
|
if (!jEnv)
|
|
return JS_FALSE;
|
|
|
|
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_ReportError(cx, "getClass expects a Java object argument");
|
|
return JS_FALSE;
|
|
}
|
|
|
|
java_wrapper = JS_GetPrivate(cx, obj_arg);
|
|
if (!java_wrapper) {
|
|
JS_ReportError(cx, "getClass called on prototype object");
|
|
return JS_FALSE;
|
|
}
|
|
|
|
class_descriptor = java_wrapper->class_descriptor;
|
|
|
|
JavaClass_obj = jsj_new_JavaClass(cx, jEnv, NULL, class_descriptor);
|
|
if (!JavaClass_obj)
|
|
return JS_FALSE;
|
|
*rval = OBJECT_TO_JSVAL(JavaClass_obj);
|
|
return JS_TRUE;
|
|
}
|
|
|
|
extern PR_IMPORT_DATA(JSObjectOps) js_ObjectOps;
|
|
|
|
JSBool
|
|
jsj_init_JavaClass(JSContext *cx, JSObject *global_obj)
|
|
{
|
|
JavaClass_ops.newObjectMap = js_ObjectOps.newObjectMap;
|
|
JavaClass_ops.destroyObjectMap = js_ObjectOps.destroyObjectMap;
|
|
|
|
/* Define JavaClass class */
|
|
if (!JS_InitClass(cx, global_obj, 0, &JavaClass_class, 0, 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();
|
|
}
|
|
|