From 2b79b024b4f46d427b8d1adbdce36bdd8ca412a7 Mon Sep 17 00:00:00 2001 From: Manolo Carrasco Date: Mon, 10 Sep 2012 15:08:42 +0000 Subject: [PATCH] Use native JSON.stringify when available. Fix to store native js numbers in Properties instead of gwt types. Handle char and byte types --- .../google/gwt/query/client/Properties.java | 67 +++------------ .../google/gwt/query/client/js/JsCache.java | 26 ++++-- .../google/gwt/query/client/js/JsUtils.java | 86 ++++++++++++++++++- .../query/rebind/JsonBuilderGenerator.java | 11 ++- .../gwt/query/client/GQueryAjaxTestGwt.java | 7 +- .../gwt/query/client/GQueryCoreTestGwt.java | 23 ++--- 6 files changed, 141 insertions(+), 79 deletions(-) diff --git a/gwtquery-core/src/main/java/com/google/gwt/query/client/Properties.java b/gwtquery-core/src/main/java/com/google/gwt/query/client/Properties.java index 52c257f7..d2ae415e 100644 --- a/gwtquery-core/src/main/java/com/google/gwt/query/client/Properties.java +++ b/gwtquery-core/src/main/java/com/google/gwt/query/client/Properties.java @@ -15,7 +15,6 @@ */ package com.google.gwt.query.client; -import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.core.client.JsArrayMixed; import com.google.gwt.query.client.js.JsCache; @@ -133,7 +132,15 @@ public class Properties extends JavaScriptObject { c().delete(String.valueOf(name)); } - public final void set(T name, Object val) { + public final void setNumber(T name, double val) { + c().putNumber(name, val); + } + + public final void setBoolean(T name, boolean val) { + c().putBoolean(name, val); + } + + public final void set(T name, O val) { c().put(String.valueOf(name), val); } @@ -142,63 +149,11 @@ public class Properties extends JavaScriptObject { } public final String toJsonString() { - String ret = ""; - for (String k : keys()){ - String ky = k.matches("\\d+") ? k : "\"" + k + "\""; - JsCache o = getArray(k).cast(); - if (o != null) { - ret += ky + ":["; - for (int i = 0, l = o.length(); i < l ; i++) { - Properties p = o.cast().getJavaScriptObject(i); - if (p != null) { - ret += p.toJsonString() + ","; - } else { - ret += "\"" + o.getString(i) + "\","; - } - } - ret += "],"; - } else { - Properties p = getJavaScriptObject(k); - if (p != null) { - ret += ky + ":" + p.toJsonString() + ","; - } else { - ret += ky + ":\"" + getStr(k) + "\","; - } - } - } - return "{" + ret.replaceAll(",\\s*([\\]}]|$)","$1") - .replaceAll("([:,\\[])\"(-?[\\d\\.]+|null|false|true)\"", "$1$2") - + "}"; + return JsUtils.JSON2String(JsCache.checkNull(this)); } public final String toQueryString() { - String ret = ""; - for (String k : keys()) { - ret += ret.isEmpty() ? "" : "&"; - JsCache o = getArray(k).cast(); - if (o != null) { - for (int i = 0, l = o.length(); i < l ; i++) { - ret += i > 0 ? "&" : ""; - Properties p = o.cast().getJavaScriptObject(i); - if (p != null) { - ret += k + "[]=" + p.toJsonString(); - } else { - ret += k + "[]=" + o.getString(i) ; - } - } - } else { - Properties p = getJavaScriptObject(k); - if (p != null) { - ret += p.toQueryString(); - } else { - String v = getStr(k); - if (v != null && !v.isEmpty() && !"null".equalsIgnoreCase(v)) { - ret += k + "=" + v; - } - } - } - } - return ret; + return JsUtils.param(JsCache.checkNull(this)); } public final boolean isEmpty(){ diff --git a/gwtquery-core/src/main/java/com/google/gwt/query/client/js/JsCache.java b/gwtquery-core/src/main/java/com/google/gwt/query/client/js/JsCache.java index ba7242a9..9de4ebc1 100644 --- a/gwtquery-core/src/main/java/com/google/gwt/query/client/js/JsCache.java +++ b/gwtquery-core/src/main/java/com/google/gwt/query/client/js/JsCache.java @@ -114,6 +114,14 @@ public class JsCache extends JavaScriptObject { public final native int indexOf(Object o) /*-{ return this.indexOf(o); }-*/; + + public final native void putBoolean(T id, boolean b) /*-{ + this[id] = b; + }-*/; + + public final native void putNumber(T id, double n) /*-{ + this[id] = n; + }-*/; public final native void put(T id, O obj) /*-{ this[id] = obj; @@ -171,11 +179,9 @@ public class JsCache extends JavaScriptObject { return ret + "}"; } - private void checkNull() { - // In dev-mode a null object casted to JavascriptObject does not throw a NPE - if (!GWT.isProdMode() && this == null) { - throw new NullPointerException(); - } + // In dev-mode a null object casted to JavascriptObject does not throw a NPE + public final void checkNull() { + checkNull(this); } private final native JsArrayString keysImpl() /*-{ @@ -184,4 +190,14 @@ public class JsCache extends JavaScriptObject { for(key in this) if (key != '__gwt_ObjectId') keys.push(String(key)); return keys; }-*/; + + /** + * Throw a NPE when a js is null + */ + public static final T checkNull(T js) { + if (!GWT.isProdMode() && js == null) { + throw new NullPointerException(); + } + return js; + } } diff --git a/gwtquery-core/src/main/java/com/google/gwt/query/client/js/JsUtils.java b/gwtquery-core/src/main/java/com/google/gwt/query/client/js/JsUtils.java index e3ad1586..a41d15e5 100644 --- a/gwtquery-core/src/main/java/com/google/gwt/query/client/js/JsUtils.java +++ b/gwtquery-core/src/main/java/com/google/gwt/query/client/js/JsUtils.java @@ -61,6 +61,10 @@ public class JsUtils { public native Properties parseJSON(String json) /*-{ return $wnd.JSON.parse(json); }-*/; + + public native String JSON2String(JavaScriptObject o) /*-{ + return $wnd.JSON.stringify(o); + }-*/; public native Element parseXML(String xml) /*-{ return new DOMParser().parseFromString(xml, "text/xml").documentElement; @@ -77,7 +81,7 @@ public class JsUtils { Element e = a.get(i); int id = e.hashCode(); if (!cache.exists(id)) { - cache.put(id, 1); + cache.putNumber(id, 1); ret.push(e); } } @@ -100,6 +104,41 @@ public class JsUtils { // a well-formed json string. return evalImpl("(" + json + ")"); } + + @Override + public String JSON2String(JavaScriptObject js) { + // This is a very basic implementation for IE6/IE7 of JSON.stringify + // If many people demand a better one we could consider to use json2.js + // @see https://github.com/douglascrockford/JSON-js/blob/master/json2.js + Properties prop = js.cast(); + String ret = ""; + for (String k : prop.keys()){ + String ky = k.matches("\\d+") ? k : "\"" + k + "\""; + JsCache o = prop.getArray(k).cast(); + if (o != null) { + ret += ky + ":["; + for (int i = 0, l = o.length(); i < l ; i++) { + Properties p = o.cast().getJavaScriptObject(i); + if (p != null) { + ret += p.toJsonString() + ","; + } else { + ret += "\"" + o.getString(i) + "\","; + } + } + ret += "],"; + } else { + Properties p = prop.getJavaScriptObject(k); + if (p != null) { + ret += ky + ":" + p.toJsonString() + ","; + } else { + ret += ky + ":\"" + prop.getStr(k) + "\","; + } + } + } + return "{" + ret.replaceAll(",\\s*([\\]}]|$)","$1") + .replaceAll("([:,\\[])\"(-?[\\d\\.]+|null|false|true)\"", "$1$2") + + "}"; + } @Override public native Element parseXML(String xml) /*-{ @@ -275,7 +314,7 @@ public class JsUtils { ? false : !"HTML".equals(getOwnerDocument(o).getDocumentElement().getNodeName()); } - + /** * Load an external javascript library. The inserted script replaces the * element with the given id in the document. @@ -352,7 +391,46 @@ public class JsUtils { return utilsImpl.unique(a); } - public static String XML2String(JavaScriptObject o) { - return utilsImpl.XML2String(o); + public static String XML2String(JavaScriptObject js) { + return utilsImpl.XML2String(js); + } + + public static String JSON2String(JavaScriptObject js) { + return utilsImpl.JSON2String(js); + } + + /** + * Returns a QueryString representation of a JavascriptObject. + * TODO: jquery implementation accepts a second parameter (traditional) + */ + public static String param(JavaScriptObject js) { + Properties prop = js.cast(); + String ret = ""; + for (String k : prop.keys()) { + ret += ret.isEmpty() ? "" : "&"; + JsCache o = prop.getArray(k).cast(); + if (o != null) { + for (int i = 0, l = o.length(); i < l ; i++) { + ret += i > 0 ? "&" : ""; + Properties p = o.cast().getJavaScriptObject(i); + if (p != null) { + ret += k + "[]=" + p.toJsonString(); + } else { + ret += k + "[]=" + o.getString(i) ; + } + } + } else { + Properties p = prop.getJavaScriptObject(k); + if (p != null) { + ret += p.toQueryString(); + } else { + String v = prop.getStr(k); + if (v != null && !v.isEmpty() && !"null".equalsIgnoreCase(v)) { + ret += k + "=" + v; + } + } + } + } + return ret; } } diff --git a/gwtquery-core/src/main/java/com/google/gwt/query/rebind/JsonBuilderGenerator.java b/gwtquery-core/src/main/java/com/google/gwt/query/rebind/JsonBuilderGenerator.java index bbb7e80c..f61fd4e3 100644 --- a/gwtquery-core/src/main/java/com/google/gwt/query/rebind/JsonBuilderGenerator.java +++ b/gwtquery-core/src/main/java/com/google/gwt/query/rebind/JsonBuilderGenerator.java @@ -144,6 +144,10 @@ public class JsonBuilderGenerator extends Generator { sw.println("return new Date(java.lang.Long.parseLong(p.getStr(\"" + name + "\")));"); } else if (method.getReturnType().isPrimitive() != null) { sw.println("return (" + retType + ")p.getFloat(\"" + name + "\");"); + } else if (retType.equals("java.lang.Character")) { + sw.println("return (char) p.getFloat(\"" + name + "\");"); + } else if (retType.equals("java.lang.Byte")) { + sw.println("return (byte) p.getFloat(\"" + name + "\");"); } else if (retType.equals("java.lang.Integer")) { sw.println("return (int) p.getFloat(\"" + name + "\");"); } else if (retType.equals("java.lang.Float")) { @@ -154,7 +158,6 @@ public class JsonBuilderGenerator extends Generator { sw.println("return (long) p.getFloat(\"" + name + "\");"); } else if (retType.equals("java.lang.Byte")) { sw.println("return (byte) p.getFloat(\"" + name + "\");"); - } else if (isTypeAssignableTo(method.getReturnType(), stringType)) { sw.println("return p.getStr(\"" + name + "\");"); } else if (isTypeAssignableTo(method.getReturnType(), jsonBuilderType)) { @@ -219,7 +222,11 @@ public class JsonBuilderGenerator extends Generator { } sw.println("setArrayBase(\"" + name + "\", " + a + ");"); } else if (type.getParameterizedQualifiedSourceName().matches("java.util.Date")) { - sw.println("p.set(\"" + name + "\", a.getTime());"); + sw.println("p.setNumber(\"" + name + "\", a.getTime());"); + } else if (type.getParameterizedQualifiedSourceName().matches("(java.lang.(Character|Long|Double|Integer|Float|Byte)|(char|long|double|int|float|byte))")) { + sw.println("p.setNumber(\"" + name + "\", a);"); + } else if (type.getParameterizedQualifiedSourceName().matches("(java.lang.Boolean|boolean)")) { + sw.println("p.setBoolean(\"" + name + "\", a);"); } else { sw.println("p.set(\"" + name + "\", a);"); } diff --git a/gwtquery-core/src/test/java/com/google/gwt/query/client/GQueryAjaxTestGwt.java b/gwtquery-core/src/test/java/com/google/gwt/query/client/GQueryAjaxTestGwt.java index 98a85094..03eddce8 100644 --- a/gwtquery-core/src/test/java/com/google/gwt/query/client/GQueryAjaxTestGwt.java +++ b/gwtquery-core/src/test/java/com/google/gwt/query/client/GQueryAjaxTestGwt.java @@ -147,11 +147,14 @@ public class GQueryAjaxTestGwt extends GWTTestCase { public void testJsonValidService() { delayTestFinish(5000); - String testJsonpUrl = "http://services.digg.com/stories/top?appkey=http://mashup.com&type=javascript&callback=?"; + // Use a public json service + String testJsonpUrl = "https://www.googleapis.com/blogger/v2/blogs/user_id/posts/post_id?callback=?&key=NO-KEY"; Ajax.getJSONP(testJsonpUrl, new Function(){ public void f() { Properties p = getDataProperties(); - assertTrue(0 < p.getInt("count")); + // It should return error since we do not use a valid key + // {"error":{"errors":[{"domain":"usageLimits","reason":"keyInvalid","message":"Bad Request"}],"code":400,"message":"Bad Request"}} + assertEquals(400, p.getJavaScriptObject("error").cast().getInt("code")); finishTest(); } }, null, 0); diff --git a/gwtquery-core/src/test/java/com/google/gwt/query/client/GQueryCoreTestGwt.java b/gwtquery-core/src/test/java/com/google/gwt/query/client/GQueryCoreTestGwt.java index c0843612..b14533c9 100644 --- a/gwtquery-core/src/test/java/com/google/gwt/query/client/GQueryCoreTestGwt.java +++ b/gwtquery-core/src/test/java/com/google/gwt/query/client/GQueryCoreTestGwt.java @@ -19,6 +19,14 @@ import static com.google.gwt.query.client.GQuery.$; import static com.google.gwt.query.client.GQuery.$$; import static com.google.gwt.query.client.GQuery.document; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import junit.framework.Assert; + import com.google.gwt.dom.client.Document; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.InputElement; @@ -44,14 +52,6 @@ import com.google.gwt.user.client.ui.RootPanel; import com.google.gwt.user.client.ui.TextArea; import com.google.gwt.user.client.ui.Widget; -import junit.framework.Assert; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; - /** * Test class for testing gwtquery-core api. */ @@ -539,7 +539,7 @@ public class GQueryCoreTestGwt extends GWTTestCase { } - public void aatestProperties() { + public void testProperties() { Properties p = $$("border:'1px solid black'"); assertEquals(1, p.keys().length); assertNotNull(p.getStr("border")); @@ -549,7 +549,10 @@ public class GQueryCoreTestGwt extends GWTTestCase { assertNotNull(p.getStr("border")); try { - // DevMode null casting return an object + // DevMode null casting returns an object + // @see: + // http://code.google.com/p/gwtquery/issues/detail?id=122 + // http://code.google.com/p/google-web-toolkit/issues/detail?id=6625 ((Properties)null).toJsonString(); fail("Executing methods of a null object should throw a NullPointerException"); } catch (NullPointerException e) { -- 2.39.5