330 lines
12 KiB
Java
330 lines
12 KiB
Java
/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
|
*
|
|
* ***** BEGIN LICENSE BLOCK *****
|
|
* Version: MPL 1.1/GPL 2.0
|
|
*
|
|
* 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 Rhino code, released
|
|
* May 6, 1999.
|
|
*
|
|
* The Initial Developer of the Original Code is
|
|
* Netscape Communications Corporation.
|
|
* Portions created by the Initial Developer are Copyright (C) 1997-1999
|
|
* the Initial Developer. All Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
* Norris Boyd
|
|
* Frank Mitchell
|
|
* Mike Shaver
|
|
* Kurt Westerfeld
|
|
* Kemal Bayram
|
|
* Ulrike Mueller <umueller@demandware.com>
|
|
*
|
|
* Alternatively, the contents of this file may be used under the terms of
|
|
* the GNU General Public License Version 2 or later (the "GPL"), in which
|
|
* case the provisions of the GPL are applicable instead of those above. If
|
|
* you wish to allow use of your version of this file only under the terms of
|
|
* the GPL and not to allow others to use your version of this file under the
|
|
* MPL, indicate your decision by deleting the provisions above and replacing
|
|
* them with the notice and other provisions required by the GPL. If you do
|
|
* not delete the provisions above, a recipient may use your version of this
|
|
* file under either the MPL or the GPL.
|
|
*
|
|
* ***** END LICENSE BLOCK ***** */
|
|
|
|
package org.mozilla.javascript;
|
|
|
|
import java.lang.reflect.*;
|
|
import java.util.Map;
|
|
|
|
/**
|
|
* This class reflects Java classes into the JavaScript environment, mainly
|
|
* for constructors and static members. We lazily reflect properties,
|
|
* and currently do not guarantee that a single j.l.Class is only
|
|
* reflected once into the JS environment, although we should.
|
|
* The only known case where multiple reflections
|
|
* are possible occurs when a j.l.Class is wrapped as part of a
|
|
* method return or property access, rather than by walking the
|
|
* Packages/java tree.
|
|
*
|
|
* @author Mike Shaver
|
|
* @see NativeJavaArray
|
|
* @see NativeJavaObject
|
|
* @see NativeJavaPackage
|
|
*/
|
|
|
|
public class NativeJavaClass extends NativeJavaObject implements Function
|
|
{
|
|
static final long serialVersionUID = -6460763940409461664L;
|
|
|
|
// Special property for getting the underlying Java class object.
|
|
static final String javaClassPropertyName = "__javaObject__";
|
|
|
|
public NativeJavaClass() {
|
|
}
|
|
|
|
public NativeJavaClass(Scriptable scope, Class<?> cl) {
|
|
this.parent = scope;
|
|
this.javaObject = cl;
|
|
initMembers();
|
|
}
|
|
|
|
@Override
|
|
protected void initMembers() {
|
|
Class<?> cl = (Class<?>)javaObject;
|
|
members = JavaMembers.lookupClass(parent, cl, cl, false);
|
|
staticFieldAndMethods
|
|
= members.getFieldAndMethodsObjects(this, cl, true);
|
|
}
|
|
|
|
@Override
|
|
public String getClassName() {
|
|
return "JavaClass";
|
|
}
|
|
|
|
@Override
|
|
public boolean has(String name, Scriptable start) {
|
|
return members.has(name, true) || javaClassPropertyName.equals(name);
|
|
}
|
|
|
|
@Override
|
|
public Object get(String name, Scriptable start) {
|
|
// When used as a constructor, ScriptRuntime.newObject() asks
|
|
// for our prototype to create an object of the correct type.
|
|
// We don't really care what the object is, since we're returning
|
|
// one constructed out of whole cloth, so we return null.
|
|
if (name.equals("prototype"))
|
|
return null;
|
|
|
|
if (staticFieldAndMethods != null) {
|
|
Object result = staticFieldAndMethods.get(name);
|
|
if (result != null)
|
|
return result;
|
|
}
|
|
|
|
if (members.has(name, true)) {
|
|
return members.get(this, name, javaObject, true);
|
|
}
|
|
|
|
if (javaClassPropertyName.equals(name)) {
|
|
Context cx = Context.getContext();
|
|
Scriptable scope = ScriptableObject.getTopLevelScope(start);
|
|
return cx.getWrapFactory().wrap(cx, scope, javaObject,
|
|
ScriptRuntime.ClassClass);
|
|
}
|
|
|
|
// experimental: look for nested classes by appending $name to
|
|
// current class' name.
|
|
Class<?> nestedClass = findNestedClass(getClassObject(), name);
|
|
if (nestedClass != null) {
|
|
NativeJavaClass nestedValue = new NativeJavaClass
|
|
(ScriptableObject.getTopLevelScope(this), nestedClass);
|
|
nestedValue.setParentScope(this);
|
|
return nestedValue;
|
|
}
|
|
|
|
throw members.reportMemberNotFound(name);
|
|
}
|
|
|
|
@Override
|
|
public void put(String name, Scriptable start, Object value) {
|
|
members.put(this, name, javaObject, value, true);
|
|
}
|
|
|
|
@Override
|
|
public Object[] getIds() {
|
|
return members.getIds(true);
|
|
}
|
|
|
|
public Class<?> getClassObject() {
|
|
return (Class<?>) super.unwrap();
|
|
}
|
|
|
|
@Override
|
|
public Object getDefaultValue(Class<?> hint) {
|
|
if (hint == null || hint == ScriptRuntime.StringClass)
|
|
return this.toString();
|
|
if (hint == ScriptRuntime.BooleanClass)
|
|
return Boolean.TRUE;
|
|
if (hint == ScriptRuntime.NumberClass)
|
|
return ScriptRuntime.NaNobj;
|
|
return this;
|
|
}
|
|
|
|
public Object call(Context cx, Scriptable scope, Scriptable thisObj,
|
|
Object[] args)
|
|
{
|
|
// If it looks like a "cast" of an object to this class type,
|
|
// walk the prototype chain to see if there's a wrapper of a
|
|
// object that's an instanceof this class.
|
|
if (args.length == 1 && args[0] instanceof Scriptable) {
|
|
Class<?> c = getClassObject();
|
|
Scriptable p = (Scriptable) args[0];
|
|
do {
|
|
if (p instanceof Wrapper) {
|
|
Object o = ((Wrapper) p).unwrap();
|
|
if (c.isInstance(o))
|
|
return p;
|
|
}
|
|
p = p.getPrototype();
|
|
} while (p != null);
|
|
}
|
|
return construct(cx, scope, args);
|
|
}
|
|
|
|
public Scriptable construct(Context cx, Scriptable scope, Object[] args)
|
|
{
|
|
Class<?> classObject = getClassObject();
|
|
int modifiers = classObject.getModifiers();
|
|
if (! (Modifier.isInterface(modifiers) ||
|
|
Modifier.isAbstract(modifiers)))
|
|
{
|
|
MemberBox[] ctors = members.ctors;
|
|
int index = NativeJavaMethod.findFunction(cx, ctors, args);
|
|
if (index < 0) {
|
|
String sig = NativeJavaMethod.scriptSignature(args);
|
|
throw Context.reportRuntimeError2(
|
|
"msg.no.java.ctor", classObject.getName(), sig);
|
|
}
|
|
|
|
// Found the constructor, so try invoking it.
|
|
return constructSpecific(cx, scope, args, ctors[index]);
|
|
} else {
|
|
Scriptable topLevel = ScriptableObject.getTopLevelScope(this);
|
|
String msg = "";
|
|
try {
|
|
// trying to construct an interface; use JavaAdapter to
|
|
// construct a new class on the fly that implements this
|
|
// interface.
|
|
Object v = topLevel.get("JavaAdapter", topLevel);
|
|
if (v != NOT_FOUND) {
|
|
Function f = (Function) v;
|
|
Object[] adapterArgs = { this, args[0] };
|
|
return f.construct(cx, topLevel,adapterArgs);
|
|
}
|
|
} catch (Exception ex) {
|
|
// fall through to error
|
|
String m = ex.getMessage();
|
|
if (m != null)
|
|
msg = m;
|
|
}
|
|
throw Context.reportRuntimeError2(
|
|
"msg.cant.instantiate", msg, classObject.getName());
|
|
}
|
|
}
|
|
|
|
static Scriptable constructSpecific(Context cx, Scriptable scope,
|
|
Object[] args, MemberBox ctor)
|
|
{
|
|
Scriptable topLevel = ScriptableObject.getTopLevelScope(scope);
|
|
Class<?>[] argTypes = ctor.argTypes;
|
|
|
|
if (ctor.vararg) {
|
|
// marshall the explicit parameter
|
|
Object[] newArgs = new Object[argTypes.length];
|
|
for (int i = 0; i < argTypes.length-1; i++) {
|
|
newArgs[i] = Context.jsToJava(args[i], argTypes[i]);
|
|
}
|
|
|
|
Object varArgs;
|
|
|
|
// Handle special situation where a single variable parameter
|
|
// is given and it is a Java or ECMA array.
|
|
if (args.length == argTypes.length &&
|
|
(args[args.length-1] == null ||
|
|
args[args.length-1] instanceof NativeArray ||
|
|
args[args.length-1] instanceof NativeJavaArray))
|
|
{
|
|
// convert the ECMA array into a native array
|
|
varArgs = Context.jsToJava(args[args.length-1],
|
|
argTypes[argTypes.length - 1]);
|
|
} else {
|
|
// marshall the variable parameter
|
|
Class<?> componentType = argTypes[argTypes.length - 1].
|
|
getComponentType();
|
|
varArgs = Array.newInstance(componentType,
|
|
args.length - argTypes.length + 1);
|
|
for (int i=0; i < Array.getLength(varArgs); i++) {
|
|
Object value = Context.jsToJava(args[argTypes.length-1 + i],
|
|
componentType);
|
|
Array.set(varArgs, i, value);
|
|
}
|
|
}
|
|
|
|
// add varargs
|
|
newArgs[argTypes.length-1] = varArgs;
|
|
// replace the original args with the new one
|
|
args = newArgs;
|
|
} else {
|
|
Object[] origArgs = args;
|
|
for (int i = 0; i < args.length; i++) {
|
|
Object arg = args[i];
|
|
Object x = Context.jsToJava(arg, argTypes[i]);
|
|
if (x != arg) {
|
|
if (args == origArgs) {
|
|
args = origArgs.clone();
|
|
}
|
|
args[i] = x;
|
|
}
|
|
}
|
|
}
|
|
|
|
Object instance = ctor.newInstance(args);
|
|
// we need to force this to be wrapped, because construct _has_
|
|
// to return a scriptable
|
|
return cx.getWrapFactory().wrapNewObject(cx, topLevel, instance);
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return "[JavaClass " + getClassObject().getName() + "]";
|
|
}
|
|
|
|
/**
|
|
* Determines if prototype is a wrapped Java object and performs
|
|
* a Java "instanceof".
|
|
* Exception: if value is an instance of NativeJavaClass, it isn't
|
|
* considered an instance of the Java class; this forestalls any
|
|
* name conflicts between java.lang.Class's methods and the
|
|
* static methods exposed by a JavaNativeClass.
|
|
*/
|
|
@Override
|
|
public boolean hasInstance(Scriptable value) {
|
|
|
|
if (value instanceof Wrapper &&
|
|
!(value instanceof NativeJavaClass)) {
|
|
Object instance = ((Wrapper)value).unwrap();
|
|
|
|
return getClassObject().isInstance(instance);
|
|
}
|
|
|
|
// value wasn't something we understand
|
|
return false;
|
|
}
|
|
|
|
private static Class<?> findNestedClass(Class<?> parentClass, String name) {
|
|
String nestedClassName = parentClass.getName() + '$' + name;
|
|
ClassLoader loader = parentClass.getClassLoader();
|
|
if (loader == null) {
|
|
// ALERT: if loader is null, nested class should be loaded
|
|
// via system class loader which can be different from the
|
|
// loader that brought Rhino classes that Class.forName() would
|
|
// use, but ClassLoader.getSystemClassLoader() is Java 2 only
|
|
return Kit.classOrNull(nestedClassName);
|
|
} else {
|
|
return Kit.classOrNull(loader, nestedClassName);
|
|
}
|
|
}
|
|
|
|
private Map<String,FieldAndMethods> staticFieldAndMethods;
|
|
}
|