com.google.gwt.dev.shell.designtime.JsValueGlue.java Source code

Java tutorial

Introduction

Here is the source code for com.google.gwt.dev.shell.designtime.JsValueGlue.java

Source

/*******************************************************************************
 * Copyright 2011 Google Inc. All Rights Reserved.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * 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.designtime;

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, ClassLoader classLoader) {
        Throwable caught;
        try {
            // See if there's already a wrapper object (assures identity comparison).
            Object jso = WrappersCache.getCachedJso(classLoader, 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);
            WrappersCache.putCachedJso(classLoader, 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, ClassLoader cl, Class<T> type, String msgPrefix) {
        if (type.isPrimitive()) {
            if (type == Boolean.TYPE) {
                if (!value.isBoolean()) {
                    throw new RuntimeException(
                            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 RuntimeException(
                            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 RuntimeException(
                            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 RuntimeException(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 RuntimeException(
                            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 "
                + 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, ClassLoader cl, DispatchIdOracle dispIdOracle, 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, dispIdOracle, new JavaLong(longVal));
            } else if (type == Short.TYPE) {
                value.setInt(((Short) obj).shortValue());
            } else if (type == Void.TYPE) {
                value.setUndefined();
            } else {
                throw new RuntimeException("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 RuntimeException(
                        "object is of type " + obj.getClass().getName() + ", expected " + type.getName());
            }
            if (obj instanceof String) {
                value.setString((String) obj);
            } else {
                value.setWrappedJavaObject(cl, dispIdOracle, 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 RuntimeException(
                        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 RuntimeException(
                        msgPrefix + ": JS double value " + doubleVal + " out of range for a " + typeName);
            }
            intVal = (int) doubleVal;
            if (intVal != doubleVal) {
                /* FIXME: ModuleSpace.getLogger().log(
                  TreeLogger.WARN,
                  msgPrefix + ": Rounding double (" + doubleVal + ") to int for " + typeName,
                  null);*/
            }
        } else {
            throw new RuntimeException(
                    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() {
    }

    private static String getSourceRepresentation(Class<?> type) {
        // Primitives
        //
        if (type.equals(Integer.TYPE)) {
            return "int";
        } else if (type.equals(Long.TYPE)) {
            return "long";
        } else if (type.equals(Short.TYPE)) {
            return "short";
        } else if (type.equals(Byte.TYPE)) {
            return "byte";
        } else if (type.equals(Character.TYPE)) {
            return "char";
        } else if (type.equals(Boolean.TYPE)) {
            return "boolean";
        } else if (type.equals(Float.TYPE)) {
            return "float";
        } else if (type.equals(Double.TYPE)) {
            return "double";
        }
        // Arrays
        //
        if (type.isArray()) {
            Class<?> componentType = type.getComponentType();
            return getSourceRepresentation(componentType) + "[]";
        }
        // Everything else
        //
        return type.getName().replace('$', '.');
    }
}