gecko/js/src/liveconnect/jsj_JavaPackage.c

570 lines
20 KiB
C

/* -*- 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 the JavaPackage class.
*
* A JavaPackage is JavaScript's representation of a Java package. The
* JavaPackage object contains only a string, which is the path to the package,
* e.g. "java/lang". The JS properties of a JavaPackage are either nested packages
* or a JavaClass object, which represents the path to a Java class.
*
* Note that there is no equivalent to a JavaPackage object in Java. Example:
* 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 java.lang
* object in Java that exists at run time.
*
*/
#include <stdlib.h>
#include <string.h>
#include "jsj_private.h" /* LiveConnect internals */
#include "jsjava.h"
JSClass JavaPackage_class; /* Forward declaration */
/*
* The native part of a JavaPackage object. It gets stored in the object's
* private slot.
*/
typedef struct {
const char * path; /* e.g. "java/lang" or NULL if top level package */
int flags; /* e.g. PKG_SYSTEM, PKG_CLASS */
} JavaPackage_Private;
static JSObject *
define_JavaPackage(JSContext *cx, JSObject *parent_obj,
const char *obj_name, const char *path, int flags, int access)
{
JSObject *package_obj, *obj;
JavaPackage_Private *package;
jsval v;
/*
* Expose the same JSObject for Packages.java and java.
* "java" will be defined during the java package initialization stage.
* "Packages.java" will be lazily resolved by JavaPackage_resolve.
* Ditto for sun and netscape.
* See bugzilla bug: https://bugzilla.mozilla.org/show_bug.cgi?id=248409.
*/
if (!strcmp(obj_name, path) &&
(obj = JS_GetParent(cx, parent_obj)) &&
JS_LookupProperty(cx, obj, obj_name, &v) &&
!JSVAL_IS_PRIMITIVE(v))
{
if (!JS_DefineProperty(cx, parent_obj, obj_name, v, NULL, NULL,
JSPROP_PERMANENT | access)) {
return NULL;
}
package_obj = JSVAL_TO_OBJECT(v);
return package_obj;
}
package_obj = JS_DefineObject(cx, parent_obj, obj_name, &JavaPackage_class,
0, JSPROP_PERMANENT | access);
if (!package_obj)
return NULL;
/* Attach private, native data to the JS object */
package = (JavaPackage_Private *)JS_malloc(cx, sizeof(JavaPackage_Private));
if (!package) {
JS_DeleteProperty(cx, parent_obj, obj_name);
return NULL;
}
JS_SetPrivate(cx, package_obj, (void *)package);
if (path)
package->path = JS_strdup(cx, path);
else
package->path = "";
package->flags = flags;
/* Check for OOM */
if (!package->path) {
JS_DeleteProperty(cx, parent_obj, obj_name);
JS_free(cx, package);
return NULL;
}
return package_obj;
}
/* JavaPackage uses standard JS getProperty */
/*
* Don't allow user-defined properties to be set on Java package objects, e.g.
* it is illegal to write "java.lang.myProperty = 4". We probably could relax
* this restriction, but it's potentially confusing and not clearly useful.
*/
static JSBool
JavaPackage_setProperty(JSContext *cx, JSObject *obj, jsval slot, jsval *vp)
{
JavaPackage_Private *package = JS_GetPrivate(cx, obj);
if (!package) {
JS_ReportErrorNumber(cx, jsj_GetErrorMessage, NULL,
JSJMSG_BAD_ADD_TO_PACKAGE);
return JS_FALSE;
}
JS_ReportErrorNumber(cx, jsj_GetErrorMessage, NULL,
JSJMSG_DONT_ADD_TO_PACKAGE);
return JS_FALSE;
}
static JSBool quiet_resolve_failure;
/*
* Resolve a component name to be either the name of a class or a package.
*/
static JSBool
JavaPackage_resolve(JSContext *cx, JSObject *obj, jsval id)
{
JavaPackage_Private *package;
JSBool ok = JS_TRUE;
jclass jclazz;
char *subPath, *newPath;
const char *path;
JNIEnv *jEnv;
JSJavaThreadState *jsj_env;
/* Painful hack for pre_define_java_packages() */
if (quiet_resolve_failure)
return JS_FALSE;
package = (JavaPackage_Private *)JS_GetPrivate(cx, obj);
if (!package)
return JS_TRUE;
if (!JSVAL_IS_STRING(id))
return JS_TRUE;
subPath = JS_GetStringBytes(JSVAL_TO_STRING(id));
/*
* There will be an attempt to invoke the toString() method when producing
* the string representation of a JavaPackage. When this occurs, avoid
* creating a bogus toString package. (This means that no one can ever
* create a package with the simple name "toString", but we'll live with
* that limitation.)
*/
if (!strcmp(subPath, "toString"))
return JS_FALSE;
path = package->path;
newPath = JS_smprintf("%s%s%s", path, (path[0] ? "/" : ""), subPath);
if (!newPath) {
JS_ReportOutOfMemory(cx);
return JS_FALSE;
}
jsj_env = jsj_EnterJava(cx, &jEnv);
if (!jEnv) {
ok = JS_FALSE;
goto out;
}
/*
Unfortunately, Java provides no way to find out whether a particular
name is a package or not. The only way to tell is to try to load the
name as a class file and, if that fails, assume it's a package. This
makes things work as expected for the most part, but it has three
noticeable problems that keep coming up:
- You can refer to a package like java.lang.i.buried.paul without
generating a complaint. Of course, you'll never be able to refer to
any classes through it.
- An annoying consequence of the above is that misspelling a class name
results in a cryptic error about packages.
- In a browser context, i.e. where applets are involved, figuring out
whether something is a class may require looking for it over the net
using the current classloader. This means that the first time you
refer to java.lang.System in a js context, there will be an attempt
to search for [[DOCBASE]]/java.class on the server.
A solution is to explicitly tell jsjava the names of all the (local)
packages on the CLASSPATH. (Not implemented yet.)
*/
jclazz = (*jEnv)->FindClass(jEnv, newPath);
if (jclazz) {
JSObject *newClass;
newClass = jsj_define_JavaClass(cx, jEnv, obj, subPath, jclazz);
(*jEnv)->DeleteLocalRef(jEnv, jclazz);
if (!newClass) {
ok = JS_FALSE;
goto out;
}
} else {
/* We assume that any failed attempt to load a class is because it
doesn't exist. If we wanted to do a better job, we would check
the exception type and make sure that it's NoClassDefFoundError */
(*jEnv)->ExceptionClear(jEnv);
/*
* If there's no class of the given name, then we must be referring to
* a package. However, don't allow bogus sub-packages of pre-defined
* system packages to be created.
*/
if (JS_InstanceOf(cx, obj, &JavaPackage_class, NULL)) {
JavaPackage_Private *current_package;
current_package = JS_GetPrivate(cx, obj);
if (current_package->flags & PKG_SYSTEM) {
char *msg, *cp;
msg = JS_strdup(cx, newPath);
/* Check for OOM */
if (msg) {
/* Convert package of form "java/lang" to "java.lang" */
for (cp = msg; *cp != '\0'; cp++)
if (*cp == '/')
*cp = '.';
JS_ReportErrorNumber(cx, jsj_GetErrorMessage, NULL,
JSJMSG_MISSING_PACKAGE, msg);
free((char*)msg);
}
ok = JS_FALSE;
goto out;
}
}
if (!define_JavaPackage(cx, obj, subPath, newPath, 0, JSPROP_READONLY)) {
ok = JS_FALSE;
goto out;
}
#ifdef DEBUG
/* printf("JavaPackage \'%s\' created\n", newPath); */
#endif
}
out:
JS_smprintf_free(newPath);
jsj_ExitJava(jsj_env);
return ok;
}
static JSBool
JavaPackage_convert(JSContext *cx, JSObject *obj, JSType type, jsval *vp)
{
JSString *str;
char *name, *cp;
JavaPackage_Private *package = JS_GetPrivate(cx, obj);
if (!package) {
fprintf(stderr, "JavaPackage_resolve: no private data!\n");
return JS_FALSE;
}
switch (type) {
/* Pretty-printing of JavaPackage */
case JSTYPE_VOID: /* Default value */
case JSTYPE_NUMBER:
case JSTYPE_STRING:
/* Convert '/' to '.' so that it looks like Java language syntax. */
if (!package->path)
break;
name = JS_smprintf("[JavaPackage %s]", package->path);
if (!name) {
JS_ReportOutOfMemory(cx);
return JS_FALSE;
}
for (cp = name; *cp != '\0'; cp++)
if (*cp == '/')
*cp = '.';
str = JS_NewString(cx, name, strlen(name));
if (!str) {
JS_smprintf_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);
break;
case JSTYPE_OBJECT:
*vp = OBJECT_TO_JSVAL(obj);
break;
default:
break;
}
return JS_TRUE;
}
/*
* Free the private native data associated with the JavaPackage object.
*/
static void
JavaPackage_finalize(JSContext *cx, JSObject *obj)
{
JavaPackage_Private *package = JS_GetPrivate(cx, obj);
if (!package)
return;
if (package->path)
JS_free(cx, (char *)package->path);
JS_free(cx, package);
}
/*
* The definition of the JavaPackage class
*/
JSClass JavaPackage_class = {
"JavaPackage", JSCLASS_HAS_PRIVATE,
JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JavaPackage_setProperty,
JS_EnumerateStub, JavaPackage_resolve,
JavaPackage_convert, JavaPackage_finalize,
/* Optionally non-null members start here. */
NULL, /* getObjectOps */
NULL, /* checkAccess */
NULL, /* call */
NULL, /* construct */
NULL, /* xdrObject */
NULL, /* hasInstance */
NULL, /* mark */
0, /* spare */
};
JavaPackageDef
standard_java_packages[] = {
{"java", NULL, PKG_USER, 0},
{"java.applet", NULL, PKG_USER, JSPROP_READONLY},
{"java.awt", NULL, PKG_USER, JSPROP_READONLY},
{"java.awt.datatransfer",
NULL, PKG_SYSTEM, JSPROP_READONLY},
{"java.awt.event", NULL, PKG_SYSTEM, JSPROP_READONLY},
{"java.awt.image", NULL, PKG_SYSTEM, JSPROP_READONLY},
{"java.awt.peer", NULL, PKG_SYSTEM, JSPROP_READONLY},
{"java.beans", NULL, PKG_USER, JSPROP_READONLY},
{"java.io", NULL, PKG_SYSTEM, JSPROP_READONLY},
{"java.lang", NULL, PKG_USER, JSPROP_READONLY},
{"java.lang.reflect", NULL, PKG_SYSTEM, JSPROP_READONLY},
{"java.math", NULL, PKG_SYSTEM, JSPROP_READONLY},
{"java.net", NULL, PKG_USER, JSPROP_READONLY},
{"java.rmi", NULL, PKG_USER, JSPROP_READONLY},
{"java.rmi.dgc", NULL, PKG_USER, JSPROP_READONLY},
{"java.rmi.user", NULL, PKG_USER, JSPROP_READONLY},
{"java.rmi.registry", NULL, PKG_USER, JSPROP_READONLY},
{"java.rmi.server", NULL, PKG_USER, JSPROP_READONLY},
{"java.security", NULL, PKG_USER, JSPROP_READONLY},
{"java.security.acl", NULL, PKG_SYSTEM, JSPROP_READONLY},
{"java.security.interfaces",
NULL, PKG_SYSTEM, JSPROP_READONLY},
{"java.sql", NULL, PKG_USER, JSPROP_READONLY},
{"java.text", NULL, PKG_USER, JSPROP_READONLY},
{"java.text.resources", NULL, PKG_SYSTEM, JSPROP_READONLY},
{"java.util", NULL, PKG_USER, JSPROP_READONLY},
{"java.util.zip", NULL, PKG_SYSTEM, JSPROP_READONLY},
{"netscape", NULL, PKG_USER, 0},
{"netscape.applet", NULL, PKG_SYSTEM, JSPROP_READONLY},
{"netscape.application",NULL, PKG_SYSTEM, JSPROP_READONLY},
{"netscape.debug", NULL, PKG_SYSTEM, JSPROP_READONLY},
{"netscape.javascript", NULL, PKG_SYSTEM, JSPROP_READONLY},
{"netscape.ldap", NULL, PKG_SYSTEM, JSPROP_READONLY},
{"netscape.misc", NULL, PKG_SYSTEM, JSPROP_READONLY},
{"netscape.net", NULL, PKG_SYSTEM, JSPROP_READONLY},
{"netscape.plugin", NULL, PKG_SYSTEM, JSPROP_READONLY},
{"netscape.util", NULL, PKG_SYSTEM, JSPROP_READONLY},
{"netscape.secfile", NULL, PKG_SYSTEM, JSPROP_READONLY},
{"netscape.security", NULL, PKG_SYSTEM, JSPROP_READONLY},
{"netscape.WAI", NULL, PKG_SYSTEM, JSPROP_READONLY},
{"sun", NULL, PKG_USER, 0},
{"Packages", "", PKG_USER, JSPROP_READONLY},
{NULL, NULL, 0, 0}
};
/*
* On systems which provide strtok_r we'll use that function to avoid
* problems with non-thread-safety.
*/
#if HAVE_STRTOK_R
# define STRTOK_1ST(str, seps, res) strtok_r (str, seps, &res)
# define STRTOK_OTHER(seps, res) strtok_r (res, seps, &res)
#else
# define STRTOK_1ST(str, seps, res) strtok (str, seps)
# define STRTOK_OTHER(seps, res) strtok (NULL, seps)
#endif
/*
* Pre-define a hierarchy of JavaPackage objects.
* Pre-defining a Java package at initialization time is not necessary, but
* it will make package lookup faster and, more importantly, will avoid
* unnecessary network accesses if classes are being loaded over the network.
*/
static JSBool
pre_define_java_packages(JSContext *cx, JSObject *global_obj,
JavaPackageDef *predefined_packages)
{
JSBool package_exists;
JSObject *parent_obj;
JavaPackageDef *package_def;
char *simple_name, *cp, *package_name, *path;
int flags;
if (!predefined_packages)
return JS_TRUE;
/* Iterate over all pre-defined Java packages */
for (package_def = predefined_packages; package_def->name; package_def++) {
#if HAVE_STRTOK_R
char *nextstr;
#endif
package_name = path = NULL;
parent_obj = global_obj;
package_name = strdup(package_def->name);
if (!package_name)
goto out_of_memory;
/* Walk the chain of JavaPackage objects to get to the parent of the
rightmost sub-package in the fully-qualified package name. */
for (simple_name = STRTOK_1ST(package_name, ".", nextstr); simple_name; simple_name = STRTOK_OTHER(".", nextstr)) {
jsval v;
/* Check to see if the sub-package already exists */
quiet_resolve_failure = JS_TRUE;
package_exists = JS_LookupProperty(cx, parent_obj, simple_name, &v) && JSVAL_IS_OBJECT(v);
quiet_resolve_failure = JS_FALSE;
if (package_exists) {
parent_obj = JSVAL_TO_OBJECT(v);
continue;
}
/* New package objects should only be created at the terminal
sub-package in a fully-qualified package-name */
if (STRTOK_OTHER(".", nextstr)) {
JS_ReportErrorNumber(cx, jsj_GetErrorMessage, NULL,
JSJMSG_BAD_PACKAGE_PREDEF,
package_def->name);
goto error;
}
if (package_def->path) {
path = strdup(package_def->path);
if (!path)
goto out_of_memory;
} else {
/*
* The default path is specified, so create it from the
* fully-qualified package name.
*/
path = strdup(package_def->name);
if (!path)
goto out_of_memory;
/* Transform package name, e.g. "java.lang" ==> "java/lang" */
for (cp = path; *cp != '\0'; cp++) {
if (*cp == '.')
*cp = '/';
}
}
flags = package_def->flags;
parent_obj = define_JavaPackage(cx, parent_obj, simple_name, path, flags,
package_def->access);
if (!parent_obj)
goto error;
free(path);
break;
}
free(package_name);
}
return JS_TRUE;
out_of_memory:
JS_ReportOutOfMemory(cx);
error:
JS_FREE_IF(cx, package_name);
JS_FREE_IF(cx, path);
return JS_FALSE;
}
static JSBool
JavaPackage_toString(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
jsval *rval)
{
if (!JS_InstanceOf(cx, obj, &JavaPackage_class, argv))
return JS_FALSE;
return JavaPackage_convert(cx, obj, JSTYPE_STRING, rval);
}
static JSFunctionSpec JavaPackage_methods[] = {
{"toString", JavaPackage_toString, 0, 0, 0},
{0, 0, 0, 0, 0},
};
/*
* One-time initialization for the JavaPackage class. (This is not
* run once per thread, rather it's run once for a given JSContext.)
*/
JSBool
jsj_init_JavaPackage(JSContext *cx, JSObject *global_obj,
JavaPackageDef *additional_predefined_packages) {
/* Define JavaPackage class */
if (!JS_InitClass(cx, global_obj, 0, &JavaPackage_class,
0, 0, 0, JavaPackage_methods, 0, 0))
return JS_FALSE;
/* Add top-level packages, e.g. : java, netscape, sun */
if (!pre_define_java_packages(cx, global_obj, standard_java_packages))
return JS_FALSE;
if (!pre_define_java_packages(cx, global_obj, additional_predefined_packages))
return JS_FALSE;
return JS_TRUE;
}