1003 lines
33 KiB
Java
1003 lines
33 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-2000
|
|
* the Initial Developer. All Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
* Norris Boyd
|
|
* Igor Bukanov
|
|
* Frank Mitchell
|
|
* Mike Shaver
|
|
* Kemal Bayram
|
|
*
|
|
* 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.io.*;
|
|
import java.lang.reflect.*;
|
|
import java.util.Map;
|
|
import java.util.Date;
|
|
|
|
/**
|
|
* This class reflects non-Array Java objects into the JavaScript environment. It
|
|
* reflect fields directly, and uses NativeJavaMethod objects to reflect (possibly
|
|
* overloaded) methods.<p>
|
|
*
|
|
* @author Mike Shaver
|
|
* @see NativeJavaArray
|
|
* @see NativeJavaPackage
|
|
* @see NativeJavaClass
|
|
*/
|
|
|
|
public class NativeJavaObject implements Scriptable, Wrapper, Serializable
|
|
{
|
|
static final long serialVersionUID = -6948590651130498591L;
|
|
|
|
public NativeJavaObject() { }
|
|
|
|
public NativeJavaObject(Scriptable scope, Object javaObject,
|
|
Class<?> staticType)
|
|
{
|
|
this(scope, javaObject, staticType, false);
|
|
}
|
|
|
|
public NativeJavaObject(Scriptable scope, Object javaObject,
|
|
Class<?> staticType, boolean isAdapter)
|
|
{
|
|
this.parent = scope;
|
|
this.javaObject = javaObject;
|
|
this.staticType = staticType;
|
|
this.isAdapter = isAdapter;
|
|
initMembers();
|
|
}
|
|
|
|
protected void initMembers() {
|
|
Class<?> dynamicType;
|
|
if (javaObject != null) {
|
|
dynamicType = javaObject.getClass();
|
|
} else {
|
|
dynamicType = staticType;
|
|
}
|
|
members = JavaMembers.lookupClass(parent, dynamicType, staticType,
|
|
isAdapter);
|
|
fieldAndMethods
|
|
= members.getFieldAndMethodsObjects(this, javaObject, false);
|
|
}
|
|
|
|
public boolean has(String name, Scriptable start) {
|
|
return members.has(name, false);
|
|
}
|
|
|
|
public boolean has(int index, Scriptable start) {
|
|
return false;
|
|
}
|
|
|
|
public Object get(String name, Scriptable start) {
|
|
if (fieldAndMethods != null) {
|
|
Object result = fieldAndMethods.get(name);
|
|
if (result != null) {
|
|
return result;
|
|
}
|
|
}
|
|
// TODO: passing 'this' as the scope is bogus since it has
|
|
// no parent scope
|
|
return members.get(this, name, javaObject, false);
|
|
}
|
|
|
|
public Object get(int index, Scriptable start) {
|
|
throw members.reportMemberNotFound(Integer.toString(index));
|
|
}
|
|
|
|
public void put(String name, Scriptable start, Object value) {
|
|
// We could be asked to modify the value of a property in the
|
|
// prototype. Since we can't add a property to a Java object,
|
|
// we modify it in the prototype rather than copy it down.
|
|
if (prototype == null || members.has(name, false))
|
|
members.put(this, name, javaObject, value, false);
|
|
else
|
|
prototype.put(name, prototype, value);
|
|
}
|
|
|
|
public void put(int index, Scriptable start, Object value) {
|
|
throw members.reportMemberNotFound(Integer.toString(index));
|
|
}
|
|
|
|
public boolean hasInstance(Scriptable value) {
|
|
// This is an instance of a Java class, so always return false
|
|
return false;
|
|
}
|
|
|
|
public void delete(String name) {
|
|
}
|
|
|
|
public void delete(int index) {
|
|
}
|
|
|
|
public Scriptable getPrototype() {
|
|
if (prototype == null && javaObject instanceof String) {
|
|
return ScriptableObject.getClassPrototype(parent, "String");
|
|
}
|
|
return prototype;
|
|
}
|
|
|
|
/**
|
|
* Sets the prototype of the object.
|
|
*/
|
|
public void setPrototype(Scriptable m) {
|
|
prototype = m;
|
|
}
|
|
|
|
/**
|
|
* Returns the parent (enclosing) scope of the object.
|
|
*/
|
|
public Scriptable getParentScope() {
|
|
return parent;
|
|
}
|
|
|
|
/**
|
|
* Sets the parent (enclosing) scope of the object.
|
|
*/
|
|
public void setParentScope(Scriptable m) {
|
|
parent = m;
|
|
}
|
|
|
|
public Object[] getIds() {
|
|
return members.getIds(false);
|
|
}
|
|
|
|
/**
|
|
@deprecated Use {@link Context#getWrapFactory()} together with calling {@link
|
|
WrapFactory#wrap(Context, Scriptable, Object, Class)}
|
|
*/
|
|
public static Object wrap(Scriptable scope, Object obj, Class<?> staticType) {
|
|
|
|
Context cx = Context.getContext();
|
|
return cx.getWrapFactory().wrap(cx, scope, obj, staticType);
|
|
}
|
|
|
|
public Object unwrap() {
|
|
return javaObject;
|
|
}
|
|
|
|
public String getClassName() {
|
|
return "JavaObject";
|
|
}
|
|
|
|
public Object getDefaultValue(Class<?> hint)
|
|
{
|
|
Object value;
|
|
if (hint == null) {
|
|
if (javaObject instanceof Boolean) {
|
|
hint = ScriptRuntime.BooleanClass;
|
|
}
|
|
}
|
|
if (hint == null || hint == ScriptRuntime.StringClass) {
|
|
value = javaObject.toString();
|
|
} else {
|
|
String converterName;
|
|
if (hint == ScriptRuntime.BooleanClass) {
|
|
converterName = "booleanValue";
|
|
} else if (hint == ScriptRuntime.NumberClass) {
|
|
converterName = "doubleValue";
|
|
} else {
|
|
throw Context.reportRuntimeError0("msg.default.value");
|
|
}
|
|
Object converterObject = get(converterName, this);
|
|
if (converterObject instanceof Function) {
|
|
Function f = (Function)converterObject;
|
|
value = f.call(Context.getContext(), f.getParentScope(),
|
|
this, ScriptRuntime.emptyArgs);
|
|
} else {
|
|
if (hint == ScriptRuntime.NumberClass
|
|
&& javaObject instanceof Boolean)
|
|
{
|
|
boolean b = ((Boolean)javaObject).booleanValue();
|
|
value = ScriptRuntime.wrapNumber(b ? 1.0 : 0.0);
|
|
} else {
|
|
value = javaObject.toString();
|
|
}
|
|
}
|
|
}
|
|
return value;
|
|
}
|
|
|
|
/**
|
|
* Determine whether we can/should convert between the given type and the
|
|
* desired one. This should be superceded by a conversion-cost calculation
|
|
* function, but for now I'll hide behind precedent.
|
|
*/
|
|
public static boolean canConvert(Object fromObj, Class<?> to) {
|
|
int weight = getConversionWeight(fromObj, to);
|
|
|
|
return (weight < CONVERSION_NONE);
|
|
}
|
|
|
|
private static final int JSTYPE_UNDEFINED = 0; // undefined type
|
|
private static final int JSTYPE_NULL = 1; // null
|
|
private static final int JSTYPE_BOOLEAN = 2; // boolean
|
|
private static final int JSTYPE_NUMBER = 3; // number
|
|
private static final int JSTYPE_STRING = 4; // string
|
|
private static final int JSTYPE_JAVA_CLASS = 5; // JavaClass
|
|
private static final int JSTYPE_JAVA_OBJECT = 6; // JavaObject
|
|
private static final int JSTYPE_JAVA_ARRAY = 7; // JavaArray
|
|
private static final int JSTYPE_OBJECT = 8; // Scriptable
|
|
|
|
static final byte CONVERSION_TRIVIAL = 1;
|
|
static final byte CONVERSION_NONTRIVIAL = 0;
|
|
static final byte CONVERSION_NONE = 99;
|
|
|
|
/**
|
|
* Derive a ranking based on how "natural" the conversion is.
|
|
* The special value CONVERSION_NONE means no conversion is possible,
|
|
* and CONVERSION_NONTRIVIAL signals that more type conformance testing
|
|
* is required.
|
|
* Based on
|
|
* <a href="http://www.mozilla.org/js/liveconnect/lc3_method_overloading.html">
|
|
* "preferred method conversions" from Live Connect 3</a>
|
|
*/
|
|
static int getConversionWeight(Object fromObj, Class<?> to) {
|
|
int fromCode = getJSTypeCode(fromObj);
|
|
|
|
switch (fromCode) {
|
|
|
|
case JSTYPE_UNDEFINED:
|
|
if (to == ScriptRuntime.StringClass ||
|
|
to == ScriptRuntime.ObjectClass) {
|
|
return 1;
|
|
}
|
|
break;
|
|
|
|
case JSTYPE_NULL:
|
|
if (!to.isPrimitive()) {
|
|
return 1;
|
|
}
|
|
break;
|
|
|
|
case JSTYPE_BOOLEAN:
|
|
// "boolean" is #1
|
|
if (to == Boolean.TYPE) {
|
|
return 1;
|
|
}
|
|
else if (to == ScriptRuntime.BooleanClass) {
|
|
return 2;
|
|
}
|
|
else if (to == ScriptRuntime.ObjectClass) {
|
|
return 3;
|
|
}
|
|
else if (to == ScriptRuntime.StringClass) {
|
|
return 4;
|
|
}
|
|
break;
|
|
|
|
case JSTYPE_NUMBER:
|
|
if (to.isPrimitive()) {
|
|
if (to == Double.TYPE) {
|
|
return 1;
|
|
}
|
|
else if (to != Boolean.TYPE) {
|
|
return 1 + getSizeRank(to);
|
|
}
|
|
}
|
|
else {
|
|
if (to == ScriptRuntime.StringClass) {
|
|
// native numbers are #1-8
|
|
return 9;
|
|
}
|
|
else if (to == ScriptRuntime.ObjectClass) {
|
|
return 10;
|
|
}
|
|
else if (ScriptRuntime.NumberClass.isAssignableFrom(to)) {
|
|
// "double" is #1
|
|
return 2;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case JSTYPE_STRING:
|
|
if (to == ScriptRuntime.StringClass) {
|
|
return 1;
|
|
}
|
|
else if (to.isInstance(fromObj)) {
|
|
return 2;
|
|
}
|
|
else if (to.isPrimitive()) {
|
|
if (to == Character.TYPE) {
|
|
return 3;
|
|
} else if (to != Boolean.TYPE) {
|
|
return 4;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case JSTYPE_JAVA_CLASS:
|
|
if (to == ScriptRuntime.ClassClass) {
|
|
return 1;
|
|
}
|
|
else if (to == ScriptRuntime.ObjectClass) {
|
|
return 3;
|
|
}
|
|
else if (to == ScriptRuntime.StringClass) {
|
|
return 4;
|
|
}
|
|
break;
|
|
|
|
case JSTYPE_JAVA_OBJECT:
|
|
case JSTYPE_JAVA_ARRAY:
|
|
Object javaObj = fromObj;
|
|
if (javaObj instanceof Wrapper) {
|
|
javaObj = ((Wrapper)javaObj).unwrap();
|
|
}
|
|
if (to.isInstance(javaObj)) {
|
|
return CONVERSION_NONTRIVIAL;
|
|
}
|
|
if (to == ScriptRuntime.StringClass) {
|
|
return 2;
|
|
}
|
|
else if (to.isPrimitive() && to != Boolean.TYPE) {
|
|
return (fromCode == JSTYPE_JAVA_ARRAY)
|
|
? CONVERSION_NONE : 2 + getSizeRank(to);
|
|
}
|
|
break;
|
|
|
|
case JSTYPE_OBJECT:
|
|
// Other objects takes #1-#3 spots
|
|
if (Scriptable.class.isAssignableFrom(to) && to.isInstance(fromObj)) {
|
|
// No conversion required
|
|
return 1;
|
|
}
|
|
if (to.isArray()) {
|
|
if (fromObj instanceof NativeArray) {
|
|
// This is a native array conversion to a java array
|
|
// Array conversions are all equal, and preferable to object
|
|
// and string conversion, per LC3.
|
|
return 1;
|
|
}
|
|
}
|
|
else if (to == ScriptRuntime.ObjectClass) {
|
|
return 2;
|
|
}
|
|
else if (to == ScriptRuntime.StringClass) {
|
|
return 3;
|
|
}
|
|
else if (to == ScriptRuntime.DateClass) {
|
|
if (fromObj instanceof NativeDate) {
|
|
// This is a native date to java date conversion
|
|
return 1;
|
|
}
|
|
}
|
|
else if (to.isInterface()) {
|
|
if (fromObj instanceof Function) {
|
|
// See comments in coerceType
|
|
if (to.getMethods().length == 1) {
|
|
return 1;
|
|
}
|
|
}
|
|
return 11;
|
|
}
|
|
else if (to.isPrimitive() && to != Boolean.TYPE) {
|
|
return 3 + getSizeRank(to);
|
|
}
|
|
break;
|
|
}
|
|
|
|
return CONVERSION_NONE;
|
|
}
|
|
|
|
static int getSizeRank(Class<?> aType) {
|
|
if (aType == Double.TYPE) {
|
|
return 1;
|
|
}
|
|
else if (aType == Float.TYPE) {
|
|
return 2;
|
|
}
|
|
else if (aType == Long.TYPE) {
|
|
return 3;
|
|
}
|
|
else if (aType == Integer.TYPE) {
|
|
return 4;
|
|
}
|
|
else if (aType == Short.TYPE) {
|
|
return 5;
|
|
}
|
|
else if (aType == Character.TYPE) {
|
|
return 6;
|
|
}
|
|
else if (aType == Byte.TYPE) {
|
|
return 7;
|
|
}
|
|
else if (aType == Boolean.TYPE) {
|
|
return CONVERSION_NONE;
|
|
}
|
|
else {
|
|
return 8;
|
|
}
|
|
}
|
|
|
|
private static int getJSTypeCode(Object value) {
|
|
if (value == null) {
|
|
return JSTYPE_NULL;
|
|
}
|
|
else if (value == Undefined.instance) {
|
|
return JSTYPE_UNDEFINED;
|
|
}
|
|
else if (value instanceof String) {
|
|
return JSTYPE_STRING;
|
|
}
|
|
else if (value instanceof Number) {
|
|
return JSTYPE_NUMBER;
|
|
}
|
|
else if (value instanceof Boolean) {
|
|
return JSTYPE_BOOLEAN;
|
|
}
|
|
else if (value instanceof Scriptable) {
|
|
if (value instanceof NativeJavaClass) {
|
|
return JSTYPE_JAVA_CLASS;
|
|
}
|
|
else if (value instanceof NativeJavaArray) {
|
|
return JSTYPE_JAVA_ARRAY;
|
|
}
|
|
else if (value instanceof Wrapper) {
|
|
return JSTYPE_JAVA_OBJECT;
|
|
}
|
|
else {
|
|
return JSTYPE_OBJECT;
|
|
}
|
|
}
|
|
else if (value instanceof Class) {
|
|
return JSTYPE_JAVA_CLASS;
|
|
}
|
|
else {
|
|
Class<?> valueClass = value.getClass();
|
|
if (valueClass.isArray()) {
|
|
return JSTYPE_JAVA_ARRAY;
|
|
}
|
|
else {
|
|
return JSTYPE_JAVA_OBJECT;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Not intended for public use. Callers should use the
|
|
* public API Context.toType.
|
|
* @deprecated as of 1.5 Release 4
|
|
* @see org.mozilla.javascript.Context#jsToJava(Object, Class)
|
|
*/
|
|
public static Object coerceType(Class<?> type, Object value)
|
|
{
|
|
return coerceTypeImpl(type, value);
|
|
}
|
|
|
|
/**
|
|
* Type-munging for field setting and method invocation.
|
|
* Conforms to LC3 specification
|
|
*/
|
|
static Object coerceTypeImpl(Class<?> type, Object value)
|
|
{
|
|
if (value != null && value.getClass() == type) {
|
|
return value;
|
|
}
|
|
|
|
switch (getJSTypeCode(value)) {
|
|
|
|
case JSTYPE_NULL:
|
|
// raise error if type.isPrimitive()
|
|
if (type.isPrimitive()) {
|
|
reportConversionError(value, type);
|
|
}
|
|
return null;
|
|
|
|
case JSTYPE_UNDEFINED:
|
|
if (type == ScriptRuntime.StringClass ||
|
|
type == ScriptRuntime.ObjectClass) {
|
|
return "undefined";
|
|
}
|
|
else {
|
|
reportConversionError("undefined", type);
|
|
}
|
|
break;
|
|
|
|
case JSTYPE_BOOLEAN:
|
|
// Under LC3, only JS Booleans can be coerced into a Boolean value
|
|
if (type == Boolean.TYPE ||
|
|
type == ScriptRuntime.BooleanClass ||
|
|
type == ScriptRuntime.ObjectClass) {
|
|
return value;
|
|
}
|
|
else if (type == ScriptRuntime.StringClass) {
|
|
return value.toString();
|
|
}
|
|
else {
|
|
reportConversionError(value, type);
|
|
}
|
|
break;
|
|
|
|
case JSTYPE_NUMBER:
|
|
if (type == ScriptRuntime.StringClass) {
|
|
return ScriptRuntime.toString(value);
|
|
}
|
|
else if (type == ScriptRuntime.ObjectClass) {
|
|
return coerceToNumber(Double.TYPE, value);
|
|
}
|
|
else if ((type.isPrimitive() && type != Boolean.TYPE) ||
|
|
ScriptRuntime.NumberClass.isAssignableFrom(type)) {
|
|
return coerceToNumber(type, value);
|
|
}
|
|
else {
|
|
reportConversionError(value, type);
|
|
}
|
|
break;
|
|
|
|
case JSTYPE_STRING:
|
|
if (type == ScriptRuntime.StringClass || type.isInstance(value)) {
|
|
return value;
|
|
}
|
|
else if (type == Character.TYPE
|
|
|| type == ScriptRuntime.CharacterClass)
|
|
{
|
|
// Special case for converting a single char string to a
|
|
// character
|
|
// Placed here because it applies *only* to JS strings,
|
|
// not other JS objects converted to strings
|
|
if (((String)value).length() == 1) {
|
|
return new Character(((String)value).charAt(0));
|
|
}
|
|
else {
|
|
return coerceToNumber(type, value);
|
|
}
|
|
}
|
|
else if ((type.isPrimitive() && type != Boolean.TYPE)
|
|
|| ScriptRuntime.NumberClass.isAssignableFrom(type))
|
|
{
|
|
return coerceToNumber(type, value);
|
|
}
|
|
else {
|
|
reportConversionError(value, type);
|
|
}
|
|
break;
|
|
|
|
case JSTYPE_JAVA_CLASS:
|
|
if (value instanceof Wrapper) {
|
|
value = ((Wrapper)value).unwrap();
|
|
}
|
|
|
|
if (type == ScriptRuntime.ClassClass ||
|
|
type == ScriptRuntime.ObjectClass) {
|
|
return value;
|
|
}
|
|
else if (type == ScriptRuntime.StringClass) {
|
|
return value.toString();
|
|
}
|
|
else {
|
|
reportConversionError(value, type);
|
|
}
|
|
break;
|
|
|
|
case JSTYPE_JAVA_OBJECT:
|
|
case JSTYPE_JAVA_ARRAY:
|
|
if (value instanceof Wrapper) {
|
|
value = ((Wrapper)value).unwrap();
|
|
}
|
|
if (type.isPrimitive()) {
|
|
if (type == Boolean.TYPE) {
|
|
reportConversionError(value, type);
|
|
}
|
|
return coerceToNumber(type, value);
|
|
}
|
|
else {
|
|
if (type == ScriptRuntime.StringClass) {
|
|
return value.toString();
|
|
}
|
|
else {
|
|
if (type.isInstance(value)) {
|
|
return value;
|
|
}
|
|
else {
|
|
reportConversionError(value, type);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case JSTYPE_OBJECT:
|
|
if (type == ScriptRuntime.StringClass) {
|
|
return ScriptRuntime.toString(value);
|
|
}
|
|
else if (type.isPrimitive()) {
|
|
if (type == Boolean.TYPE) {
|
|
reportConversionError(value, type);
|
|
}
|
|
return coerceToNumber(type, value);
|
|
}
|
|
else if (type.isInstance(value)) {
|
|
return value;
|
|
}
|
|
else if (type == ScriptRuntime.DateClass
|
|
&& value instanceof NativeDate)
|
|
{
|
|
double time = ((NativeDate)value).getJSTimeValue();
|
|
// XXX: This will replace NaN by 0
|
|
return new Date((long)time);
|
|
}
|
|
else if (type.isArray() && value instanceof NativeArray) {
|
|
// Make a new java array, and coerce the JS array components
|
|
// to the target (component) type.
|
|
NativeArray array = (NativeArray) value;
|
|
long length = array.getLength();
|
|
Class<?> arrayType = type.getComponentType();
|
|
Object Result = Array.newInstance(arrayType, (int)length);
|
|
for (int i = 0 ; i < length ; ++i) {
|
|
try {
|
|
Array.set(Result, i, coerceType(arrayType,
|
|
array.get(i, array)));
|
|
}
|
|
catch (EvaluatorException ee) {
|
|
reportConversionError(value, type);
|
|
}
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
else if (value instanceof Wrapper) {
|
|
value = ((Wrapper)value).unwrap();
|
|
if (type.isInstance(value))
|
|
return value;
|
|
reportConversionError(value, type);
|
|
}
|
|
else if (type.isInterface() && value instanceof Callable) {
|
|
// Try to use function as implementation of Java interface.
|
|
//
|
|
// XXX: Currently only instances of ScriptableObject are
|
|
// supported since the resulting interface proxies should
|
|
// be reused next time conversion is made and generic
|
|
// Callable has no storage for it. Weak references can
|
|
// address it but for now use this restriction.
|
|
if (value instanceof ScriptableObject) {
|
|
ScriptableObject so = (ScriptableObject)value;
|
|
Object key = Kit.makeHashKeyFromPair(
|
|
COERCED_INTERFACE_KEY, type);
|
|
Object old = so.getAssociatedValue(key);
|
|
if (old != null) {
|
|
// Function was already wrapped
|
|
return old;
|
|
}
|
|
Context cx = Context.getContext();
|
|
Object glue
|
|
= InterfaceAdapter.create(cx, type, (Callable)value);
|
|
// Store for later retrival
|
|
glue = so.associateValue(key, glue);
|
|
return glue;
|
|
}
|
|
reportConversionError(value, type);
|
|
} else {
|
|
reportConversionError(value, type);
|
|
}
|
|
break;
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
private static Object coerceToNumber(Class<?> type, Object value)
|
|
{
|
|
Class<?> valueClass = value.getClass();
|
|
|
|
// Character
|
|
if (type == Character.TYPE || type == ScriptRuntime.CharacterClass) {
|
|
if (valueClass == ScriptRuntime.CharacterClass) {
|
|
return value;
|
|
}
|
|
return new Character((char)toInteger(value,
|
|
ScriptRuntime.CharacterClass,
|
|
Character.MIN_VALUE,
|
|
Character.MAX_VALUE));
|
|
}
|
|
|
|
// Double, Float
|
|
if (type == ScriptRuntime.ObjectClass ||
|
|
type == ScriptRuntime.DoubleClass || type == Double.TYPE) {
|
|
return valueClass == ScriptRuntime.DoubleClass
|
|
? value
|
|
: new Double(toDouble(value));
|
|
}
|
|
|
|
if (type == ScriptRuntime.FloatClass || type == Float.TYPE) {
|
|
if (valueClass == ScriptRuntime.FloatClass) {
|
|
return value;
|
|
}
|
|
else {
|
|
double number = toDouble(value);
|
|
if (Double.isInfinite(number) || Double.isNaN(number)
|
|
|| number == 0.0) {
|
|
return new Float((float)number);
|
|
}
|
|
else {
|
|
double absNumber = Math.abs(number);
|
|
if (absNumber < Float.MIN_VALUE) {
|
|
return new Float((number > 0.0) ? +0.0 : -0.0);
|
|
}
|
|
else if (absNumber > Float.MAX_VALUE) {
|
|
return new Float((number > 0.0) ?
|
|
Float.POSITIVE_INFINITY :
|
|
Float.NEGATIVE_INFINITY);
|
|
}
|
|
else {
|
|
return new Float((float)number);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Integer, Long, Short, Byte
|
|
if (type == ScriptRuntime.IntegerClass || type == Integer.TYPE) {
|
|
if (valueClass == ScriptRuntime.IntegerClass) {
|
|
return value;
|
|
}
|
|
else {
|
|
return new Integer((int)toInteger(value,
|
|
ScriptRuntime.IntegerClass,
|
|
Integer.MIN_VALUE,
|
|
Integer.MAX_VALUE));
|
|
}
|
|
}
|
|
|
|
if (type == ScriptRuntime.LongClass || type == Long.TYPE) {
|
|
if (valueClass == ScriptRuntime.LongClass) {
|
|
return value;
|
|
} else {
|
|
/* Long values cannot be expressed exactly in doubles.
|
|
* We thus use the largest and smallest double value that
|
|
* has a value expressible as a long value. We build these
|
|
* numerical values from their hexidecimal representations
|
|
* to avoid any problems caused by attempting to parse a
|
|
* decimal representation.
|
|
*/
|
|
final double max = Double.longBitsToDouble(0x43dfffffffffffffL);
|
|
final double min = Double.longBitsToDouble(0xc3e0000000000000L);
|
|
return new Long(toInteger(value,
|
|
ScriptRuntime.LongClass,
|
|
min,
|
|
max));
|
|
}
|
|
}
|
|
|
|
if (type == ScriptRuntime.ShortClass || type == Short.TYPE) {
|
|
if (valueClass == ScriptRuntime.ShortClass) {
|
|
return value;
|
|
}
|
|
else {
|
|
return new Short((short)toInteger(value,
|
|
ScriptRuntime.ShortClass,
|
|
Short.MIN_VALUE,
|
|
Short.MAX_VALUE));
|
|
}
|
|
}
|
|
|
|
if (type == ScriptRuntime.ByteClass || type == Byte.TYPE) {
|
|
if (valueClass == ScriptRuntime.ByteClass) {
|
|
return value;
|
|
}
|
|
else {
|
|
return new Byte((byte)toInteger(value,
|
|
ScriptRuntime.ByteClass,
|
|
Byte.MIN_VALUE,
|
|
Byte.MAX_VALUE));
|
|
}
|
|
}
|
|
|
|
return new Double(toDouble(value));
|
|
}
|
|
|
|
|
|
private static double toDouble(Object value)
|
|
{
|
|
if (value instanceof Number) {
|
|
return ((Number)value).doubleValue();
|
|
}
|
|
else if (value instanceof String) {
|
|
return ScriptRuntime.toNumber((String)value);
|
|
}
|
|
else if (value instanceof Scriptable) {
|
|
if (value instanceof Wrapper) {
|
|
// XXX: optimize tail-recursion?
|
|
return toDouble(((Wrapper)value).unwrap());
|
|
}
|
|
else {
|
|
return ScriptRuntime.toNumber(value);
|
|
}
|
|
}
|
|
else {
|
|
Method meth;
|
|
try {
|
|
meth = value.getClass().getMethod("doubleValue",
|
|
(Class [])null);
|
|
}
|
|
catch (NoSuchMethodException e) {
|
|
meth = null;
|
|
}
|
|
catch (SecurityException e) {
|
|
meth = null;
|
|
}
|
|
if (meth != null) {
|
|
try {
|
|
return ((Number)meth.invoke(value,
|
|
(Object [])null)).doubleValue();
|
|
}
|
|
catch (IllegalAccessException e) {
|
|
// XXX: ignore, or error message?
|
|
reportConversionError(value, Double.TYPE);
|
|
}
|
|
catch (InvocationTargetException e) {
|
|
// XXX: ignore, or error message?
|
|
reportConversionError(value, Double.TYPE);
|
|
}
|
|
}
|
|
return ScriptRuntime.toNumber(value.toString());
|
|
}
|
|
}
|
|
|
|
private static long toInteger(Object value, Class<?> type,
|
|
double min, double max)
|
|
{
|
|
double d = toDouble(value);
|
|
|
|
if (Double.isInfinite(d) || Double.isNaN(d)) {
|
|
// Convert to string first, for more readable message
|
|
reportConversionError(ScriptRuntime.toString(value), type);
|
|
}
|
|
|
|
if (d > 0.0) {
|
|
d = Math.floor(d);
|
|
}
|
|
else {
|
|
d = Math.ceil(d);
|
|
}
|
|
|
|
if (d < min || d > max) {
|
|
// Convert to string first, for more readable message
|
|
reportConversionError(ScriptRuntime.toString(value), type);
|
|
}
|
|
return (long)d;
|
|
}
|
|
|
|
static void reportConversionError(Object value, Class<?> type)
|
|
{
|
|
// It uses String.valueOf(value), not value.toString() since
|
|
// value can be null, bug 282447.
|
|
throw Context.reportRuntimeError2(
|
|
"msg.conversion.not.allowed",
|
|
String.valueOf(value),
|
|
JavaMembers.javaSignature(type));
|
|
}
|
|
|
|
private void writeObject(ObjectOutputStream out)
|
|
throws IOException
|
|
{
|
|
out.defaultWriteObject();
|
|
|
|
out.writeBoolean(isAdapter);
|
|
if (isAdapter) {
|
|
if (adapter_writeAdapterObject == null) {
|
|
throw new IOException();
|
|
}
|
|
Object[] args = { javaObject, out };
|
|
try {
|
|
adapter_writeAdapterObject.invoke(null, args);
|
|
} catch (Exception ex) {
|
|
throw new IOException();
|
|
}
|
|
} else {
|
|
out.writeObject(javaObject);
|
|
}
|
|
|
|
if (staticType != null) {
|
|
out.writeObject(staticType.getClass().getName());
|
|
} else {
|
|
out.writeObject(null);
|
|
}
|
|
}
|
|
|
|
private void readObject(ObjectInputStream in)
|
|
throws IOException, ClassNotFoundException
|
|
{
|
|
in.defaultReadObject();
|
|
|
|
isAdapter = in.readBoolean();
|
|
if (isAdapter) {
|
|
if (adapter_readAdapterObject == null)
|
|
throw new ClassNotFoundException();
|
|
Object[] args = { this, in };
|
|
try {
|
|
javaObject = adapter_readAdapterObject.invoke(null, args);
|
|
} catch (Exception ex) {
|
|
throw new IOException();
|
|
}
|
|
} else {
|
|
javaObject = in.readObject();
|
|
}
|
|
|
|
String className = (String)in.readObject();
|
|
if (className != null) {
|
|
staticType = Class.forName(className);
|
|
} else {
|
|
staticType = null;
|
|
}
|
|
|
|
initMembers();
|
|
}
|
|
|
|
/**
|
|
* The prototype of this object.
|
|
*/
|
|
protected Scriptable prototype;
|
|
|
|
/**
|
|
* The parent scope of this object.
|
|
*/
|
|
protected Scriptable parent;
|
|
|
|
protected transient Object javaObject;
|
|
|
|
protected transient Class<?> staticType;
|
|
protected transient JavaMembers members;
|
|
private transient Map<String,FieldAndMethods> fieldAndMethods;
|
|
private transient boolean isAdapter;
|
|
|
|
private static final Object COERCED_INTERFACE_KEY = "Coerced Interface";
|
|
private static Method adapter_writeAdapterObject;
|
|
private static Method adapter_readAdapterObject;
|
|
|
|
static {
|
|
// Reflection in java is verbose
|
|
Class<?>[] sig2 = new Class[2];
|
|
Class<?> cl = Kit.classOrNull("org.mozilla.javascript.JavaAdapter");
|
|
if (cl != null) {
|
|
try {
|
|
sig2[0] = ScriptRuntime.ObjectClass;
|
|
sig2[1] = Kit.classOrNull("java.io.ObjectOutputStream");
|
|
adapter_writeAdapterObject = cl.getMethod("writeAdapterObject",
|
|
sig2);
|
|
|
|
sig2[0] = ScriptRuntime.ScriptableClass;
|
|
sig2[1] = Kit.classOrNull("java.io.ObjectInputStream");
|
|
adapter_readAdapterObject = cl.getMethod("readAdapterObject",
|
|
sig2);
|
|
|
|
} catch (Exception ex) {
|
|
adapter_writeAdapterObject = null;
|
|
adapter_readAdapterObject = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|