blob: 02e2cdd43dc5ac5b91e95c450da90947a8587b64 [file] [log] [blame]
/*
* Copyright 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.gwt.dev.shell;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.dev.util.TypeInfo;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
/**
* Glue layer that performs GWT-specific operations on JsValues. Used to isolate
* HostedModeExceptions/etc from JsValue code
*/
public final class JsValueGlue {
public static final String HOSTED_MODE_REFERENCE = "hostedModeReference";
public static final String JSO_CLASS = "com.google.gwt.core.client.JavaScriptObject";
public static final String JSO_IMPL_CLASS = "com.google.gwt.core.client.JavaScriptObject$";
/**
* Create a JavaScriptObject instance referring to this JavaScript object.
*
* @param classLoader the classLoader to create from
* @return the constructed JavaScriptObject
*/
public static Object createJavaScriptObject(JsValue value,
CompilingClassLoader classLoader) {
Throwable caught;
try {
// See if there's already a wrapper object (assures identity comparison).
Object jso = classLoader.getCachedJso(value.getJavaScriptObjectPointer());
if (jso != null) {
return jso;
}
// Instantiate the JSO class.
Class<?> jsoType = Class.forName(JSO_IMPL_CLASS, true, classLoader);
Constructor<?> ctor = jsoType.getDeclaredConstructor();
ctor.setAccessible(true);
jso = ctor.newInstance();
// Set the reference field to this JsValue using reflection.
Field referenceField = jsoType.getField(HOSTED_MODE_REFERENCE);
referenceField.set(jso, value);
classLoader.putCachedJso(value.getJavaScriptObjectPointer(), jso);
return jso;
} catch (InstantiationException e) {
caught = e;
} catch (IllegalAccessException e) {
caught = e;
} catch (SecurityException e) {
caught = e;
} catch (NoSuchMethodException e) {
caught = e;
} catch (IllegalArgumentException e) {
caught = e;
} catch (InvocationTargetException e) {
caught = e;
} catch (ClassNotFoundException e) {
caught = e;
} catch (NoSuchFieldException e) {
caught = e;
}
throw new RuntimeException("Error creating JavaScript object", caught);
}
/**
* Return an object containing the value JavaScript object as a specified
* type.
*
* @param value the JavaScript value
* @param type expected type of the returned object
* @param msgPrefix a prefix for error/warning messages
* @return the object reference
* @throws com.google.gwt.dev.shell.HostedModeException if the JavaScript
* object is not assignable to the supplied type.
*/
@SuppressWarnings("unchecked")
public static <T> T get(JsValue value, CompilingClassLoader cl,
Class<T> type, String msgPrefix) {
if (type.isPrimitive()) {
if (type == Boolean.TYPE) {
if (!value.isBoolean()) {
throw new HostedModeException(msgPrefix + ": JS value of type "
+ value.getTypeString() + ", expected boolean");
}
return (T) Boolean.valueOf(value.getBoolean());
} else if (type == Byte.TYPE) {
return (T) Byte.valueOf((byte) getIntRange(value, Byte.MIN_VALUE,
Byte.MAX_VALUE, "byte", msgPrefix));
} else if (type == Character.TYPE) {
return (T) Character.valueOf((char) getIntRange(value,
Character.MIN_VALUE, Character.MAX_VALUE, "char", msgPrefix));
} else if (type == Double.TYPE) {
if (!value.isNumber()) {
throw new HostedModeException(msgPrefix + ": JS value of type "
+ value.getTypeString() + ", expected double");
}
return (T) Double.valueOf(value.getNumber());
} else if (type == Float.TYPE) {
if (!value.isNumber()) {
throw new HostedModeException(msgPrefix + ": JS value of type "
+ value.getTypeString() + ", expected float");
}
double doubleVal = value.getNumber();
// Check for small changes near MIN_VALUE and replace with the
// actual end point value, in case it is being used as a sentinel
// value. This test works by the subtraction result rounding off to
// zero if the delta is not representable in a float.
// TODO(jat): add similar test for MAX_VALUE if we have a JS
// platform that alters the value while converting to/from strings.
if ((float) (doubleVal - Float.MIN_VALUE) == 0.0f) {
doubleVal = Float.MIN_VALUE;
}
float floatVal = (float) doubleVal;
if (Float.isInfinite(floatVal) && !Double.isInfinite(doubleVal)) {
// in this case we had overflow from the double value which was
// outside the range of supported float values, and the cast
// converted it to infinity. Since this lost data, we treat this
// as an error in hosted mode.
throw new HostedModeException(msgPrefix + ": JS value " + doubleVal
+ " out of range for a float");
}
return (T) Float.valueOf(floatVal);
} else if (type == Integer.TYPE) {
return (T) Integer.valueOf(getIntRange(value, Integer.MIN_VALUE,
Integer.MAX_VALUE, "int", msgPrefix));
} else if (type == Long.TYPE) {
if (!value.isWrappedJavaObject()) {
throw new HostedModeException(msgPrefix + ": JS value of type "
+ value.getTypeString() + ", expected Java long");
}
JavaLong javaLong = (JavaLong) value.getWrappedJavaObject();
return (T) Long.valueOf(javaLong.longValue());
} else if (type == Short.TYPE) {
return (T) Short.valueOf((short) getIntRange(value, Short.MIN_VALUE,
Short.MAX_VALUE, "short", msgPrefix));
}
}
if (value.isNull() || value.isUndefined()) {
return null;
}
if (value.isWrappedJavaObject()) {
return type.cast(value.getWrappedJavaObject());
}
if (value.isString()) {
return type.cast(value.getString());
}
if (value.isJavaScriptObject()) {
return type.cast(createJavaScriptObject(value, cl));
}
// Just don't know what do to with this.
/*
* TODO (amitmanjhi): does throwing a HostedModeException here and catching
* a RuntimeException in user test
* com.google.gwt.dev.jjs.test.HostedTest::testObjectReturns() make sense
*/
throw new IllegalArgumentException(msgPrefix + ": JS value of type "
+ value.getTypeString() + ", expected "
+ TypeInfo.getSourceRepresentation(type));
}
/**
* Set the underlying value.
*
* @param value JsValue to set
* @param type static type of the object
* @param obj the object to store in the JS value
*/
public static void set(JsValue value, CompilingClassLoader cl, Class<?> type,
Object obj) {
if (type.isPrimitive()) {
if (type == Boolean.TYPE) {
value.setBoolean(((Boolean) obj).booleanValue());
} else if (type == Byte.TYPE) {
value.setInt(((Byte) obj).byteValue());
} else if (type == Character.TYPE) {
value.setInt(((Character) obj).charValue());
} else if (type == Double.TYPE) {
value.setDouble(((Double) obj).doubleValue());
} else if (type == Float.TYPE) {
value.setDouble(((Float) obj).floatValue());
} else if (type == Integer.TYPE) {
value.setInt(((Integer) obj).intValue());
} else if (type == Long.TYPE) {
long longVal = ((Long) obj).longValue();
value.setWrappedJavaObject(cl, new JavaLong(longVal));
} else if (type == Short.TYPE) {
value.setInt(((Short) obj).shortValue());
} else if (type == Void.TYPE) {
value.setUndefined();
} else {
throw new HostedModeException("Cannot marshal primitive type " + type);
}
} else if (obj == null) {
value.setNull();
} else {
// not a boxed primitive
try {
Class<?> jsoType = Class.forName(JSO_IMPL_CLASS, false, cl);
if (jsoType == obj.getClass()) {
JsValue jsObject = getUnderlyingObject(obj);
value.setValue(jsObject);
return;
}
} catch (ClassNotFoundException e) {
// Ignore the exception, if we can't find the class then obviously we
// don't have to worry about o being one
}
// Fall through case: Object.
if (!type.isInstance(obj)) {
throw new HostedModeException("object is of type "
+ obj.getClass().getName() + ", expected " + type.getName());
}
if (obj instanceof String) {
value.setString((String) obj);
} else {
value.setWrappedJavaObject(cl, obj);
}
}
}
private static int getIntRange(JsValue value, int low, int high,
String typeName, String msgPrefix) {
int intVal;
if (value.isInt()) {
intVal = value.getInt();
if (intVal < low || intVal > high) {
throw new HostedModeException(msgPrefix + ": JS int value " + intVal
+ " out of range for a " + typeName);
}
} else if (value.isNumber()) {
double doubleVal = value.getNumber();
if (doubleVal < low || doubleVal > high) {
throw new HostedModeException(msgPrefix + ": JS double value "
+ doubleVal + " out of range for a " + typeName);
}
intVal = (int) doubleVal;
if (intVal != doubleVal) {
ModuleSpace.getLogger().log(TreeLogger.WARN,
msgPrefix + ": Rounding double (" + doubleVal + ") to int for "
+ typeName, null);
}
} else {
throw new HostedModeException(msgPrefix + ": JS value of type "
+ value.getTypeString() + ", expected " + typeName);
}
return intVal;
}
/**
* Returns the underlying JsValue from a JavaScriptObject instance.
*
* The tricky part is that it is in a different ClassLoader so therefore can't
* be specified directly. The type is specified as Object, and reflection is
* used to retrieve the reference field.
*
* @param jso the instance of JavaScriptObject to retrieve the JsValue from.
* @return the JsValue representing the JavaScript object
*/
private static JsValue getUnderlyingObject(Object jso) {
Throwable caught;
try {
Field referenceField = jso.getClass().getField(HOSTED_MODE_REFERENCE);
referenceField.setAccessible(true);
return (JsValue) referenceField.get(jso);
} catch (IllegalAccessException e) {
caught = e;
} catch (SecurityException e) {
caught = e;
} catch (NoSuchFieldException e) {
caught = e;
}
throw new RuntimeException("Error reading " + HOSTED_MODE_REFERENCE, caught);
}
private JsValueGlue() {
}
}