ko4j registers implementation of Transfer and WSTransfer based on XHR and WebSocket from a browser. The Java implementations of these interfaces has been moved to ko-ws-tyrus module. JavaScript arrays passed as parameters to Java callback methods need to be wrapped.
1.1 --- a/boot-fx/src/main/java/org/netbeans/html/boot/fx/AbstractFXPresenter.java Thu Jan 09 19:12:55 2014 +0100
1.2 +++ b/boot-fx/src/main/java/org/netbeans/html/boot/fx/AbstractFXPresenter.java Thu Jan 09 20:39:23 2014 +0100
1.3 @@ -61,7 +61,7 @@
1.4 * @author Jaroslav Tulach <jaroslav.tulach@apidesign.org>
1.5 */
1.6 public abstract class AbstractFXPresenter
1.7 -implements Fn.Presenter, Fn.ToJavaScript, Executor {
1.8 +implements Fn.Presenter, Fn.ToJavaScript, Fn.FromJavaScript, Executor {
1.9 static final Logger LOG = Logger.getLogger(FXPresenter.class.getName());
1.10 protected static int cnt;
1.11 protected List<String> scripts;
1.12 @@ -220,6 +220,11 @@
1.13 }
1.14
1.15 @Override
1.16 + public Object toJava(Object jsArray) {
1.17 + return checkArray(jsArray);
1.18 + }
1.19 +
1.20 + @Override
1.21 public Object toJavaScript(Object toReturn) {
1.22 if (toReturn instanceof Object[]) {
1.23 return convertArrays((Object[])toReturn);
2.1 --- a/boot/src/main/java/org/apidesign/html/boot/spi/Fn.java Thu Jan 09 19:12:55 2014 +0100
2.2 +++ b/boot/src/main/java/org/apidesign/html/boot/spi/Fn.java Thu Jan 09 20:39:23 2014 +0100
2.3 @@ -265,4 +265,24 @@
2.4 */
2.5 public Object toJavaScript(Object toReturn);
2.6 }
2.7 +
2.8 + /** Additional interface to be implemented by {@link Presenter}s that
2.9 + * need to convert JavaScript object (usually array) to Java object
2.10 + * when calling back from JavaScript to Java.
2.11 + * <p>
2.12 + * <em>Note:</em> The implementation based on <em>JavaFX</em>
2.13 + * <code>WebView</code> uses this interface to convert JavaScript arrays to
2.14 + * Java ones.
2.15 + *
2.16 + * @since 0.7
2.17 + */
2.18 + public interface FromJavaScript {
2.19 + /** Convert a JavaScript object into suitable Java representation
2.20 + * before a Java method is called with this object as an argument.
2.21 + *
2.22 + * @param js the JavaScript object
2.23 + * @return replacement object for
2.24 + */
2.25 + public Object toJava(Object js);
2.26 + }
2.27 }
3.1 --- a/boot/src/main/java/org/netbeans/html/boot/impl/JavaScriptProcesor.java Thu Jan 09 19:12:55 2014 +0100
3.2 +++ b/boot/src/main/java/org/netbeans/html/boot/impl/JavaScriptProcesor.java Thu Jan 09 20:39:23 2014 +0100
3.3 @@ -298,13 +298,26 @@
3.4 }
3.5
3.6 int cnt = 0;
3.7 + StringBuilder convert = new StringBuilder();
3.8 for (VariableElement ve : m.getParameters()) {
3.9 source.append(sep);
3.10 - source.append(ve.asType());
3.11 - source.append(" arg").append(++cnt);
3.12 + ++cnt;
3.13 + final TypeMirror t = ve.asType();
3.14 + if (!t.getKind().isPrimitive()) {
3.15 + source.append("Object");
3.16 + convert.append(" if (p instanceof org.apidesign.html.boot.spi.Fn.FromJavaScript) {\n");
3.17 + convert.append(" arg").append(cnt).
3.18 + append(" = ((org.apidesign.html.boot.spi.Fn.FromJavaScript)p).toJava(arg").append(cnt).
3.19 + append(");\n");
3.20 + convert.append(" }\n");
3.21 + } else {
3.22 + source.append(t);
3.23 + }
3.24 + source.append(" arg").append(cnt);
3.25 sep = ", ";
3.26 }
3.27 source.append(") throws Throwable {\n");
3.28 + source.append(convert);
3.29 if (processingEnv.getSourceVersion().compareTo(SourceVersion.RELEASE_7) >= 0) {
3.30 source.append(" try (java.io.Closeable a = org.apidesign.html.boot.spi.Fn.activate(p)) { \n");
3.31 } else {
3.32 @@ -326,7 +339,8 @@
3.33 sep = "";
3.34 for (VariableElement ve : m.getParameters()) {
3.35 source.append(sep);
3.36 - source.append("arg").append(++cnt);
3.37 + source.append("(").append(ve.asType());
3.38 + source.append(")arg").append(++cnt);
3.39 sep = ", ";
3.40 }
3.41 source.append(");\n");
4.1 --- a/boot/src/main/java/org/netbeans/html/boot/impl/JsClassLoader.java Thu Jan 09 19:12:55 2014 +0100
4.2 +++ b/boot/src/main/java/org/netbeans/html/boot/impl/JsClassLoader.java Thu Jan 09 20:39:23 2014 +0100
4.3 @@ -88,6 +88,9 @@
4.4 if (name.equals(Fn.ToJavaScript.class.getName())) {
4.5 return Fn.ToJavaScript.class;
4.6 }
4.7 + if (name.equals(Fn.FromJavaScript.class.getName())) {
4.8 + return Fn.FromJavaScript.class;
4.9 + }
4.10 if (name.equals(FnUtils.class.getName())) {
4.11 return FnUtils.class;
4.12 }
5.1 --- a/json-tck/src/main/java/net/java/html/js/tests/Bodies.java Thu Jan 09 19:12:55 2014 +0100
5.2 +++ b/json-tck/src/main/java/net/java/html/js/tests/Bodies.java Thu Jan 09 20:39:23 2014 +0100
5.3 @@ -99,4 +99,9 @@
5.4
5.5 @JavaScriptBody(args = {}, body = "return true;")
5.6 public static native boolean truth();
5.7 +
5.8 + @JavaScriptBody(args = { "s" }, javacall = true, body =
5.9 + "return s.@net.java.html.js.tests.Sum::sum([Ljava/lang/Object;)([1, 2, 3]);"
5.10 + )
5.11 + public static native int sumArr(Sum s);
5.12 }
6.1 --- a/json-tck/src/main/java/net/java/html/js/tests/JavaScriptBodyTest.java Thu Jan 09 19:12:55 2014 +0100
6.2 +++ b/json-tck/src/main/java/net/java/html/js/tests/JavaScriptBodyTest.java Thu Jan 09 20:39:23 2014 +0100
6.3 @@ -267,6 +267,11 @@
6.4 assert new Factorial().factorial(6) == 720;
6.5 }
6.6
6.7 + @KOTest public void sumArray() {
6.8 + int r = Bodies.sumArr(new Sum());
6.9 + assert r == 6 : "Sum is six: " + r;
6.10 + }
6.11 +
6.12 private static class R implements Runnable {
6.13 int cnt;
6.14 private final Thread initThread;
7.1 --- a/json-tck/src/main/java/net/java/html/js/tests/Sum.java Thu Jan 09 19:12:55 2014 +0100
7.2 +++ b/json-tck/src/main/java/net/java/html/js/tests/Sum.java Thu Jan 09 20:39:23 2014 +0100
7.3 @@ -50,4 +50,14 @@
7.4 public int sum(int a, int b) {
7.5 return a + b;
7.6 }
7.7 +
7.8 + public int sum(Object[] arr) {
7.9 + int s = 0;
7.10 + for (int i = 0; i < arr.length; i++) {
7.11 + if (arr[i] instanceof Number) {
7.12 + s += ((Number)arr[i]).intValue();
7.13 + }
7.14 + }
7.15 + return s;
7.16 + }
7.17 }
8.1 --- a/ko-ws-tyrus/pom.xml Thu Jan 09 19:12:55 2014 +0100
8.2 +++ b/ko-ws-tyrus/pom.xml Thu Jan 09 20:39:23 2014 +0100
8.3 @@ -94,7 +94,6 @@
8.4 <groupId>${project.groupId}</groupId>
8.5 <artifactId>net.java.html.boot</artifactId>
8.6 <version>${project.version}</version>
8.7 - <scope>test</scope>
8.8 <type>jar</type>
8.9 </dependency>
8.10 <dependency>
9.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
9.2 +++ b/ko-ws-tyrus/src/main/java/org/netbeans/html/wstyrus/LoadJSON.java Thu Jan 09 20:39:23 2014 +0100
9.3 @@ -0,0 +1,248 @@
9.4 +/**
9.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
9.6 + *
9.7 + * Copyright 2013-2013 Oracle and/or its affiliates. All rights reserved.
9.8 + *
9.9 + * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
9.10 + * Other names may be trademarks of their respective owners.
9.11 + *
9.12 + * The contents of this file are subject to the terms of either the GNU
9.13 + * General Public License Version 2 only ("GPL") or the Common
9.14 + * Development and Distribution License("CDDL") (collectively, the
9.15 + * "License"). You may not use this file except in compliance with the
9.16 + * License. You can obtain a copy of the License at
9.17 + * http://www.netbeans.org/cddl-gplv2.html
9.18 + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
9.19 + * specific language governing permissions and limitations under the
9.20 + * License. When distributing the software, include this License Header
9.21 + * Notice in each file and include the License file at
9.22 + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
9.23 + * particular file as subject to the "Classpath" exception as provided
9.24 + * by Oracle in the GPL Version 2 section of the License file that
9.25 + * accompanied this code. If applicable, add the following below the
9.26 + * License Header, with the fields enclosed by brackets [] replaced by
9.27 + * your own identifying information:
9.28 + * "Portions Copyrighted [year] [name of copyright owner]"
9.29 + *
9.30 + * Contributor(s):
9.31 + *
9.32 + * The Original Software is NetBeans. The Initial Developer of the Original
9.33 + * Software is Oracle. Portions Copyright 2013-2013 Oracle. All Rights Reserved.
9.34 + *
9.35 + * If you wish your version of this file to be governed by only the CDDL
9.36 + * or only the GPL Version 2, indicate your decision by adding
9.37 + * "[Contributor] elects to include this software in this distribution
9.38 + * under the [CDDL or GPL Version 2] license." If you do not indicate a
9.39 + * single choice of license, a recipient has the option to distribute
9.40 + * your version of this file under either the CDDL, the GPL Version 2 or
9.41 + * to extend the choice of license to its licensees as provided above.
9.42 + * However, if you add GPL Version 2 code and therefore, elected the GPL
9.43 + * Version 2 license, then the option applies only if the new code is
9.44 + * made subject to such option by the copyright holder.
9.45 + */
9.46 +package org.netbeans.html.wstyrus;
9.47 +
9.48 +import java.io.IOException;
9.49 +import java.io.InputStream;
9.50 +import java.io.InputStreamReader;
9.51 +import java.io.OutputStream;
9.52 +import java.io.PushbackInputStream;
9.53 +import java.io.Reader;
9.54 +import java.net.HttpURLConnection;
9.55 +import java.net.MalformedURLException;
9.56 +import java.net.URL;
9.57 +import java.net.URLConnection;
9.58 +import java.util.Iterator;
9.59 +import java.util.concurrent.Executor;
9.60 +import java.util.concurrent.Executors;
9.61 +import java.util.concurrent.ThreadFactory;
9.62 +import java.util.logging.Level;
9.63 +import java.util.logging.Logger;
9.64 +import net.java.html.js.JavaScriptBody;
9.65 +import org.apidesign.html.json.spi.JSONCall;
9.66 +import org.json.JSONArray;
9.67 +import org.json.JSONException;
9.68 +import org.json.JSONObject;
9.69 +import org.json.JSONTokener;
9.70 +
9.71 +/** This is an implementation package - just
9.72 + * include its JAR on classpath and use official {@link Context} API
9.73 + * to access the functionality.
9.74 + *
9.75 + * @author Jaroslav Tulach <jtulach@netbeans.org>
9.76 + */
9.77 +final class LoadJSON implements Runnable {
9.78 + private static final Logger LOG = Logger.getLogger(LoadJSON.class.getName());
9.79 + private static final Executor REQ = Executors.newCachedThreadPool(new ThreadFactory() {
9.80 + @Override
9.81 + public Thread newThread(Runnable runnable) {
9.82 + Thread thread = Executors.defaultThreadFactory().newThread(runnable);
9.83 + thread.setDaemon(true);
9.84 + return thread;
9.85 + }
9.86 + });
9.87 +
9.88 + private final JSONCall call;
9.89 + private final URL base;
9.90 +
9.91 +
9.92 + private LoadJSON(JSONCall call) {
9.93 + this.call = call;
9.94 + this.base = null;
9.95 + }
9.96 +
9.97 + public static void loadJSON(JSONCall call) {
9.98 + assert !"WebSocket".equals(call.getMethod());
9.99 + REQ.execute(new LoadJSON((call)));
9.100 + }
9.101 +
9.102 + @Override
9.103 + public void run() {
9.104 + final String url;
9.105 + Throwable error = null;
9.106 + Object json = null;
9.107 +
9.108 + if (call.isJSONP()) {
9.109 + url = call.composeURL("dummy");
9.110 + } else {
9.111 + url = call.composeURL(null);
9.112 + }
9.113 + try {
9.114 + final URL u = new URL(base, url.replace(" ", "%20"));
9.115 + URLConnection conn = u.openConnection();
9.116 + if (conn instanceof HttpURLConnection) {
9.117 + HttpURLConnection huc = (HttpURLConnection) conn;
9.118 + if (call.getMethod() != null) {
9.119 + huc.setRequestMethod(call.getMethod());
9.120 + }
9.121 + if (call.isDoOutput()) {
9.122 + huc.setDoOutput(true);
9.123 + final OutputStream os = huc.getOutputStream();
9.124 + call.writeData(os);
9.125 + os.flush();
9.126 + }
9.127 + }
9.128 + final PushbackInputStream is = new PushbackInputStream(
9.129 + conn.getInputStream(), 1
9.130 + );
9.131 + boolean array = false;
9.132 + boolean string = false;
9.133 + if (call.isJSONP()) {
9.134 + for (;;) {
9.135 + int ch = is.read();
9.136 + if (ch == -1) {
9.137 + break;
9.138 + }
9.139 + if (ch == '[') {
9.140 + is.unread(ch);
9.141 + array = true;
9.142 + break;
9.143 + }
9.144 + if (ch == '{') {
9.145 + is.unread(ch);
9.146 + break;
9.147 + }
9.148 + }
9.149 + } else {
9.150 + int ch = is.read();
9.151 + if (ch == -1) {
9.152 + string = true;
9.153 + } else {
9.154 + array = ch == '[';
9.155 + is.unread(ch);
9.156 + if (!array && ch != '{') {
9.157 + string = true;
9.158 + }
9.159 + }
9.160 + }
9.161 + try {
9.162 + if (string) {
9.163 + throw new JSONException("");
9.164 + }
9.165 + Reader r = new InputStreamReader(is, "UTF-8");
9.166 +
9.167 + JSONTokener tok = new JSONTokener(r);
9.168 + Object obj;
9.169 + obj = array ? new JSONArray(tok) : new JSONObject(tok);
9.170 + json = convertToArray(obj);
9.171 + } catch (JSONException ex) {
9.172 + Reader r = new InputStreamReader(is, "UTF-8");
9.173 + StringBuilder sb = new StringBuilder();
9.174 + for (;;) {
9.175 + int ch = r.read();
9.176 + if (ch == -1) {
9.177 + break;
9.178 + }
9.179 + sb.append((char)ch);
9.180 + }
9.181 + json = sb.toString();
9.182 + }
9.183 + } catch (IOException ex) {
9.184 + error = ex;
9.185 + } finally {
9.186 + if (error != null) {
9.187 + call.notifyError(error);
9.188 + } else {
9.189 + call.notifySuccess(json);
9.190 + }
9.191 + }
9.192 + }
9.193 +
9.194 + static Object convertToArray(Object o) throws JSONException {
9.195 + if (o instanceof JSONArray) {
9.196 + JSONArray ja = (JSONArray)o;
9.197 + Object[] arr = new Object[ja.length()];
9.198 + for (int i = 0; i < arr.length; i++) {
9.199 + arr[i] = convertToArray(ja.get(i));
9.200 + }
9.201 + return arr;
9.202 + } else if (o instanceof JSONObject) {
9.203 + JSONObject obj = (JSONObject)o;
9.204 + Iterator it = obj.keys();
9.205 + while (it.hasNext()) {
9.206 + String key = (String)it.next();
9.207 + obj.put(key, convertToArray(obj.get(key)));
9.208 + }
9.209 + return obj;
9.210 + } else {
9.211 + return o;
9.212 + }
9.213 + }
9.214 +
9.215 + public static void extractJSON(Object jsonObject, String[] props, Object[] values) {
9.216 + if (jsonObject instanceof JSONObject) {
9.217 + JSONObject obj = (JSONObject)jsonObject;
9.218 + for (int i = 0; i < props.length; i++) {
9.219 + try {
9.220 + values[i] = obj.has(props[i]) ? obj.get(props[i]) : null;
9.221 + } catch (JSONException ex) {
9.222 + LoadJSON.LOG.log(Level.SEVERE, "Can't read " + props[i] + " from " + jsonObject, ex);
9.223 + }
9.224 + }
9.225 + return;
9.226 + }
9.227 + for (int i = 0; i < props.length; i++) {
9.228 + values[i] = getProperty(jsonObject, props[i]);
9.229 + }
9.230 + }
9.231 +
9.232 + @JavaScriptBody(args = {"object", "property"},
9.233 + body
9.234 + = "if (property === null) return object;\n"
9.235 + + "if (object === null) return null;\n"
9.236 + + "var p = object[property]; return p ? p : null;"
9.237 + )
9.238 + private static Object getProperty(Object object, String property) {
9.239 + return null;
9.240 + }
9.241 +
9.242 + public static Object parse(InputStream is) throws IOException {
9.243 + try {
9.244 + InputStreamReader r = new InputStreamReader(is, "UTF-8");
9.245 + JSONTokener t = new JSONTokener(r);
9.246 + return new JSONObject(t);
9.247 + } catch (JSONException ex) {
9.248 + throw new IOException(ex);
9.249 + }
9.250 + }
9.251 +}
10.1 --- a/ko-ws-tyrus/src/main/java/org/netbeans/html/wstyrus/TyrusContext.java Thu Jan 09 19:12:55 2014 +0100
10.2 +++ b/ko-ws-tyrus/src/main/java/org/netbeans/html/wstyrus/TyrusContext.java Thu Jan 09 20:39:23 2014 +0100
10.3 @@ -43,6 +43,7 @@
10.4 package org.netbeans.html.wstyrus;
10.5
10.6 import java.io.IOException;
10.7 +import java.io.InputStream;
10.8 import java.net.URI;
10.9 import java.net.URISyntaxException;
10.10 import java.util.Iterator;
10.11 @@ -58,6 +59,8 @@
10.12 import net.java.html.json.OnReceive;
10.13 import org.apidesign.html.context.spi.Contexts;
10.14 import org.apidesign.html.json.spi.JSONCall;
10.15 +import org.apidesign.html.json.spi.Technology;
10.16 +import org.apidesign.html.json.spi.Transfer;
10.17 import org.apidesign.html.json.spi.WSTransfer;
10.18 import org.netbeans.html.wstyrus.TyrusContext.Comm;
10.19 import org.json.JSONArray;
10.20 @@ -82,12 +85,14 @@
10.21 * @author Jaroslav Tulach <jtulach@netbeans.org>
10.22 */
10.23 @ServiceProvider(service = Contexts.Provider.class)
10.24 -public final class TyrusContext implements Contexts.Provider, WSTransfer<Comm> {
10.25 +public final class TyrusContext
10.26 +implements Contexts.Provider, WSTransfer<Comm>, Transfer {
10.27 @Override
10.28 public void fillContext(Contexts.Builder context, Class<?> requestor) {
10.29 // default WebSocket transfer implementation is registered
10.30 // in ko-fx module with 100, provide this one as a fallback only
10.31 context.register(WSTransfer.class, this, 1000);
10.32 + context.register(Transfer.class, this, 1000);
10.33 }
10.34
10.35 @Override
10.36 @@ -115,6 +120,21 @@
10.37 socket.callback.notifyError(ex);
10.38 }
10.39 }
10.40 +
10.41 + @Override
10.42 + public void extract(Object obj, String[] props, Object[] values) {
10.43 + LoadJSON.extractJSON(obj, props, values);
10.44 + }
10.45 +
10.46 + @Override
10.47 + public Object toJSON(InputStream is) throws IOException {
10.48 + return LoadJSON.parse(is);
10.49 + }
10.50 +
10.51 + @Override
10.52 + public void loadJSON(JSONCall call) {
10.53 + LoadJSON.loadJSON(call);
10.54 + }
10.55
10.56 /** Implementation class in an implementation. Represents a {@link ClientEndpoint} of the
10.57 * WebSocket channel. You are unlikely to get on hold of it.
11.1 --- a/ko-ws-tyrus/src/test/java/org/netbeans/html/wstyrus/TyrusKnockoutTest.java Thu Jan 09 19:12:55 2014 +0100
11.2 +++ b/ko-ws-tyrus/src/test/java/org/netbeans/html/wstyrus/TyrusKnockoutTest.java Thu Jan 09 20:39:23 2014 +0100
11.3 @@ -150,7 +150,7 @@
11.4 TyrusContext tc = new TyrusContext();
11.5 Contexts.Builder cb = Contexts.newBuilder().
11.6 register(Technology.class, ko.knockout(), 10).
11.7 - register(Transfer.class, ko.transferViaOrgJSON(), 10).
11.8 + register(Transfer.class, tc, 10).
11.9 register(WSTransfer.class, tc, 10);
11.10 return cb.build();
11.11 }
12.1 --- a/ko4j/pom.xml Thu Jan 09 19:12:55 2014 +0100
12.2 +++ b/ko4j/pom.xml Thu Jan 09 20:39:23 2014 +0100
12.3 @@ -49,10 +49,6 @@
12.4 <systemPath>${jfxrt.jar}</systemPath>
12.5 </dependency>
12.6 <dependency>
12.7 - <groupId>de.twentyeleven.skysail</groupId>
12.8 - <artifactId>org.json-osgi</artifactId>
12.9 - </dependency>
12.10 - <dependency>
12.11 <groupId>org.netbeans.html</groupId>
12.12 <artifactId>net.java.html.json</artifactId>
12.13 <version>${project.version}</version>
13.1 --- a/ko4j/src/main/java/org/netbeans/html/ko4j/FXContext.java Thu Jan 09 19:12:55 2014 +0100
13.2 +++ b/ko4j/src/main/java/org/netbeans/html/ko4j/FXContext.java Thu Jan 09 20:39:23 2014 +0100
13.3 @@ -42,9 +42,11 @@
13.4 */
13.5 package org.netbeans.html.ko4j;
13.6
13.7 +import java.io.ByteArrayOutputStream;
13.8 import java.io.Closeable;
13.9 import java.io.IOException;
13.10 import java.io.InputStream;
13.11 +import java.io.InputStreamReader;
13.12 import java.util.concurrent.Executor;
13.13 import java.util.logging.Logger;
13.14 import net.java.html.js.JavaScriptBody;
13.15 @@ -73,7 +75,7 @@
13.16 this.browserContext = browserContext;
13.17 }
13.18
13.19 - @JavaScriptBody(args = {}, body = "return true;")
13.20 + @JavaScriptBody(args = {}, body = "if (window) return true; else return false;")
13.21 private static boolean isJavaScriptEnabledJs() {
13.22 return false;
13.23 }
13.24 @@ -148,7 +150,22 @@
13.25
13.26 @Override
13.27 public void loadJSON(final JSONCall call) {
13.28 - LoadJSON.loadJSON(call);
13.29 + if (call.isJSONP()) {
13.30 + String me = LoadJSON.createJSONP(call);
13.31 + LoadJSON.loadJSONP(call.composeURL(me), me);
13.32 + } else {
13.33 + String data = null;
13.34 + if (call.isDoOutput()) {
13.35 + try {
13.36 + ByteArrayOutputStream bos = new ByteArrayOutputStream();
13.37 + call.writeData(bos);
13.38 + data = new String(bos.toByteArray(), "UTF-8");
13.39 + } catch (IOException ex) {
13.40 + call.notifyError(ex);
13.41 + }
13.42 + }
13.43 + LoadJSON.loadJSON(call.composeURL(null), call, call.getMethod(), data);
13.44 + }
13.45 }
13.46
13.47 @Override
13.48 @@ -158,7 +175,16 @@
13.49
13.50 @Override
13.51 public Object toJSON(InputStream is) throws IOException {
13.52 - return LoadJSON.parse(is);
13.53 + StringBuilder sb = new StringBuilder();
13.54 + InputStreamReader r = new InputStreamReader(is);
13.55 + for (;;) {
13.56 + int ch = r.read();
13.57 + if (ch == -1) {
13.58 + break;
13.59 + }
13.60 + sb.append((char)ch);
13.61 + }
13.62 + return LoadJSON.parse(sb.toString());
13.63 }
13.64
13.65 @Override
14.1 --- a/ko4j/src/main/java/org/netbeans/html/ko4j/KO4J.java Thu Jan 09 19:12:55 2014 +0100
14.2 +++ b/ko4j/src/main/java/org/netbeans/html/ko4j/KO4J.java Thu Jan 09 20:39:23 2014 +0100
14.3 @@ -84,34 +84,31 @@
14.4 return getKO();
14.5 }
14.6
14.7 - /** Java based implementation of transfer interface. Requires
14.8 - * org.json libraries on classpath. Use: <pre>
14.9 -<dependency>
14.10 - <groupId>de.twentyeleven.skysail</groupId>
14.11 - <artifactId>org.json-osgi</artifactId>
14.12 -</dependency>
14.13 - * </pre>
14.14 - * @return instance of the technology or <code>null</code>,
14.15 - * if <code>org.json</code> interfaces are not around
14.16 + /** Browser based implementation of transfer interface. Uses
14.17 + * browser method to convert string to JSON.
14.18 + *
14.19 + * @return non-null instance
14.20 */
14.21 - public Transfer transferViaOrgJSON() {
14.22 + public Transfer transfer() {
14.23 return getKO();
14.24 }
14.25
14.26 /** Returns browser based implementation of websocket transfer.
14.27 + * If available (for example JavaFX WebView on JDK7 does not have
14.28 + * this implementation).
14.29 *
14.30 * @return an instance or <code>null</code>, if there is no
14.31 * <code>WebSocket</code> object in the browser
14.32 */
14.33 - public WSTransfer<?> websocketsViaBrowser() {
14.34 + public WSTransfer<?> websockets() {
14.35 return getKO().areWebSocketsSupported() ? getKO() : null;
14.36 }
14.37
14.38 /** Registers technologies at position 100:
14.39 * <ul>
14.40 * <li>{@link #knockout()}</li>
14.41 - * <li>{@link #transferViaOrgJSON()} - if <code>org.json</code> libraries are around</li>
14.42 - * <li>{@link #websocketsViaBrowser()()} - if browser supports web sockets</li>
14.43 + * <li>{@link #transfer()}</li>
14.44 + * <li>{@link #websockets()()} - if browser supports web sockets</li>
14.45 * </ul>
14.46 * @param context the context to register to
14.47 * @param requestor the class requesting the registration
14.48 @@ -120,9 +117,9 @@
14.49 public void fillContext(Contexts.Builder context, Class<?> requestor) {
14.50 if (FXContext.isJavaScriptEnabled()) {
14.51 context.register(Technology.class, knockout(), 100);
14.52 - context.register(Transfer.class, transferViaOrgJSON(), 100);
14.53 + context.register(Transfer.class, transfer(), 100);
14.54 if (c.areWebSocketsSupported()) {
14.55 - context.register(WSTransfer.class, websocketsViaBrowser(), 100);
14.56 + context.register(WSTransfer.class, websockets(), 100);
14.57 }
14.58 }
14.59 }
15.1 --- a/ko4j/src/main/java/org/netbeans/html/ko4j/LoadJSON.java Thu Jan 09 19:12:55 2014 +0100
15.2 +++ b/ko4j/src/main/java/org/netbeans/html/ko4j/LoadJSON.java Thu Jan 09 20:39:23 2014 +0100
15.3 @@ -42,226 +42,167 @@
15.4 */
15.5 package org.netbeans.html.ko4j;
15.6
15.7 +import java.io.ByteArrayOutputStream;
15.8 import java.io.IOException;
15.9 import java.io.InputStream;
15.10 import java.io.InputStreamReader;
15.11 -import java.io.OutputStream;
15.12 -import java.io.PushbackInputStream;
15.13 -import java.io.Reader;
15.14 -import java.net.HttpURLConnection;
15.15 -import java.net.MalformedURLException;
15.16 -import java.net.URL;
15.17 -import java.net.URLConnection;
15.18 -import java.util.Iterator;
15.19 -import java.util.concurrent.Executor;
15.20 -import java.util.concurrent.Executors;
15.21 -import java.util.concurrent.ThreadFactory;
15.22 -import java.util.logging.Level;
15.23 -import java.util.logging.Logger;
15.24 -import javafx.application.Platform;
15.25 import net.java.html.js.JavaScriptBody;
15.26 -import netscape.javascript.JSObject;
15.27 import org.apidesign.html.json.spi.JSONCall;
15.28 -import org.json.JSONArray;
15.29 -import org.json.JSONException;
15.30 -import org.json.JSONObject;
15.31 -import org.json.JSONTokener;
15.32 +import org.apidesign.html.json.spi.Transfer;
15.33 +import org.apidesign.html.json.spi.WSTransfer;
15.34
15.35 -/** This is an implementation package - just
15.36 - * include its JAR on classpath and use official {@link Context} API
15.37 - * to access the functionality.
15.38 +/**
15.39 *
15.40 * @author Jaroslav Tulach <jtulach@netbeans.org>
15.41 */
15.42 -final class LoadJSON implements Runnable {
15.43 - private static final Logger LOG = FXContext.LOG;
15.44 - private static final Executor REQ = Executors.newCachedThreadPool(new ThreadFactory() {
15.45 - @Override
15.46 - public Thread newThread(Runnable runnable) {
15.47 - Thread thread = Executors.defaultThreadFactory().newThread(runnable);
15.48 - thread.setDaemon(true);
15.49 - return thread;
15.50 - }
15.51 - });
15.52 -
15.53 - private final JSONCall call;
15.54 - private final URL base;
15.55 - private Throwable error;
15.56 - private Object json;
15.57 -
15.58 -
15.59 - private LoadJSON(JSONCall call) {
15.60 - this.call = call;
15.61 - URL b;
15.62 - try {
15.63 - b = new URL(findBaseURL());
15.64 - } catch (MalformedURLException ex) {
15.65 - LOG.log(Level.SEVERE, "Can't find base url for " + call.composeURL("dummy"), ex);
15.66 - b = null;
15.67 - }
15.68 - this.base = b;
15.69 - }
15.70 -
15.71 - public static void loadJSON(JSONCall call) {
15.72 - assert !"WebSocket".equals(call.getMethod());
15.73 - REQ.execute(new LoadJSON((call)));
15.74 +final class LoadJSON implements Transfer, WSTransfer<LoadWS> {
15.75 + private LoadJSON() {}
15.76 +
15.77 + @Override
15.78 + public void extract(Object obj, String[] props, Object[] values) {
15.79 + extractJSON(obj, props, values);
15.80 }
15.81
15.82 @Override
15.83 - public void run() {
15.84 - if (Platform.isFxApplicationThread()) {
15.85 - if (error != null) {
15.86 - call.notifyError(error);
15.87 - } else {
15.88 - call.notifySuccess(json);
15.89 - }
15.90 - return;
15.91 - }
15.92 - final String url;
15.93 + public void loadJSON(final JSONCall call) {
15.94 if (call.isJSONP()) {
15.95 - url = call.composeURL("dummy");
15.96 + String me = createJSONP(call);
15.97 + loadJSONP(call.composeURL(me), me);
15.98 } else {
15.99 - url = call.composeURL(null);
15.100 - }
15.101 - try {
15.102 - final URL u = new URL(base, url.replace(" ", "%20"));
15.103 - URLConnection conn = u.openConnection();
15.104 - if (conn instanceof HttpURLConnection) {
15.105 - HttpURLConnection huc = (HttpURLConnection) conn;
15.106 - if (call.getMethod() != null) {
15.107 - huc.setRequestMethod(call.getMethod());
15.108 - }
15.109 - if (call.isDoOutput()) {
15.110 - huc.setDoOutput(true);
15.111 - final OutputStream os = huc.getOutputStream();
15.112 - call.writeData(os);
15.113 - os.flush();
15.114 + String data = null;
15.115 + if (call.isDoOutput()) {
15.116 + try {
15.117 + ByteArrayOutputStream bos = new ByteArrayOutputStream();
15.118 + call.writeData(bos);
15.119 + data = new String(bos.toByteArray(), "UTF-8");
15.120 + } catch (IOException ex) {
15.121 + call.notifyError(ex);
15.122 }
15.123 }
15.124 - final PushbackInputStream is = new PushbackInputStream(
15.125 - conn.getInputStream(), 1
15.126 - );
15.127 - boolean array = false;
15.128 - boolean string = false;
15.129 - if (call.isJSONP()) {
15.130 - for (;;) {
15.131 - int ch = is.read();
15.132 - if (ch == -1) {
15.133 - break;
15.134 - }
15.135 - if (ch == '[') {
15.136 - is.unread(ch);
15.137 - array = true;
15.138 - break;
15.139 - }
15.140 - if (ch == '{') {
15.141 - is.unread(ch);
15.142 - break;
15.143 - }
15.144 - }
15.145 - } else {
15.146 - int ch = is.read();
15.147 - if (ch == -1) {
15.148 - string = true;
15.149 - } else {
15.150 - array = ch == '[';
15.151 - is.unread(ch);
15.152 - if (!array && ch != '{') {
15.153 - string = true;
15.154 - }
15.155 - }
15.156 - }
15.157 - try {
15.158 - if (string) {
15.159 - throw new JSONException("");
15.160 - }
15.161 - Reader r = new InputStreamReader(is, "UTF-8");
15.162 -
15.163 - JSONTokener tok = new JSONTokener(r);
15.164 - Object obj;
15.165 - obj = array ? new JSONArray(tok) : new JSONObject(tok);
15.166 - json = convertToArray(obj);
15.167 - } catch (JSONException ex) {
15.168 - Reader r = new InputStreamReader(is, "UTF-8");
15.169 - StringBuilder sb = new StringBuilder();
15.170 - for (;;) {
15.171 - int ch = r.read();
15.172 - if (ch == -1) {
15.173 - break;
15.174 - }
15.175 - sb.append((char)ch);
15.176 - }
15.177 - json = sb.toString();
15.178 - }
15.179 - } catch (IOException ex) {
15.180 - error = ex;
15.181 - } finally {
15.182 - Platform.runLater(this);
15.183 + loadJSON(call.composeURL(null), call, call.getMethod(), data);
15.184 }
15.185 }
15.186
15.187 - static Object convertToArray(Object o) throws JSONException {
15.188 - if (o instanceof JSONArray) {
15.189 - JSONArray ja = (JSONArray)o;
15.190 - Object[] arr = new Object[ja.length()];
15.191 - for (int i = 0; i < arr.length; i++) {
15.192 - arr[i] = convertToArray(ja.get(i));
15.193 + @Override
15.194 + public Object toJSON(InputStream is) throws IOException {
15.195 + StringBuilder sb = new StringBuilder();
15.196 + InputStreamReader r = new InputStreamReader(is);
15.197 + for (;;) {
15.198 + int ch = r.read();
15.199 + if (ch == -1) {
15.200 + break;
15.201 }
15.202 - return arr;
15.203 - } else if (o instanceof JSONObject) {
15.204 - JSONObject obj = (JSONObject)o;
15.205 - Iterator it = obj.keys();
15.206 - while (it.hasNext()) {
15.207 - String key = (String)it.next();
15.208 - obj.put(key, convertToArray(obj.get(key)));
15.209 + sb.append((char)ch);
15.210 + }
15.211 + return parse(sb.toString());
15.212 + }
15.213 +
15.214 + @Override
15.215 + public LoadWS open(String url, JSONCall callback) {
15.216 + return new LoadWS(callback, url);
15.217 + }
15.218 +
15.219 + @Override
15.220 + public void send(LoadWS socket, JSONCall data) {
15.221 + socket.send(data);
15.222 + }
15.223 +
15.224 + @Override
15.225 + public void close(LoadWS socket) {
15.226 + socket.close();
15.227 + }
15.228 +
15.229 + //
15.230 + // implementations
15.231 + //
15.232 +
15.233 + @JavaScriptBody(args = {"object", "property"},
15.234 + body
15.235 + = "if (property === null) return object;\n"
15.236 + + "if (object === null) return null;\n"
15.237 + + "var p = object[property]; return p ? p : null;"
15.238 + )
15.239 + private static Object getProperty(Object object, String property) {
15.240 + return null;
15.241 + }
15.242 +
15.243 + static String createJSONP(JSONCall whenDone) {
15.244 + int h = whenDone.hashCode();
15.245 + String name;
15.246 + for (;;) {
15.247 + name = "jsonp" + Integer.toHexString(h);
15.248 + if (defineIfUnused(name, whenDone)) {
15.249 + return name;
15.250 }
15.251 - return obj;
15.252 - } else {
15.253 - return o;
15.254 + h++;
15.255 + }
15.256 + }
15.257 +
15.258 + @JavaScriptBody(args = {"name", "done"}, javacall = true, body
15.259 + = "if (window[name]) return false;\n "
15.260 + + "window[name] = function(data) {\n "
15.261 + + " delete window[name];\n"
15.262 + + " var el = window.document.getElementById(name);\n"
15.263 + + " el.parentNode.removeChild(el);\n"
15.264 + + " done.@org.apidesign.html.json.spi.JSONCall::notifySuccess(Ljava/lang/Object;)(data);\n"
15.265 + + "};\n"
15.266 + + "return true;\n"
15.267 + )
15.268 + private static boolean defineIfUnused(String name, JSONCall done) {
15.269 + return true;
15.270 + }
15.271 +
15.272 + @JavaScriptBody(args = {"s"}, body = "return eval('(' + s + ')');")
15.273 + static Object parse(String s) {
15.274 + return s;
15.275 + }
15.276 +
15.277 + @JavaScriptBody(args = {"url", "done", "method", "data"}, javacall = true, body = ""
15.278 + + "var request = new XMLHttpRequest();\n"
15.279 + + "if (!method) method = 'GET';\n"
15.280 + + "request.open(method, url, true);\n"
15.281 + + "request.setRequestHeader('Content-Type', 'application/json; charset=utf-8');\n"
15.282 + + "request.onreadystatechange = function() {\n"
15.283 + + " if (this.readyState!==4) return;\n"
15.284 + + " try {\n"
15.285 + + " var r = this.response;\n"
15.286 + + " try { r = eval('(' + this.response + ')'); } catch (ignore) { }"
15.287 + + " done.@org.apidesign.html.json.spi.JSONCall::notifySuccess(Ljava/lang/Object;)(r);\n"
15.288 + + " } catch (error) {;\n"
15.289 + + " @org.netbeans.html.ko4j.LoadJSON::notifyError(Ljava/lang/Object;Ljava/lang/Object;)(done, this.response);\n"
15.290 + + " }\n"
15.291 + + "};\n"
15.292 + + "request.onerror = function (e) {console.log('eeeor:' + Object.getOwnPropertyNames(e));\n"
15.293 + + " @org.netbeans.html.ko4j.LoadJSON::notifyError(Ljava/lang/Object;Ljava/lang/Object;)(done, e);\n"
15.294 + + "}\n"
15.295 + + "if (data) request.send(data);"
15.296 + + "else request.send();"
15.297 + )
15.298 + static void loadJSON(
15.299 + String url, JSONCall done, String method, String data
15.300 + ) {
15.301 + }
15.302 +
15.303 + static void notifyError(Object done, Object msg) {
15.304 + ((JSONCall)done).notifyError(new Exception(msg.toString()));
15.305 + }
15.306 +
15.307 + @JavaScriptBody(args = {"url", "jsonp"}, body
15.308 + = "var scrpt = window.document.createElement('script');\n "
15.309 + + "scrpt.setAttribute('src', url);\n "
15.310 + + "scrpt.setAttribute('id', jsonp);\n "
15.311 + + "scrpt.setAttribute('type', 'text/javascript');\n "
15.312 + + "var body = document.getElementsByTagName('body')[0];\n "
15.313 + + "body.appendChild(scrpt);\n"
15.314 + )
15.315 + static void loadJSONP(String url, String jsonp) {
15.316 +
15.317 + }
15.318 +
15.319 + static void extractJSON(Object jsonObject, String[] props, Object[] values) {
15.320 + for (int i = 0; i < props.length; i++) {
15.321 + values[i] = getProperty(jsonObject, props[i]);
15.322 }
15.323 }
15.324
15.325 - public static void extractJSON(Object jsonObject, String[] props, Object[] values) {
15.326 - if (jsonObject instanceof JSONObject) {
15.327 - JSONObject obj = (JSONObject)jsonObject;
15.328 - for (int i = 0; i < props.length; i++) {
15.329 - try {
15.330 - values[i] = obj.has(props[i]) ? obj.get(props[i]) : null;
15.331 - } catch (JSONException ex) {
15.332 - LoadJSON.LOG.log(Level.SEVERE, "Can't read " + props[i] + " from " + jsonObject, ex);
15.333 - }
15.334 - }
15.335 - }
15.336 - if (jsonObject instanceof JSObject) {
15.337 - JSObject obj = (JSObject)jsonObject;
15.338 - for (int i = 0; i < props.length; i++) {
15.339 - Object val = obj.getMember(props[i]);
15.340 - values[i] = isDefined(val) ? val : null;
15.341 - }
15.342 - }
15.343 - }
15.344 -
15.345 - public static Object parse(InputStream is) throws IOException {
15.346 - try {
15.347 - InputStreamReader r = new InputStreamReader(is, "UTF-8");
15.348 - JSONTokener t = new JSONTokener(r);
15.349 - return new JSONObject(t);
15.350 - } catch (JSONException ex) {
15.351 - throw new IOException(ex);
15.352 - }
15.353 - }
15.354 -
15.355 - @JavaScriptBody(args = { }, body =
15.356 - "var h;"
15.357 - + "if (!!window && !!window.location && !!window.location.href)\n"
15.358 - + " h = window.location.href;\n"
15.359 - + "else "
15.360 - + " h = null;"
15.361 - + "return h;\n"
15.362 - )
15.363 - private static native String findBaseURL();
15.364 -
15.365 - private static boolean isDefined(Object val) {
15.366 - return !"undefined".equals(val);
15.367 - }
15.368 }
16.1 --- a/ko4j/src/main/java/org/netbeans/html/ko4j/LoadWS.java Thu Jan 09 19:12:55 2014 +0100
16.2 +++ b/ko4j/src/main/java/org/netbeans/html/ko4j/LoadWS.java Thu Jan 09 20:39:23 2014 +0100
16.3 @@ -44,12 +44,8 @@
16.4
16.5 import net.java.html.js.JavaScriptBody;
16.6 import org.apidesign.html.json.spi.JSONCall;
16.7 -import org.json.JSONArray;
16.8 -import org.json.JSONException;
16.9 -import org.json.JSONObject;
16.10 -import org.json.JSONTokener;
16.11
16.12 -/** Communication with WebSockets for WebView 1.8.
16.13 +/** Communication with WebSockets via browser's WebSocket object.
16.14 *
16.15 * @author Jaroslav Tulach <jtulach@netbeans.org>
16.16 */
16.17 @@ -57,7 +53,6 @@
16.18 private static final boolean SUPPORTED = isWebSocket();
16.19 private final Object ws;
16.20 private final JSONCall call;
16.21 -
16.22 LoadWS(JSONCall first, String url) {
16.23 call = first;
16.24 ws = initWebSocket(this, url);
16.25 @@ -84,18 +79,17 @@
16.26 }
16.27 }
16.28
16.29 +
16.30 + @JavaScriptBody(args = { "data" }, body = "try {\n"
16.31 + + " return eval('(' + data + ')');\n"
16.32 + + " } catch (error) {;\n"
16.33 + + " return data;\n"
16.34 + + " }\n"
16.35 + )
16.36 + private static native Object toJSON(String data);
16.37 +
16.38 void onMessage(Object ev, String data) {
16.39 - Object json;
16.40 - try {
16.41 - data = data.trim();
16.42 -
16.43 - JSONTokener tok = new JSONTokener(data);
16.44 - Object obj;
16.45 - obj = data.startsWith("[") ? new JSONArray(tok) : new JSONObject(tok);
16.46 - json = LoadJSON.convertToArray(obj);
16.47 - } catch (JSONException ex) {
16.48 - json = data;
16.49 - }
16.50 + Object json = toJSON(data);
16.51 call.notifySuccess(json);
16.52 }
16.53
16.54 @@ -113,20 +107,28 @@
16.55 }
16.56
16.57 @JavaScriptBody(args = { "back", "url" }, javacall = true, body = ""
16.58 - + "if (window.WebSocket) {"
16.59 - + " try {"
16.60 - + " var ws = new window.WebSocket(url);"
16.61 - + " ws.onopen = function(ev) { back.@org.netbeans.html.ko4j.LoadWS::onOpen(Ljava/lang/Object;)(ev); };"
16.62 - + " ws.onmessage = function(ev) { back.@org.netbeans.html.ko4j.LoadWS::onMessage(Ljava/lang/Object;Ljava/lang/String;)(ev, ev.data); };"
16.63 - + " ws.onerror = function(ev) { back.@org.netbeans.html.ko4j.LoadWS::onError(Ljava/lang/Object;)(ev); };"
16.64 - + " ws.onclose = function(ev) { back.@org.netbeans.html.ko4j.LoadWS::onClose(ZILjava/lang/String;)(ev.wasClean, ev.code, ev.reason); };"
16.65 - + " return ws;"
16.66 - + " } catch (ex) {"
16.67 - + " return null;"
16.68 - + " }"
16.69 - + "} else {"
16.70 - + " return null;"
16.71 - + "}"
16.72 + + "if (window.WebSocket) {\n"
16.73 + + " try {\n"
16.74 + + " var ws = new window.WebSocket(url);\n"
16.75 + + " ws.onopen = function(ev) {\n"
16.76 + + " back.@org.netbeans.html.ko4j.LoadWS::onOpen(Ljava/lang/Object;)(ev);\n"
16.77 + + " };\n"
16.78 + + " ws.onmessage = function(ev) {\n"
16.79 + + " back.@org.netbeans.html.ko4j.LoadWS::onMessage(Ljava/lang/Object;Ljava/lang/String;)(ev, ev.data);\n"
16.80 + + " };\n"
16.81 + + " ws.onerror = function(ev) {\n"
16.82 + + " back.@org.netbeans.html.ko4j.LoadWS::onError(Ljava/lang/Object;)(ev);\n"
16.83 + + " };\n"
16.84 + + " ws.onclose = function(ev) {\n"
16.85 + + " back.@org.netbeans.html.ko4j.LoadWS::onClose(ZILjava/lang/String;)(ev.wasClean, ev.code, ev.reason);\n"
16.86 + + " };\n"
16.87 + + " return ws;\n"
16.88 + + " } catch (ex) {\n"
16.89 + + " return null;\n"
16.90 + + " }\n"
16.91 + + "} else {\n"
16.92 + + " return null;\n"
16.93 + + "}\n"
16.94 )
16.95 private static Object initWebSocket(Object back, String url) {
16.96 return null;
17.1 --- a/ko4j/src/test/java/org/netbeans/html/ko4j/KnockoutFXTest.java Thu Jan 09 19:12:55 2014 +0100
17.2 +++ b/ko4j/src/test/java/org/netbeans/html/ko4j/KnockoutFXTest.java Thu Jan 09 20:39:23 2014 +0100
17.3 @@ -66,8 +66,6 @@
17.4 import org.apidesign.html.json.spi.WSTransfer;
17.5 import org.apidesign.html.json.tck.KOTest;
17.6 import org.apidesign.html.json.tck.KnockoutTCK;
17.7 -import org.json.JSONException;
17.8 -import org.json.JSONObject;
17.9 import org.openide.util.lookup.ServiceProvider;
17.10 import org.testng.annotations.Factory;
17.11 import static org.testng.Assert.*;
17.12 @@ -163,16 +161,17 @@
17.13
17.14 @Override
17.15 public Object createJSON(Map<String, Object> values) {
17.16 - JSONObject json = new JSONObject();
17.17 + Object json = createJSON();
17.18 for (Map.Entry<String, Object> entry : values.entrySet()) {
17.19 - try {
17.20 - json.put(entry.getKey(), entry.getValue());
17.21 - } catch (JSONException ex) {
17.22 - throw new IllegalStateException(ex);
17.23 - }
17.24 + setProperty(json, entry.getKey(), entry.getValue());
17.25 }
17.26 return json;
17.27 }
17.28 +
17.29 + @JavaScriptBody(args = {}, body = "return new Object();")
17.30 + private static native Object createJSON();
17.31 + @JavaScriptBody(args = { "json", "key", "value" }, body = "json[key] = value;")
17.32 + private static native void setProperty(Object json, String key, Object value);
17.33
17.34 @Override
17.35 @JavaScriptBody(args = { "s", "args" }, body = ""