package org.timepedia.exporter.client; import java.util.Date; import java.util.HashMap; import java.util.IdentityHashMap; import java.util.Map; import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.core.client.JsArray; import com.google.gwt.core.client.JsArrayNumber; import com.google.gwt.core.client.JsArrayString; /** * Methods used to maintain a mapping between JS types and Java (GWT) objects. */ public class ExporterBaseActual extends ExporterBaseImpl { public static final String WRAPPER_PROPERTY = "__gwtex_wrap"; private native static JavaScriptObject wrap0(Object type, JavaScriptObject constructor) /*-{ return constructor && ((typeof constructor == 'function')) ? new (constructor)(type) : type; }-*/; private HashMap typeMap = new HashMap(); private HashMap<Class, JavaScriptObject> dispatchMap = new HashMap<Class, JavaScriptObject>(); private HashMap<Class, JavaScriptObject> staticDispatchMap = new HashMap<Class, JavaScriptObject>(); //TODO: track garbage collected wrappers and remove mapping private IdentityHashMap<Object, JavaScriptObject> wrapperMap = null; public ExporterBaseActual() { if (!GWT.isScript()) { wrapperMap = new IdentityHashMap<Object, JavaScriptObject>(); } } public void addTypeMap(Exportable type, JavaScriptObject exportedConstructor) { addTypeMap(type.getClass(), exportedConstructor); } public void addTypeMap(Class type, JavaScriptObject exportedConstructor) { typeMap.put(type, exportedConstructor); } public JavaScriptObject typeConstructor(Object type) { return typeConstructor(type.getClass()); } public JavaScriptObject typeConstructor(Class type) { Object o = typeMap.get(type); Class sup = type.getSuperclass(); if (o == null && sup != null && sup != Object.class) { return typeConstructor(sup); } return (JavaScriptObject) o; } private JavaScriptObject getWrapper(Object type) { JavaScriptObject wrapper = null; if (!GWT.isScript()) { wrapper = wrapperMap.get(type); } else { wrapper = getWrapperJS(type, WRAPPER_PROPERTY); } if (wrapper == null) { wrapper = setWrapper(type); } return wrapper; } private static native JavaScriptObject reinterpretCast(Object nl) /*-{ return nl; }-*/; private static native <T> T[] reinterpretArray(Object nl) /*-{ return nl; }-*/; @Override public JavaScriptObject wrap(Date[] type) { JsArray<JavaScriptObject> ret = JavaScriptObject.createArray().cast(); for (Date d : type) { ret.push(dateToJsDate(d)); } return ret; } @Override public JavaScriptObject wrap(float[] type) { if (!GWT.isScript()) { if (type == null) { return null; } JsArrayNumber wrapperArray = JavaScriptObject.createArray().cast(); for (int i = 0; i < type.length; i++) { wrapperArray.set(i, type[i]); } return wrapperArray; } else { return reinterpretCast(type); } } @Override public JavaScriptObject wrap(byte[] type) { if (!GWT.isScript()) { if (type == null) { return null; } JsArrayNumber wrapperArray = JavaScriptObject.createArray().cast(); for (int i = 0; i < type.length; i++) { wrapperArray.set(i, type[i]); } return wrapperArray; } else { return reinterpretCast(type); } } @Override public JavaScriptObject wrap(char[] type) { if (!GWT.isScript()) { if (type == null) { return null; } JsArrayNumber wrapperArray = JavaScriptObject.createArray().cast(); for (int i = 0; i < type.length; i++) { wrapperArray.set(i, type[i]); } return wrapperArray; } else { return reinterpretCast(type); } } @Override public JavaScriptObject wrap(int[] type) { if (!GWT.isScript()) { if (type == null) { return null; } JsArrayNumber wrapperArray = JavaScriptObject.createArray().cast(); for (int i = 0; i < type.length; i++) { wrapperArray.set(i, type[i]); } return wrapperArray; } else { return reinterpretCast(type); } } @Override public JavaScriptObject wrap(long[] type) { if (type == null) { return null; } JsArrayNumber wrapperArray = JavaScriptObject.createArray().cast(); for (int i = 0; i < type.length; i++) { wrapperArray.set(i, type[i]); } return wrapperArray; } @Override public JavaScriptObject wrap(short[] type) { if (!GWT.isScript()) { if (type == null) { return null; } JsArrayNumber wrapperArray = JavaScriptObject.createArray().cast(); for (int i = 0; i < type.length; i++) { wrapperArray.set(i, type[i]); } return wrapperArray; } else { return reinterpretCast(type); } } @Override public JavaScriptObject wrap(double[] type) { if (!GWT.isScript()) { if (type == null) { return null; } JsArrayNumber wrapperArray = JavaScriptObject.createArray().cast(); for (int i = 0; i < type.length; i++) { wrapperArray.set(i, type[i]); } return wrapperArray; } else { return reinterpretCast(type); } } @Override public JavaScriptObject wrap(String[] type) { if (!GWT.isScript()) { if (type == null) { return null; } JsArrayString wrapperArray = JavaScriptObject.createArray().cast(); for (int i = 0; i < type.length; i++) { wrapperArray.set(i, type[i]); } return wrapperArray; } else { return reinterpretCast(type); } } @Override public JavaScriptObject wrap(Object type) { if (type == null) { return null; } return getWrapper(type); } @Override public JavaScriptObject wrap(Exportable[] type) { if (type == null) { return null; } JsArrayObject wrapperArray = JavaScriptObject.createArray().cast(); for (int i = 0; i < type.length; i++) { wrapperArray.setObject(i, wrap(type[i])); } return wrapperArray; } public JavaScriptObject setWrapper(Object type) { if (type.getClass().isArray()) { return JavaScriptObject.createArray(); } JavaScriptObject cons = typeConstructor(type); assert cons != null : "No constructor for type: " + type.getClass().getName() + " " + type.getClass().getSuperclass(); JavaScriptObject wrapper = wrap0(type, cons); setWrapper(type, wrapper); return wrapper; } @Override public void setWrapper(Object instance, JavaScriptObject wrapper) { if (GWT.isScript()) { setWrapperJS(instance, wrapper, WRAPPER_PROPERTY); } else { setWrapperHosted(instance, wrapper); } } public JavaScriptObject getWrapper(Exportable type) { JavaScriptObject wrapper = null; if (GWT.isScript()) { wrapper = getWrapperJS(type, WRAPPER_PROPERTY); } else { wrapper = wrapperMap.get(type); } return wrapper; } @Override public JavaScriptObject wrap(JavaScriptObject[] type) { if (type == null) { return null; } JsArray<JavaScriptObject> wrapperArray = JavaScriptObject.createArray().cast(); for (int i = 0; i < type.length; i++) { wrapperArray.set(i, type[i]); } return wrapperArray; } @Override public Object gwtInstance(Object o) { Object g; return (o != null && o instanceof JavaScriptObject && (g = getGwtInstance((JavaScriptObject)o)) != null) ? g : o; } // JsArray.get() returns a JavaScriptObject, so we need this wrapper // class to avoid a casting exception at runtime. public static class JsArrayObject extends JavaScriptObject { protected JsArrayObject(){} final public native <T> T getObject(int i) /*-{ return this[i]; }-*/; final public native <T> void setObject(int i, T o) /*-{ this[i] = o; }-*/; final public native int length() /*-{ return this.length; }-*/; final public Double getNumberObject(int i) { return getPrimitiveNumber(i); }; final public Boolean getBooleanObject(int i) { return getPrimitiveBoolean(i); }; final public native double getPrimitiveNumber(int i) /*-{ return this[i]; }-*/; final public native boolean getPrimitiveBoolean(int i) /*-{ return this[i]; }-*/; } @SuppressWarnings("unchecked") @Override public <T> T[] toArrObject(JavaScriptObject j, T[] ret) { // We can not use here reinterpretArray because we have to replace // the gwtInstance JsArrayObject s = j.cast(); int l = s.length(); for (int i = 0; i < l; i++) { Object o = s.getObject(i); if (o instanceof JavaScriptObject && getGwtInstance((JavaScriptObject)o) != null) { o = getGwtInstance((JavaScriptObject)o); } ret[i] = (T)o; } return ret; } @Override public JavaScriptObject[] toArrJsObject(JavaScriptObject p) { if (!GWT.isScript()) { JsArray<JavaScriptObject> s = p.cast(); int l = s.length(); JavaScriptObject[] ret = new JavaScriptObject[l]; for (int i = 0; i < l; i++) { ret[i] = s.get(i); } return ret; } else { return reinterpretArray(p); } } public Exportable[] toArrExport(JavaScriptObject j) { JsArray<JavaScriptObject> s = j.cast(); int l = s.length(); Exportable[] ret = new Exportable[l]; for (int i = 0; i < l; i++) { Object o = getGwtInstance(s.get(i)); if (o == null) { o = s.get(i); } assert (o != null && (o instanceof Exportable)); ret[i] = (Exportable) o; } return ret; } public String[] toArrString(JsArrayString s) { int l = s.length(); String[] ret = new String[l]; for (int i = 0; i < l; i++) { ret[i] = s.get(i); } return ret; } public long[] toArrLong(JsArrayNumber s) { int l = s.length(); long[] ret = new long[l]; for (int i = 0; i < l; i++) { ret[i] = (long)s.get(i); } return ret; } public double[] toArrDouble(JsArrayNumber s) { int l = s.length(); double[] ret = new double[l]; for (int i = 0; i < l; i++) { ret[i] = s.get(i); } return ret; } public int[] toArrInt(JsArrayNumber s) { int l = s.length(); int[] ret = new int[l]; for (int i = 0; i < l; i++) { ret[i] = (int)s.get(i); } return ret; } public byte[] toArrByte(JsArrayNumber s) { int l = s.length(); byte[] ret = new byte[l]; for (int i = 0; i < l; i++) { ret[i] = (byte)s.get(i); } return ret; } public char[] toArrChar(JsArrayNumber s) { int l = s.length(); char[] ret = new char[l]; for (int i = 0; i < l; i++) { ret[i] = (char)s.get(i); } return ret; } public float[] toArrFloat(JsArrayNumber s) { int l = s.length(); float[] ret = new float[l]; for (int i = 0; i < l; i++) { ret[i] = (long)s.get(i); } return ret; } public Date[] toArrDate(JavaScriptObject j) { JsArray<JavaScriptObject> s = j.cast(); int l = s.length(); Date[] ret = new Date[l]; for (int i = 0; i < l; i++) { ret[i] = jsDateToDate(s.get(i)); } return ret; } private native JsArray<JavaScriptObject> computeVarArguments(int len, JavaScriptObject args) /*-{ var ret = []; for (i = 0; i < len - 1; i++) ret.push(args[i]); var alen = args.length; var p = len - 1; if (alen >= len && Object.prototype.toString.apply(args[p]) === '[object Array]') { ret.push(args[p]); } else { var a = []; for (i = p; i < alen; i++) a.push(args[i]); ret.push(a); } return ret; }-*/; @Override public native JavaScriptObject unshift(Object o, JavaScriptObject arr) /*-{ var ret = [o]; for (i = 0; i<arr.length; i++) ret.push(arr[i]); return ret; }-*/; @Override public JavaScriptObject dateToJsDate(Date d) { return numberToJsDateObject(d.getTime()); } @Override public Date jsDateToDate(JavaScriptObject jd) { return new Date((long)jsDateObjectToNumber(jd)); } private native JavaScriptObject getWrapperJS(Object type, String wrapProp) /*-{ return type[wrapProp]; }-*/; private void setWrapperHosted(Object instance, JavaScriptObject wrapper) { wrapperMap.put(instance, wrapper); } private native void setWrapperJS(Object instance, JavaScriptObject wrapper, String wrapperProperty) /*-{ instance[wrapperProperty] = wrapper; }-*/; private native void declarePackage0(JavaScriptObject prefix, String pkg) /*-{ prefix[pkg] || (prefix[pkg] = {}); }-*/; @Override public JavaScriptObject declarePackage(String qualifiedExportName) { String superPackages[] = qualifiedExportName.split("\\."); JavaScriptObject prefix = getWindow(); int i = 0; for (int l = superPackages.length - 1; i < l ; i++) { if (!superPackages[i].equals("client")) { declarePackage0(prefix, superPackages[i]); prefix = getProp(prefix, superPackages[i]); } } // return the previous object stored in this name-space if any. JavaScriptObject o = getProp(prefix, superPackages[i]); return o; } private static native JavaScriptObject getWindow() /*-{ return $wnd; }-*/; private static native JavaScriptObject getProp(JavaScriptObject jso, String key) /*-{ return jso != null ? jso[key] : null; }-*/; /** * Used for debuging */ private static native String dumpArguments(JavaScriptObject o) /*-{ function dumpObj(obj, name, indent, depth) { if (depth > 4) return name + " 4 < depth=" + depth; if (typeof obj == "object") { var child = null; var output = indent + name + "\n"; indent += " "; for (var item in obj) { try { child = obj[item]; } catch (e) { child = "<Unable to Evaluate>"; } if (typeof child == "object") { output += dumpObj(child, item, indent, depth + 1); } else { output += indent + item + ": " + child + "\n"; } } return output; } else { return "" + obj; } } return dumpObj(o, 'arguments' , '', 1); }-*/; private JavaScriptObject runDispatch(Object instance, Map<Class, JavaScriptObject> dmap, Class clazz, int meth, JsArray<JavaScriptObject> arguments) { JsArray<SignatureJSO> sigs = getSigs(dmap.get(clazz).cast(), meth, arguments.length()); JavaScriptObject jFunc = null; JavaScriptObject wFunc = null; JavaScriptObject aFunc = null; for (int i = 0, l = sigs == null ? 0 : sigs.length(); i < l; i++) { SignatureJSO sig = sigs.get(i); if (sig.matches(arguments)) { jFunc = sig.getFunction(); wFunc = sig.getWrapperFunc(); aFunc = sig.getWrapArgumentsFunc(); break; } } if (jFunc == null) { return null; } else { arguments = aFunc != null ? wrapArguments(instance, aFunc, arguments) : arguments; JavaScriptObject r = runDispatch(instance, jFunc, wFunc, arguments); return r; } } private static native JsArray<JavaScriptObject> wrapArguments(Object instance, JavaScriptObject wrapper, JavaScriptObject arguments) /*-{ return wrapper(instance, arguments); }-*/; private static native JsArray<JavaScriptObject> runDispatch(Object instance, JavaScriptObject java, JavaScriptObject wrapper, JavaScriptObject arguments) /*-{ var x = java.apply(instance, arguments); return [wrapper ? wrapper(x) : x]; }-*/; @Override public JavaScriptObject runDispatch(Object instance, Class clazz, int meth, JsArray<JavaScriptObject> arguments, boolean isStatic, boolean isVarArgs) { Map<Class, JavaScriptObject> dmap = isStatic ? staticDispatchMap : dispatchMap; if (isVarArgs) { for (int l = getMaxArity(dmap.get(clazz).cast(), meth), i = l; i >= 1; i--) { JsArray<JavaScriptObject> args = computeVarArguments(i, arguments); JavaScriptObject ret = runDispatch(instance, dmap, clazz, meth, args); if (ret == null) { // ExportInstanceMethod case args = unshift(instance, args).cast(); ret = runDispatch(instance, dmap, clazz, meth, args); } if (ret != null) { return ret; } } } else { JavaScriptObject ret = runDispatch(instance, dmap, clazz, meth, arguments); if (ret == null) { // ExportInstanceMethod case arguments = unshift(instance, arguments).cast(); ret = runDispatch(instance, dmap, clazz, meth, arguments); } if (ret != null) { return ret; } } String s = ""; //dumpArguments(arguments); throw new RuntimeException( "Can't find exported method for given arguments: " + meth + ":" + arguments.length() + "\n" + s); } public native static Object getTypeAssignableToInstance(JavaScriptObject a) /*-{ return a && a[0] && ((typeof a[0]) == 'object' || (typeof a[0]) == 'function') ? a[0] : null; }-*/; @Override @SuppressWarnings("rawtypes") public boolean isAssignableToInstance(Class clazz, JavaScriptObject args) { Object o = getTypeAssignableToInstance(args); return isAssignableToClass(o, clazz); } @SuppressWarnings("rawtypes") private static boolean isAssignableToClass(Object o, Class clazz) { if (Object.class.equals(clazz)) { return true; } if (Exportable.class.equals(clazz) && o instanceof Exportable) { return true; } if (o != null) { for (Class sup = o.getClass(); sup != null && sup != Object.class; sup = sup.getSuperclass()) { if (sup == clazz) { return true; } } } return false; } private native JsArray<SignatureJSO> getSigs(JavaScriptObject jsoMap, int meth, int arity) /*-{ return jsoMap[meth][arity]; }-*/; private native int getMaxArity(JavaScriptObject jsoMap, int meth) /*-{ var o = jsoMap[meth]; var r = 0; for (k in o) r = Math.max(r, k); return r; }-*/; private static native JavaScriptObject numberToJsDateObject(double time) /*-{ return new Date(time); }-*/; private static native double jsDateObjectToNumber(JavaScriptObject d) /*-{ return (d && d.getTime) ? d.getTime(): 0; }-*/; private static native <T> void putObject(JavaScriptObject o, int index, T val) /*-{ o[index] = val; }-*/; private static native double getNumber(JavaScriptObject o, int index) /*-{ return o[index]; }-*/; @Override public void registerDispatchMap(Class clazz, JavaScriptObject dispMap, boolean isStatic) { HashMap<Class, JavaScriptObject> map = isStatic ? staticDispatchMap : dispatchMap; JavaScriptObject jso = map.get(clazz); if (jso == null) { jso = dispMap; } else { mergeJso(jso, dispMap); } map.put(clazz, jso); } private final static native Object getGwtInstance(JavaScriptObject o) /*-{ // g must match ClassExporter.GWT_INSTANCE return o && o.g ? o.g : null; }-*/; private static native void mergeJso(JavaScriptObject o1, JavaScriptObject o2) /*-{ for(p in o2) {o1[p] = o2[p];} }-*/; final public static class SignatureJSO extends JavaScriptObject { protected SignatureJSO() { } public boolean matches(JsArray<JavaScriptObject> arguments) { // add argument matching logic // add structural type checks for (int i = 0, l = arguments.length(); i < l; i++) { // The signature saved in the Dispatch table Object jsType = getJsTypeObject(i + 3); // The js type of the argument passed (number, boolean, string, object, array) String argJsType = typeof(arguments, i); // number, boolean, string and arrays if (argJsType.equals(jsType)){ continue; } // accept nulls for strings if ("string".equals(jsType) && "null".equals(argJsType)) { continue; } boolean isNumber = "number".equals(argJsType); boolean isBoolean = "boolean".equals(argJsType); // when the signature is Object anything is valid, but we // have to replace primitives types by the appropriate object if (Object.class.equals(jsType)) { if (isNumber) { putObject(arguments, i, arguments.<JsArrayObject>cast().getNumberObject(i)); } if (isBoolean) { putObject(arguments, i, arguments.<JsArrayObject>cast().getBooleanObject(i)); } continue; } boolean isPrimitive = isNumber || isBoolean; boolean isClass = !isPrimitive && jsType != null && jsType.getClass().equals(Class.class); // Deal with complex objects if (isClass) { Object o = arguments.<JsArrayObject>cast().getObject(i); if (o == null || isAssignableToClass(o, (Class)jsType)){ continue; } if (o instanceof JavaScriptObject) { Object gwt = getGwtInstance((JavaScriptObject)o); if (gwt != null) { if (isAssignableToClass(gwt, (Class)jsType)){ putObject(arguments, i, gwt); continue; } } } } if ("object".equals(jsType) && !isNumber && !isBoolean) { continue; } return false; } return true; } public native static String typeof(JavaScriptObject args, int i) /*-{ var o = args[i]; var t = o == null ? 'null' : typeof(o); if (t == 'object') { return Object.prototype.toString.call(o) == '[object Array]' || typeof o.length == 'number' ? 'array' : t; } return t }-*/; public native Object getJsTypeObject(int i) /*-{ return this[i]; }-*/; public native JavaScriptObject getFunction() /*-{ return this[0]; }-*/; public native JavaScriptObject getWrapperFunc() /*-{ return this[1]; }-*/; public native JavaScriptObject getWrapArgumentsFunc() /*-{ return this[2]; }-*/; } }