001: /*
002: * Copyright 2007 Google Inc.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005: * use this file except in compliance with the License. You may obtain a copy of
006: * the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013: * License for the specific language governing permissions and limitations under
014: * the License.
015: */
016: package com.google.gwt.dev.shell;
017:
018: import com.google.gwt.core.ext.TreeLogger;
019: import com.google.gwt.dev.util.TypeInfo;
020:
021: import java.lang.reflect.Constructor;
022: import java.lang.reflect.Field;
023: import java.lang.reflect.InvocationTargetException;
024:
025: /**
026: * Glue layer that performs GWT-specific operations on JsValues. Used to isolate
027: * HostedModeExceptions/etc from JsValue code
028: */
029: public class JsValueGlue {
030: public static final String JSO_CLASS = "com.google.gwt.core.client.JavaScriptObject";
031:
032: /**
033: * Create a JavaScriptObject instance referring to this JavaScript object.
034: *
035: * The caller is responsible for ensuring that the requested type is a
036: * subclass of JavaScriptObject.
037: *
038: * @param type The subclass of JavaScriptObject to create
039: * @return the constructed JavaScriptObject
040: */
041: public static <T> T createJavaScriptObject(JsValue value,
042: Class<? extends T> type) {
043: try {
044: // checkThread();
045: if (!value.isJavaScriptObject()) {
046: throw new RuntimeException(
047: "Only Object type JavaScript objects can be made into JavaScriptObject");
048: }
049:
050: /* find the JavaScriptObject type, while verifying this is a subclass */
051: Class<?> jsoType = getJavaScriptObjectSuperclass(type);
052: if (jsoType == null) {
053: throw new RuntimeException("Requested type "
054: + type.getName()
055: + " not a subclass of JavaScriptObject");
056: }
057:
058: /* create the object using the default constructor */
059: Constructor<? extends T> ctor = type
060: .getDeclaredConstructor();
061: ctor.setAccessible(true);
062: T jso = ctor.newInstance();
063:
064: /* set the hostedModeReference field to this JsValue using reflection */
065: Field referenceField = jsoType
066: .getDeclaredField("hostedModeReference");
067: referenceField.setAccessible(true);
068: referenceField.set(jso, value);
069: return jso;
070: } catch (InstantiationException e) {
071: throw new RuntimeException(
072: "Error creating JavaScript object", e);
073: } catch (IllegalAccessException e) {
074: throw new RuntimeException(
075: "Error creating JavaScript object", e);
076: } catch (SecurityException e) {
077: throw new RuntimeException(
078: "Error creating JavaScript object", e);
079: } catch (NoSuchFieldException e) {
080: throw new RuntimeException(
081: "Error creating JavaScript object", e);
082: } catch (NoSuchMethodException e) {
083: throw new RuntimeException(
084: "Error creating JavaScript object", e);
085: } catch (IllegalArgumentException e) {
086: throw new RuntimeException(
087: "Error creating JavaScript object", e);
088: } catch (InvocationTargetException e) {
089: throw new RuntimeException(
090: "Error creating JavaScript object", e);
091: }
092: }
093:
094: /**
095: * Return an object containing the value JavaScript object as a specified
096: * type.
097: *
098: * @param value the JavaScript value
099: * @param type expected type of the returned object
100: * @param msgPrefix a prefix for error/warning messages
101: * @return the object reference
102: * @throws HostedModeException if the JavaScript object is not assignable to
103: * the supplied type.
104: */
105: public static <T> T get(JsValue value, Class<? extends T> type,
106: String msgPrefix) {
107: double doubleVal;
108: if (value.isNull()) {
109: return null;
110: }
111: if (value.isUndefined()) {
112: // undefined is never legal to return from JavaScript into Java
113: throw new HostedModeException(msgPrefix
114: + ": JavaScript undefined, expected "
115: + type.getName());
116: }
117: if (value.isWrappedJavaObject()) {
118: T origObject = (T) value.getWrappedJavaObject();
119: if (!type.isAssignableFrom(origObject.getClass())) {
120: throw new HostedModeException(msgPrefix
121: + ": Java object of type "
122: + origObject.getClass().getName()
123: + ", expected " + type.getName());
124: }
125: return origObject;
126: }
127: if (getJavaScriptObjectSuperclass(type) != null) {
128: if (!value.isJavaScriptObject()) {
129: throw new HostedModeException(msgPrefix
130: + ": JS object of type "
131: + value.getTypeString() + ", expected "
132: + type.getName());
133: }
134: return createJavaScriptObject(value, type);
135: }
136: switch (TypeInfo.classifyType(type)) {
137: case TypeInfo.TYPE_WRAP_BOOLEAN:
138: case TypeInfo.TYPE_PRIM_BOOLEAN:
139: if (!value.isBoolean()) {
140: throw new HostedModeException(msgPrefix
141: + ": JS value of type " + value.getTypeString()
142: + ", expected boolean");
143: }
144: return (T) Boolean.valueOf(value.getBoolean());
145:
146: case TypeInfo.TYPE_WRAP_BYTE:
147: case TypeInfo.TYPE_PRIM_BYTE:
148: return (T) new Byte((byte) getIntRange(value,
149: Byte.MIN_VALUE, Byte.MAX_VALUE, "byte", msgPrefix));
150:
151: case TypeInfo.TYPE_WRAP_CHAR:
152: case TypeInfo.TYPE_PRIM_CHAR:
153: return (T) new Character((char) getIntRange(value,
154: Character.MIN_VALUE, Character.MAX_VALUE, "char",
155: msgPrefix));
156:
157: case TypeInfo.TYPE_WRAP_DOUBLE:
158: case TypeInfo.TYPE_PRIM_DOUBLE:
159: if (!value.isNumber()) {
160: throw new HostedModeException(msgPrefix
161: + ": JS value of type " + value.getTypeString()
162: + ", expected double");
163: }
164: return (T) new Double(value.getNumber());
165:
166: case TypeInfo.TYPE_WRAP_FLOAT:
167: case TypeInfo.TYPE_PRIM_FLOAT:
168: if (!value.isNumber()) {
169: throw new HostedModeException(msgPrefix
170: + ": JS value of type " + value.getTypeString()
171: + ", expected float");
172: }
173: doubleVal = value.getNumber();
174:
175: // Check for small changes near MIN_VALUE and replace with the
176: // actual endpoint value, in case it is being used as a sentinel
177: // value. This test works by the subtraction result rounding off to
178: // zero if the delta is not representable in a float.
179: // TODO(jat): add similar test for MAX_VALUE if we have a JS
180: // platform that munges the value while converting to/from strings.
181: if ((float) (doubleVal - Float.MIN_VALUE) == 0.0f) {
182: doubleVal = Float.MIN_VALUE;
183: }
184:
185: float floatVal = (float) doubleVal;
186: if (Float.isInfinite(floatVal)
187: && !Double.isInfinite(doubleVal)) {
188: // in this case we had overflow from the double value which was
189: // outside the range of supported float values, and the cast
190: // converted it to infinity. Since this lost data, we treat this
191: // as an error in hosted mode.
192: throw new HostedModeException(msgPrefix + ": JS value "
193: + doubleVal + " out of range for a float");
194: }
195: return (T) new Float(floatVal);
196:
197: case TypeInfo.TYPE_WRAP_INT:
198: case TypeInfo.TYPE_PRIM_INT:
199: return (T) new Integer(getIntRange(value,
200: Integer.MIN_VALUE, Integer.MAX_VALUE, "int",
201: msgPrefix));
202:
203: case TypeInfo.TYPE_WRAP_LONG:
204: case TypeInfo.TYPE_PRIM_LONG:
205: if (!value.isNumber()) {
206: throw new HostedModeException(msgPrefix
207: + ": JS value of type " + value.getTypeString()
208: + ", expected long");
209: }
210: doubleVal = value.getNumber();
211: if (doubleVal < Long.MIN_VALUE
212: || doubleVal > Long.MAX_VALUE) {
213: throw new HostedModeException(msgPrefix
214: + ": JS double value " + doubleVal
215: + " out of range for a long");
216: }
217: // TODO(jat): can this actually detect loss of precision?
218: long longVal = (long) doubleVal;
219: if (doubleVal != longVal) {
220: // TODO(jat): should this be an error or exception?
221: ModuleSpace
222: .getLogger()
223: .log(
224: TreeLogger.WARN,
225: msgPrefix
226: + ": Loss of precision converting double to long",
227: null);
228: }
229: return (T) new Long(longVal);
230:
231: case TypeInfo.TYPE_WRAP_SHORT:
232: case TypeInfo.TYPE_PRIM_SHORT:
233: return (T) new Short((short) getIntRange(value,
234: Short.MIN_VALUE, Short.MAX_VALUE, "short",
235: msgPrefix));
236:
237: case TypeInfo.TYPE_WRAP_STRING:
238: if (!value.isString()) {
239: throw new HostedModeException(msgPrefix
240: + ": JS value of type " + value.getTypeString()
241: + ", expected string");
242: }
243: return (T) value.getString();
244:
245: case TypeInfo.TYPE_USER:
246: if (value.isString()) {
247: return (T) value.getString();
248: }
249: // if it isn't a String, it's an error, break to error
250: break;
251: }
252:
253: // Just don't know what do to with this.
254: throw new IllegalArgumentException(msgPrefix
255: + ": Cannot convert to type "
256: + TypeInfo.getSourceRepresentation(type) + " from "
257: + value.getTypeString());
258: }
259:
260: /**
261: * Returns the underlying JsValue from a JavaScriptObject instance.
262: *
263: * The tricky part is that it is in a different classloader so therefore can't
264: * be specified directly. The type is specified as Object, and reflection is
265: * used to retrieve the hostedModeReference field.
266: *
267: * @param jso the instance of JavaScriptObject to retrieve the JsValue from.
268: * @return the JsValue representing the JavaScript object
269: */
270: public static JsValue getUnderlyingObject(Object jso) {
271: try {
272: /*
273: * verify that jso is assignable to
274: * com.google.gwt.core.client.JavaScriptObject
275: */
276: Class<?> type = getJavaScriptObjectSuperclass(jso
277: .getClass());
278:
279: if (type == null) {
280: throw new HostedModeException(
281: "Underlying JSO not a subclass of JavaScriptObject");
282: }
283:
284: Field referenceField = type
285: .getDeclaredField("hostedModeReference");
286: referenceField.setAccessible(true);
287: return (JsValue) referenceField.get(jso);
288: } catch (IllegalAccessException e) {
289: throw new RuntimeException("Error reading handle", e);
290: } catch (SecurityException e) {
291: throw new RuntimeException("Error reading handle", e);
292: } catch (NoSuchFieldException e) {
293: throw new RuntimeException("Error reading handle", e);
294: }
295: }
296:
297: /**
298: * Set the underlying value.
299: *
300: * @param value JsValue to set
301: * @param type static type of the object
302: * @param obj the object to store in the JS value
303: */
304: public static <T> void set(JsValue value, CompilingClassLoader cl,
305: Class<?> type, T obj) {
306: if (obj == null) {
307: value.setNull();
308: } else if (type.equals(String.class)) {
309: value.setString((String) obj);
310: } else if (type.equals(boolean.class)) {
311: value.setBoolean(((Boolean) obj).booleanValue());
312: } else if (type.equals(short.class)) {
313: value.setInt(((Short) obj).shortValue());
314: } else if (type.equals(int.class)) {
315: value.setInt(((Integer) obj).intValue());
316: } else if (type.equals(byte.class)) {
317: value.setInt(((Byte) obj).byteValue());
318: } else if (type.equals(char.class)) {
319: value.setInt(((Character) obj).charValue());
320: } else if (type.equals(long.class)) {
321: long longVal = ((Long) obj).longValue();
322: double doubleVal = longVal;
323: if ((long) doubleVal != longVal) {
324: // TODO(jat): should this be an error or exception?
325: ModuleSpace.getLogger().log(TreeLogger.WARN,
326: "Loss of precision converting long to double",
327: null);
328: }
329: value.setDouble(doubleVal);
330: } else if (type.equals(float.class)) {
331: value.setDouble(((Float) obj).floatValue());
332: } else if (type.equals(double.class)) {
333: value.setDouble(((Double) obj).doubleValue());
334: } else {
335: // not a boxed primitive
336: try {
337: Class<?> jso = Class.forName(JSO_CLASS, true, cl);
338: if (jso.isAssignableFrom(type)
339: && jso.isAssignableFrom(obj.getClass())) {
340: JsValue jsObject = getUnderlyingObject(obj);
341: value.setValue(jsObject);
342: return;
343: }
344: } catch (ClassNotFoundException e) {
345: // Ignore the exception, if we can't find the class then obviously we
346: // don't have to worry about o being one
347: }
348:
349: // Fallthrough case: Object.
350: if (!type.isAssignableFrom(obj.getClass())) {
351: throw new HostedModeException("object is of type "
352: + obj.getClass().getName() + ", expected "
353: + type.getName());
354: }
355: value.setWrappedJavaObject(cl, obj);
356: }
357: }
358:
359: private static int getIntRange(JsValue value, int low, int high,
360: String typeName, String msgPrefix) {
361: int intVal;
362: if (value.isInt()) {
363: intVal = value.getInt();
364: if (intVal < low || intVal > high) {
365: throw new HostedModeException(msgPrefix
366: + ": JS int value " + intVal
367: + " out of range for a " + typeName);
368: }
369: } else if (value.isNumber()) {
370: double doubleVal = value.getNumber();
371: if (doubleVal < low || doubleVal > high) {
372: throw new HostedModeException(msgPrefix
373: + ": JS double value " + doubleVal
374: + " out of range for a " + typeName);
375: }
376: intVal = (int) doubleVal;
377: if (intVal != doubleVal) {
378: ModuleSpace.getLogger().log(
379: TreeLogger.WARN,
380: msgPrefix + ": Rounding double to int for "
381: + typeName, null);
382: }
383: } else {
384: throw new HostedModeException(msgPrefix
385: + ": JS value of type " + value.getTypeString()
386: + ", expected " + typeName);
387: }
388: return intVal;
389: }
390:
391: /**
392: * Verify that the supplied class is a subclass of
393: * com.google.gwt.core.client.JavaScriptObject, and return the
394: * JavaScriptObject class if it is. This is required since JavaScriptObject
395: * actually lives in a different classloader and can't be referenced directly.
396: *
397: * @param type class to test
398: * @return the JavaScriptObject class object if it is a subclass, or null if
399: * not.
400: */
401: private static Class<?> getJavaScriptObjectSuperclass(Class<?> type) {
402: while (type != null && !type.getName().equals(JSO_CLASS)) {
403: type = type.getSuperclass();
404: }
405: return type;
406: }
407: }
|