aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--gwtquery-core/pom.xml7
-rw-r--r--gwtquery-core/src/main/java/com/google/gwt/junit/RunStyleHtmlUnit.java318
-rw-r--r--gwtquery-core/src/main/java/com/google/gwt/query/client/GQ.java33
-rw-r--r--gwtquery-core/src/main/java/com/google/gwt/query/client/builders/JsonFactory.java3
-rw-r--r--gwtquery-core/src/main/java/com/google/gwt/query/client/js/JsUtils.java2
-rwxr-xr-xgwtquery-core/src/main/java/com/google/gwt/query/client/plugins/Effects.java3
-rw-r--r--gwtquery-core/src/main/java/com/google/gwt/query/client/plugins/ajax/Ajax.java85
-rw-r--r--gwtquery-core/src/main/java/com/google/gwt/query/client/plugins/ajax/AjaxTransportJs.java12
-rw-r--r--gwtquery-core/src/main/java/com/google/gwt/query/client/plugins/effects/Transitions.java2
-rw-r--r--gwtquery-core/src/main/java/com/google/gwt/query/rebind/JsonBuilderGenerator.java11
-rw-r--r--gwtquery-core/src/main/java/com/google/gwt/query/vm/AjaxTransportJre.java103
-rw-r--r--gwtquery-core/src/main/java/com/google/gwt/query/vm/JsonFactoryJre.java21
-rw-r--r--gwtquery-core/src/main/java/com/google/gwt/query/vm/ResponseJre.java66
-rw-r--r--gwtquery-core/src/main/super/com/google/gwt/query/super/com/google/gwt/query/client/GQ.java28
-rw-r--r--gwtquery-core/src/test/java/com/google/gwt/dev/shell/BrowserChannel.java1720
-rw-r--r--gwtquery-core/src/test/java/com/google/gwt/query/QueryTest.gwt.xml21
-rw-r--r--gwtquery-core/src/test/java/com/google/gwt/query/client/ajax/AjaxCommon.java156
-rw-r--r--gwtquery-core/src/test/java/com/google/gwt/query/client/ajax/AjaxTest.java90
-rw-r--r--gwtquery-core/src/test/java/com/google/gwt/query/client/ajax/AjaxTestGwt.java17
-rw-r--r--gwtquery-core/src/test/java/com/google/gwt/query/servlet/GQAjaxTestServlet.java74
20 files changed, 2676 insertions, 96 deletions
diff --git a/gwtquery-core/pom.xml b/gwtquery-core/pom.xml
index c44b7f9b..7bfbbb9a 100644
--- a/gwtquery-core/pom.xml
+++ b/gwtquery-core/pom.xml
@@ -16,10 +16,15 @@
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.7</version>
- <type>jar</type>
<scope>test</scope>
</dependency>
<dependency>
+ <groupId>net.sourceforge.htmlunit</groupId>
+ <artifactId>htmlunit</artifactId>
+ <version>2.13</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
<groupId>com.google.gwt</groupId>
<artifactId>gwt-user</artifactId>
<version>${gwtversion}</version>
diff --git a/gwtquery-core/src/main/java/com/google/gwt/junit/RunStyleHtmlUnit.java b/gwtquery-core/src/main/java/com/google/gwt/junit/RunStyleHtmlUnit.java
new file mode 100644
index 00000000..0d8b5fe1
--- /dev/null
+++ b/gwtquery-core/src/main/java/com/google/gwt/junit/RunStyleHtmlUnit.java
@@ -0,0 +1,318 @@
+/*
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.junit;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.dev.shell.HostedModePluginObject;
+import com.google.gwt.thirdparty.guava.common.collect.ImmutableSet;
+
+import com.gargoylesoftware.htmlunit.AlertHandler;
+import com.gargoylesoftware.htmlunit.BrowserVersion;
+import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException;
+import com.gargoylesoftware.htmlunit.IncorrectnessListener;
+import com.gargoylesoftware.htmlunit.OnbeforeunloadHandler;
+import com.gargoylesoftware.htmlunit.Page;
+import com.gargoylesoftware.htmlunit.ScriptException;
+import com.gargoylesoftware.htmlunit.WebClient;
+import com.gargoylesoftware.htmlunit.WebWindow;
+import com.gargoylesoftware.htmlunit.html.HtmlPage;
+import com.gargoylesoftware.htmlunit.javascript.JavaScriptEngine;
+import com.gargoylesoftware.htmlunit.javascript.JavaScriptErrorListener;
+import com.gargoylesoftware.htmlunit.javascript.host.Window;
+
+import net.sourceforge.htmlunit.corejs.javascript.ScriptableObject;
+
+import org.w3c.css.sac.CSSParseException;
+import org.w3c.css.sac.ErrorHandler;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Launches a web-mode test via HTMLUnit.
+ */
+public class RunStyleHtmlUnit extends RunStyle {
+
+ /**
+ * Runs HTMLUnit in a separate thread.
+ */
+ protected static class HtmlUnitThread extends Thread implements AlertHandler,
+ IncorrectnessListener, OnbeforeunloadHandler {
+
+ private final BrowserVersion browser;
+ private final boolean developmentMode;
+ private final TreeLogger treeLogger;
+ private final String url;
+ private Object waitForUnload = new Object();
+
+ public HtmlUnitThread(BrowserVersion browser, String url,
+ TreeLogger treeLogger, boolean developmentMode) {
+ this.browser = browser;
+ this.url = url;
+ this.treeLogger = treeLogger;
+ this.setName("htmlUnit client thread");
+ this.developmentMode = developmentMode;
+ }
+
+ public void handleAlert(Page page, String message) {
+ treeLogger.log(TreeLogger.ERROR, "Alert: " + message);
+ }
+
+ public boolean handleEvent(Page page, String returnValue) {
+ synchronized (waitForUnload) {
+ waitForUnload.notifyAll();
+ }
+ return true;
+ }
+
+ public void notify(String message, Object origin) {
+ if ("Obsolete content type encountered: 'text/javascript'.".equals(message)) {
+ // silently eat warning about text/javascript MIME type
+ return;
+ }
+ treeLogger.log(TreeLogger.WARN, message);
+ }
+
+ @Override
+ public void run() {
+ WebClient webClient = new WebClient(browser);
+ webClient.setAlertHandler(this);
+ // Adding a handler that ignores errors to work-around
+ // https://sourceforge.net/tracker/?func=detail&aid=3090806&group_id=47038&atid=448266
+ webClient.setCssErrorHandler(new ErrorHandler() {
+
+ public void error(CSSParseException exception) {
+ // ignore
+ }
+
+ public void fatalError(CSSParseException exception) {
+ treeLogger.log(TreeLogger.WARN,
+ "CSS fatal error: " + exception.getURI() + " ["
+ + exception.getLineNumber() + ":"
+ + exception.getColumnNumber() + "] " + exception.getMessage());
+ }
+
+ public void warning(CSSParseException exception) {
+ // ignore
+ }
+ });
+ webClient.setIncorrectnessListener(this);
+ webClient.setOnbeforeunloadHandler(this);
+ webClient.setJavaScriptErrorListener(new JavaScriptErrorListener() {
+
+ @Override
+ public void loadScriptError(HtmlPage htmlPage, URL scriptUrl,
+ Exception exception) {
+ treeLogger.log(TreeLogger.ERROR,
+ "Load Script Error: " + exception, exception);
+ }
+
+ @Override
+ public void malformedScriptURL(HtmlPage htmlPage, String url,
+ MalformedURLException malformedURLException) {
+ treeLogger.log(TreeLogger.ERROR,
+ "Malformed Script URL: " + malformedURLException.getLocalizedMessage());
+ }
+
+ @Override
+ public void scriptException(HtmlPage htmlPage,
+ ScriptException scriptException) {
+ treeLogger.log(TreeLogger.DEBUG,
+ "Script Exception: " + scriptException.getLocalizedMessage() +
+ ", line " + scriptException.getFailingLine());
+ }
+
+ @Override
+ public void timeoutError(HtmlPage htmlPage, long allowedTime,
+ long executionTime) {
+ treeLogger.log(TreeLogger.ERROR,
+ "Script Timeout Error " + executionTime + " > " + allowedTime);
+ }
+ });
+ setupWebClient(webClient);
+ try {
+ Page page = webClient.getPage(url);
+ webClient.waitForBackgroundJavaScriptStartingBefore(2000);
+ if (treeLogger.isLoggable(TreeLogger.SPAM)) {
+ treeLogger.log(TreeLogger.SPAM, "getPage returned "
+ + ((HtmlPage) page).asXml());
+ }
+ // TODO(amitmanjhi): call webClient.closeAllWindows()
+ } catch (FailingHttpStatusCodeException e) {
+ treeLogger.log(TreeLogger.ERROR, "HTTP request failed", e);
+ return;
+ } catch (MalformedURLException e) {
+ treeLogger.log(TreeLogger.ERROR, "Bad URL", e);
+ return;
+ } catch (IOException e) {
+ treeLogger.log(TreeLogger.ERROR, "I/O error on HTTP request", e);
+ return;
+ }
+ }
+
+ protected void setupWebClient(WebClient webClient) {
+ if (developmentMode) {
+ JavaScriptEngine hostedEngine = new HostedJavaScriptEngine(webClient,
+ treeLogger);
+ webClient.setJavaScriptEngine(hostedEngine);
+ }
+ }
+ }
+
+ /**
+ * JavaScriptEngine subclass that provides a hook of initializing the
+ * __gwt_HostedModePlugin property on any new window, so it acts just like
+ * Firefox with the XPCOM plugin installed.
+ */
+ private static class HostedJavaScriptEngine extends JavaScriptEngine {
+
+ private static final long serialVersionUID = 3594816610842448691L;
+ private final TreeLogger logger;
+
+ public HostedJavaScriptEngine(WebClient webClient, TreeLogger logger) {
+ super(webClient);
+ this.logger = logger;
+ }
+
+ @Override
+ public void initialize(WebWindow webWindow) {
+ // Hook in the hosted-mode plugin after initializing the JS engine.
+ super.initialize(webWindow);
+ Window window = (Window) webWindow.getScriptObject();
+ window.defineProperty("__gwt_HostedModePlugin",
+ new HostedModePluginObject(this, logger), ScriptableObject.READONLY);
+ }
+ }
+
+ private static final Map<String, BrowserVersion> BROWSER_MAP = createBrowserMap();
+
+ /*
+ * as long as this number is greater than 1, GWTTestCaseTest::testRetry will
+ * pass
+ */
+ private static final int DEFAULT_TRIES = 1;
+
+ private static final Set<Platform> PLATFORMS = ImmutableSet.of(Platform.HtmlUnitBug,
+ Platform.HtmlUnitLayout, Platform.HtmlUnitUnknown);
+
+ /**
+ * Returns the list of browsers Htmlunit emulates as a comma separated string.
+ */
+ static String getBrowserList() {
+ StringBuffer sb = new StringBuffer();
+ for (String str : BROWSER_MAP.keySet()) {
+ sb.append(str);
+ sb.append(",");
+ }
+ if (sb.length() > 1) {
+ return sb.substring(0, sb.length() - 1);
+ }
+ return sb.toString();
+ }
+
+ private static Map<String, BrowserVersion> createBrowserMap() {
+ Map<String, BrowserVersion> browserMap = new HashMap<String, BrowserVersion>();
+ for (BrowserVersion browser : new BrowserVersion[] {
+ BrowserVersion.FIREFOX_3_6, BrowserVersion.INTERNET_EXPLORER_6,
+ BrowserVersion.INTERNET_EXPLORER_7}) {
+ browserMap.put(browser.getNickname(), browser);
+ }
+ return Collections.unmodifiableMap(browserMap);
+ }
+
+ private Set<BrowserVersion> browsers = new HashSet<BrowserVersion>();
+ private boolean developmentMode;
+ private final List<Thread> threads = new ArrayList<Thread>();
+
+ /**
+ * Create a RunStyle instance with the passed-in browser targets.
+ */
+ public RunStyleHtmlUnit(JUnitShell shell) {
+ super(shell);
+ }
+
+ @Override
+ public Set<Platform> getPlatforms() {
+ return PLATFORMS;
+ }
+
+ @Override
+ public int initialize(String args) {
+ if (args == null || args.length() == 0) {
+ // If no browsers specified, default to Firefox 3.
+ args = "FF3.6";
+ }
+ Set<BrowserVersion> browserSet = new HashSet<BrowserVersion>();
+ for (String browserName : args.split(",")) {
+ BrowserVersion browser = BROWSER_MAP.get(browserName);
+ if (browser == null) {
+ getLogger().log(
+ TreeLogger.ERROR,
+ "RunStyleHtmlUnit: Unknown browser " + "name " + browserName
+ + ", expected browser name: one of " + BROWSER_MAP.keySet());
+ return -1;
+ }
+ browserSet.add(browser);
+ }
+ browsers = Collections.unmodifiableSet(browserSet);
+
+ setTries(DEFAULT_TRIES); // set to the default value for this RunStyle
+ return browsers.size();
+ }
+
+ @Override
+ public void launchModule(String moduleName) {
+ for (BrowserVersion browser : browsers) {
+ String url = shell.getModuleUrl(moduleName);
+ HtmlUnitThread hut = createHtmlUnitThread(browser, url);
+ TreeLogger logger = shell.getTopLogger();
+ if (logger.isLoggable(TreeLogger.INFO)) {
+ logger.log(TreeLogger.INFO,
+ "Starting " + url + " on browser " + browser.getNickname());
+ }
+ /*
+ * TODO (amitmanjhi): Is it worth pausing here and waiting for the main
+ * test thread to get to an "okay" state.
+ */
+ hut.start();
+ threads.add(hut);
+ }
+ }
+
+ public int numBrowsers() {
+ return browsers.size();
+ }
+
+ @Override
+ public boolean setupMode(TreeLogger logger, boolean developmentMode) {
+ this.developmentMode = developmentMode;
+ return true;
+ }
+
+ protected HtmlUnitThread createHtmlUnitThread(BrowserVersion browser,
+ String url) {
+ return new HtmlUnitThread(browser, url, shell.getTopLogger().branch(
+ TreeLogger.SPAM, "logging for HtmlUnit thread"), developmentMode);
+ }
+}
diff --git a/gwtquery-core/src/main/java/com/google/gwt/query/client/GQ.java b/gwtquery-core/src/main/java/com/google/gwt/query/client/GQ.java
index 0791feaf..1e6aa304 100644
--- a/gwtquery-core/src/main/java/com/google/gwt/query/client/GQ.java
+++ b/gwtquery-core/src/main/java/com/google/gwt/query/client/GQ.java
@@ -18,19 +18,18 @@ package com.google.gwt.query.client;
import com.google.gwt.core.shared.GWT;
import com.google.gwt.query.client.builders.JsonBuilder;
import com.google.gwt.query.client.builders.JsonFactory;
+import com.google.gwt.query.client.plugins.ajax.Ajax.AjaxTransport;
+import com.google.gwt.query.client.plugins.ajax.AjaxTransportJs;
+import com.google.gwt.query.vm.AjaxTransportJre;
import com.google.gwt.query.vm.JsonFactoryJre;
public class GQ {
private static JsonFactory jsonFactory;
+ private static AjaxTransport ajaxTransport;
public static <T extends JsonBuilder> T create(Class<T> clz) {
- if (jsonFactory == null) {
- jsonFactory = GWT.isClient() ?
- GWT.<JsonFactory>create(JsonFactory.class) :
- new JsonFactoryJre();
- }
- return jsonFactory.create(clz);
+ return getFactory().create(clz);
}
public static <T extends JsonBuilder> T create(Class<T> clz, String payload) {
@@ -38,4 +37,26 @@ public class GQ {
ret.load(payload);
return ret;
}
+
+ public static <T extends Binder> T create(String s) {
+ return getFactory().create(s);
+ }
+
+ public static AjaxTransport getAjaxTransport() {
+ if (ajaxTransport == null) {
+ ajaxTransport = GWT.isClient() ?
+ new AjaxTransportJs() :
+ new AjaxTransportJre();
+ }
+ return ajaxTransport;
+ }
+
+ private static JsonFactory getFactory() {
+ if (jsonFactory == null) {
+ jsonFactory = GWT.isClient() ?
+ GWT.<JsonFactory>create(JsonFactory.class) :
+ new JsonFactoryJre();
+ }
+ return jsonFactory;
+ }
}
diff --git a/gwtquery-core/src/main/java/com/google/gwt/query/client/builders/JsonFactory.java b/gwtquery-core/src/main/java/com/google/gwt/query/client/builders/JsonFactory.java
index 30f08bb9..adcd5212 100644
--- a/gwtquery-core/src/main/java/com/google/gwt/query/client/builders/JsonFactory.java
+++ b/gwtquery-core/src/main/java/com/google/gwt/query/client/builders/JsonFactory.java
@@ -1,5 +1,8 @@
package com.google.gwt.query.client.builders;
+import com.google.gwt.query.client.Binder;
+
public interface JsonFactory {
<T extends JsonBuilder> T create(Class<T> clz);
+ <T extends Binder> T create(String s);
}
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 be06f79b..ac01f7ca 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
@@ -531,7 +531,7 @@ public class JsUtils {
} else {
Properties p = prop.getJavaScriptObject(k);
if (p != null) {
- ret += p.toQueryString();
+ ret += k + "=" + p.tostring();
} else {
String v = prop.getStr(k);
if (v != null && !v.isEmpty() && !"null".equalsIgnoreCase(v)) {
diff --git a/gwtquery-core/src/main/java/com/google/gwt/query/client/plugins/Effects.java b/gwtquery-core/src/main/java/com/google/gwt/query/client/plugins/Effects.java
index c7b66d5b..7f6ce927 100755
--- a/gwtquery-core/src/main/java/com/google/gwt/query/client/plugins/Effects.java
+++ b/gwtquery-core/src/main/java/com/google/gwt/query/client/plugins/Effects.java
@@ -166,7 +166,8 @@ public class Effects extends QueuePlugin<Effects> {
public Effects animate(Object stringOrProperties, int duration, Easing easing, Function... funcs) {
final Properties p = (stringOrProperties instanceof String)
- ? $$((String) stringOrProperties) : (Properties) stringOrProperties;
+ ? (Properties) $$((String) stringOrProperties)
+ : (Properties) stringOrProperties;
if (p.getStr("duration") != null) {
duration = p.getInt("duration");
diff --git a/gwtquery-core/src/main/java/com/google/gwt/query/client/plugins/ajax/Ajax.java b/gwtquery-core/src/main/java/com/google/gwt/query/client/plugins/ajax/Ajax.java
index 9da8e7dd..ecf5b626 100644
--- a/gwtquery-core/src/main/java/com/google/gwt/query/client/plugins/ajax/Ajax.java
+++ b/gwtquery-core/src/main/java/com/google/gwt/query/client/plugins/ajax/Ajax.java
@@ -1,25 +1,20 @@
package com.google.gwt.query.client.plugins.ajax;
-import com.google.gwt.core.client.Callback;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.ScriptInjector;
import com.google.gwt.dom.client.Element;
-import com.google.gwt.dom.client.ScriptElement;
import com.google.gwt.http.client.Request;
import com.google.gwt.http.client.Response;
+import com.google.gwt.query.client.Binder;
import com.google.gwt.query.client.Function;
import com.google.gwt.query.client.GQ;
import com.google.gwt.query.client.GQuery;
import com.google.gwt.query.client.Promise;
import com.google.gwt.query.client.Properties;
-import com.google.gwt.query.client.Binder;
import com.google.gwt.query.client.builders.JsonBuilder;
import com.google.gwt.query.client.js.JsUtils;
import com.google.gwt.query.client.plugins.Plugin;
-import com.google.gwt.query.client.plugins.deferred.PromiseFunction;
-import com.google.gwt.query.client.plugins.deferred.PromiseReqBuilder;
-import com.google.gwt.query.client.plugins.deferred.PromiseReqBuilderJSONP;
/**
* Ajax class for GQuery.
@@ -37,6 +32,17 @@ import com.google.gwt.query.client.plugins.deferred.PromiseReqBuilderJSONP;
*/
public class Ajax extends GQuery {
+ public static interface AjaxTransport {
+
+ Promise getJsonP(Settings settings);
+
+ Promise getLoadScript(Settings settings);
+
+ Promise getXhr(Settings settings);
+ }
+
+ static AjaxTransport transport = GQ.getAjaxTransport();
+
/**
* Ajax Settings object
*/
@@ -109,7 +115,8 @@ public class Ajax extends GQuery {
* @param settings a Properties object with the configuration of the Ajax request.
*/
public static Promise ajax(Settings settings) {
-
+ resolveSettings(settings);
+
final Function onSuccess = settings.getSuccess();
if (onSuccess != null) {
onSuccess.setElement(settings.getContext());
@@ -120,28 +127,26 @@ public class Ajax extends GQuery {
onError.setElement(settings.getContext());
}
- resolveSettings(settings);
-
final String dataType = settings.getDataType();
Promise ret = null;
if ("jsonp".equalsIgnoreCase(dataType)) {
- ret = new PromiseReqBuilderJSONP(settings.getUrl(), null, settings.getTimeout());
+ ret = transport.getJsonP(settings);
} else if ("loadscript".equalsIgnoreCase(dataType)){
- ret = createPromiseScriptInjector(settings.getUrl());
+ ret = transport.getLoadScript(settings);
} else {
- ret = new PromiseReqBuilder(settings)
+ ret = transport.getXhr(settings)
.then(new Function() {
public Object f(Object...args) {
Response response = arguments(0);
Request request = arguments(1);
- Object retData = null;
+ Object retData = response.getText();
try {
if ("xml".equalsIgnoreCase(dataType)) {
retData = JsUtils.parseXML(response.getText());
} else if ("json".equalsIgnoreCase(dataType)) {
- retData = JsUtils.parseJSON(response.getText());
+ retData = GQ.create(response.getText());
} else {
retData = response.getText();
if ("script".equalsIgnoreCase(dataType)) {
@@ -149,17 +154,20 @@ public class Ajax extends GQuery {
}
}
} catch (Exception e) {
- if (GWT.getUncaughtExceptionHandler() != null) {
+ if (GWT.isClient() && GWT.getUncaughtExceptionHandler() != null) {
GWT.getUncaughtExceptionHandler().onUncaughtException(e);
+ } else {
+ e.printStackTrace();
}
}
return new Object[]{retData, "success", request, response};
}
}, new Function() {
public Object f(Object...args) {
- Throwable exception = (Throwable)args[0];
- Request request = (Request)args[1];
- return new Object[]{null, exception.getMessage(), request, null, exception};
+ Throwable exception = getArgument(0, Throwable.class);
+ Request request = getArgument(0, Request.class);
+ String msg = String.valueOf(exception);
+ return new Object[]{null, msg, request, null, exception};
}
});
}
@@ -175,9 +183,16 @@ public class Ajax extends GQuery {
private static void resolveSettings(Settings settings) {
String url = settings.getUrl();
assert settings != null && settings.getUrl() != null: "no url found in settings";
-
- settings.setType(settings.getType() == null ? "POST" : settings.getType().toUpperCase());
+ String type = "POST";
+ if (settings.getType() != null) {
+ type = settings.getType().toUpperCase();
+ }
+ if ("jsonp".equalsIgnoreCase(settings.getDataType())) {
+ type = "GET";
+ }
+ settings.setType(type);
+
Binder data = settings.getData();
if (data != null) {
if (data.getBound() instanceof JavaScriptObject && JsUtils.isFormData(data.<JavaScriptObject>getBound())) {
@@ -195,27 +210,6 @@ public class Ajax extends GQuery {
}
}
- private static Promise createPromiseScriptInjector(final String url) {
- return new PromiseFunction() {
- private ScriptElement scriptElement;
- public void f(final Deferred dfd) {
- scriptElement = ScriptInjector.fromUrl(url).setWindow(window)
- .setCallback(new Callback<Void, Exception>() {
- public void onSuccess(Void result) {
- $(window).delay(0, new Function(){
- public void f() {
- dfd.resolve(scriptElement);
- }
- });
- }
- public void onFailure(Exception reason) {
- dfd.reject(reason);
- }
- }).inject().cast();
- }
- };
- }
-
public static Promise ajax(String url, Function onSuccess, Function onError) {
return ajax(url, onSuccess, onError, (Settings) null);
}
@@ -240,11 +234,14 @@ public class Ajax extends GQuery {
}
public static Settings createSettings() {
- return createSettings($$(""));
+ return createSettings("");
}
public static Settings createSettings(String prop) {
- return createSettings($$(prop));
+ Settings s = GQ.create(Settings.class);
+ if (prop != null && !prop.isEmpty())
+ s.parse(prop);
+ return s;
}
public static Settings createSettings(Binder p) {
@@ -366,7 +363,7 @@ public class Ajax extends GQuery {
// Note: using '\s\S' instead of '.' because gwt String emulation does
// not support java embedded flag expressions (?s) and javascript does
// not have multidot flag.
- String s = getArguments()[0].toString().replaceAll("<![^>]+>\\s*", "")
+ String s = arguments(0).toString().replaceAll("<![^>]+>\\s*", "")
.replaceAll("</?html[\\s\\S]*?>\\s*", "")
.replaceAll("<head[\\s\\S]*?</head>\\s*", "")
.replaceAll("<script[\\s\\S]*?</script>\\s*", "")
diff --git a/gwtquery-core/src/main/java/com/google/gwt/query/client/plugins/ajax/AjaxTransportJs.java b/gwtquery-core/src/main/java/com/google/gwt/query/client/plugins/ajax/AjaxTransportJs.java
index bc7a72ef..fe4e91b8 100644
--- a/gwtquery-core/src/main/java/com/google/gwt/query/client/plugins/ajax/AjaxTransportJs.java
+++ b/gwtquery-core/src/main/java/com/google/gwt/query/client/plugins/ajax/AjaxTransportJs.java
@@ -6,20 +6,24 @@ import com.google.gwt.dom.client.ScriptElement;
import com.google.gwt.query.client.Function;
import com.google.gwt.query.client.GQuery;
import com.google.gwt.query.client.Promise;
+import com.google.gwt.query.client.plugins.ajax.Ajax.AjaxTransport;
import com.google.gwt.query.client.plugins.ajax.Ajax.Settings;
+import com.google.gwt.query.client.plugins.deferred.Deferred.DeferredPromiseImpl;
import com.google.gwt.query.client.plugins.deferred.PromiseFunction;
+import com.google.gwt.query.client.plugins.deferred.PromiseReqBuilder;
import com.google.gwt.query.client.plugins.deferred.PromiseReqBuilderJSONP;
/**
* Ajax transport for Client side.
*/
-public class AjaxTransportJs {
-
+public class AjaxTransportJs implements AjaxTransport {
+ @Override
public Promise getJsonP(Settings settings) {
return new PromiseReqBuilderJSONP(settings.getUrl(), settings.getTimeout());
}
+ @Override
public Promise getLoadScript(final Settings settings) {
return new PromiseFunction() {
private ScriptElement scriptElement;
@@ -40,5 +44,9 @@ public class AjaxTransportJs {
}
};
}
+
+ public Promise getXhr(Settings settings) {
+ return new PromiseReqBuilder(settings);
+ }
}
diff --git a/gwtquery-core/src/main/java/com/google/gwt/query/client/plugins/effects/Transitions.java b/gwtquery-core/src/main/java/com/google/gwt/query/client/plugins/effects/Transitions.java
index 57403501..ac9581cd 100644
--- a/gwtquery-core/src/main/java/com/google/gwt/query/client/plugins/effects/Transitions.java
+++ b/gwtquery-core/src/main/java/com/google/gwt/query/client/plugins/effects/Transitions.java
@@ -311,7 +311,7 @@ public class Transitions extends GQuery {
}
final Properties cssProps = (stringOrProperties instanceof String)
- ? $$((String) stringOrProperties)
+ ? (Properties) $$((String) stringOrProperties)
: (Properties) stringOrProperties;
final String ease = easing == null ? "ease" : easing.toString();
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 2e66bc3f..e1c905af 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
@@ -56,7 +56,7 @@ public class JsonBuilderGenerator extends Generator {
static JClassType jsType;
static JClassType listType;
static JClassType stringType;
- static JClassType jsonCreatorType;
+ static JClassType jsonFactoryType;
public static String capitalize(String s) {
@@ -89,11 +89,11 @@ public class JsonBuilderGenerator extends Generator {
jsType = oracle.findType(JavaScriptObject.class.getName());
listType = oracle.findType(List.class.getName());
functionType = oracle.findType(Function.class.getName());
- jsonCreatorType = oracle.findType(JsonFactory.class.getName());
+ jsonFactoryType = oracle.findType(JsonFactory.class.getName());
String t[] = generateClassName(clazz);
- boolean isFactory = clazz.isAssignableTo(jsonCreatorType);
+ boolean isFactory = clazz.isAssignableTo(jsonFactoryType);
SourceWriter sw = getSourceWriter(treeLogger, generatorContext, t[0], t[1], isFactory, requestedClass);
if (sw != null) {
@@ -323,5 +323,10 @@ public class JsonBuilderGenerator extends Generator {
sw.println("return null;");
sw.outdent();
sw.println("}");
+ sw.println("public <T extends Binder> T create(String s) {");
+ sw.indent();
+ sw.println("return (T)" + Properties.class.getName() + ".create(s);");
+ sw.outdent();
+ sw.println("}");
}
}
diff --git a/gwtquery-core/src/main/java/com/google/gwt/query/vm/AjaxTransportJre.java b/gwtquery-core/src/main/java/com/google/gwt/query/vm/AjaxTransportJre.java
index 7e882d62..822da4c2 100644
--- a/gwtquery-core/src/main/java/com/google/gwt/query/vm/AjaxTransportJre.java
+++ b/gwtquery-core/src/main/java/com/google/gwt/query/vm/AjaxTransportJre.java
@@ -1,8 +1,109 @@
package com.google.gwt.query.vm;
+// import org.apache.http.impl.client.HttpClientBuilder;
+
+import java.io.BufferedReader;
+import java.io.DataOutputStream;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+import com.google.gwt.http.client.RequestException;
+import com.google.gwt.http.client.Response;
+import com.google.gwt.query.client.Binder;
+import com.google.gwt.query.client.Function;
+import com.google.gwt.query.client.GQ;
+import com.google.gwt.query.client.Promise;
+import com.google.gwt.query.client.plugins.ajax.Ajax.AjaxTransport;
+import com.google.gwt.query.client.plugins.ajax.Ajax.Settings;
+import com.google.gwt.query.client.plugins.deferred.PromiseFunction;
+import com.google.gwt.user.server.Base64Utils;
/**
+ *
*/
-public class AjaxTransportJre {
+public class AjaxTransportJre implements AjaxTransport {
+
+ private final String USER_AGENT = "Mozilla/5.0";
+
+ public Promise getJsonP(final Settings settings) {
+ String url = settings.getUrl().replaceFirst("callback=[^&]*", "");
+ url += (url.contains("?") ? "&" : "?") + "callback=jre_callback";
+ settings.setUrl(url);
+
+ return getXhr(settings)
+ .then(new Function() {
+ public Object f(Object... args) {
+ ResponseJre response = arguments(0);
+ return GQ.create(response.getText().replaceFirst("jre_callback\\((.*)\\)", "$1"));
+ }
+ });
+ }
+
+ public Promise getLoadScript(Settings settings) {
+ return getXhr(settings);
+ }
+
+ public Promise getXhr(final Settings settings) {
+ return new PromiseFunction() {
+ public void f(Deferred dfd) {
+ try {
+ Response response = httpClient(settings);
+ int status = response.getStatusCode();
+ if (status <= 0 || status >= 400) {
+ String statusText = status <= 0 ? "Bad CORS" : response.getStatusText();
+ dfd.reject(null, new RequestException("HTTP ERROR: " + status + " " + statusText + "\n" + response.getText()));
+ } else {
+ dfd.resolve(response, null);
+ }
+ } catch (Exception e) {
+ dfd.reject(e, null);
+ }
+ }
+ };
+ }
+
+ private Response httpClient(Settings s) throws Exception {
+
+ URL u = new URL(s.getUrl());
+
+ HttpURLConnection c = (HttpURLConnection) u.openConnection();
+ c.setRequestMethod(s.getType());
+ c.setRequestProperty("User-Agent", USER_AGENT);
+ if (s.getUsername() != null && s.getPassword() != null) {
+ c.setRequestProperty ("Authorization", "Basic " + Base64Utils.toBase64((s.getUsername() + ":" + s.getPassword()).getBytes()));
+ }
+
+ Binder headers = s.getHeaders();
+ if (headers != null) {
+ for (String h : headers.getFieldNames()) {
+ c.setRequestProperty(h, "" + headers.get(h));
+ }
+ }
+
+ if (!s.getType().equals("GET")) {
+ String ctype = s.getDataType();
+ if (s.getDataType().toLowerCase().startsWith("json")) {
+ ctype = "application/json; charset=utf-8";
+ }
+ c.setRequestProperty("Content-Type", ctype);
+
+ c.setDoOutput(true);
+ DataOutputStream wr = new DataOutputStream(c.getOutputStream());
+ wr.writeBytes(s.getDataString());
+ wr.flush();
+ wr.close();
+ }
+ int code = c.getResponseCode();
+ BufferedReader in = new BufferedReader(new InputStreamReader(c.getInputStream()));
+ String inputLine;
+ StringBuffer response = new StringBuffer();
+ while ((inputLine = in.readLine()) != null) {
+ response.append(inputLine);
+ }
+ in.close();
+
+ return new ResponseJre(code, c.getResponseMessage(), response.toString(), c.getHeaderFields());
+ }
}
diff --git a/gwtquery-core/src/main/java/com/google/gwt/query/vm/JsonFactoryJre.java b/gwtquery-core/src/main/java/com/google/gwt/query/vm/JsonFactoryJre.java
index ec115086..b81ca2d4 100644
--- a/gwtquery-core/src/main/java/com/google/gwt/query/vm/JsonFactoryJre.java
+++ b/gwtquery-core/src/main/java/com/google/gwt/query/vm/JsonFactoryJre.java
@@ -14,6 +14,7 @@ import org.json.JSONArray;
import org.json.JSONObject;
import com.google.gwt.query.client.Binder;
+import com.google.gwt.query.client.Properties;
import com.google.gwt.query.client.builders.JsonBuilder;
import com.google.gwt.query.client.builders.JsonFactory;
import com.google.gwt.query.client.builders.Name;
@@ -94,7 +95,7 @@ public class JsonFactoryJre implements JsonFactory {
}
} else {
ret = obj != null ? obj.get(attr): arr.get(idx);
- if (ret instanceof JSONObject && JsonBuilder.class.isAssignableFrom(clz) && !clz.isAssignableFrom(ret.getClass())) {
+ if (ret instanceof JSONObject && Binder.class.isAssignableFrom(clz) && !clz.isAssignableFrom(ret.getClass())) {
ret = jsonFactory.create(clz, (JSONObject)ret);
}
}
@@ -125,8 +126,8 @@ public class JsonFactoryJre implements JsonFactory {
return obj != null ? obj.put(attr, o) : arr.put(o);
} else if (o instanceof Date) {
return obj != null ? obj.put(attr, ((Date) o).getTime()) : arr.put(((Date) o).getTime());
- } else if (o instanceof JsonBuilder) {
- return obj != null ? obj.put(attr, ((JsonBuilder) o).getBound()) : arr.put(((JsonBuilder) o).getBound());
+ } else if (o instanceof Binder) {
+ return obj != null ? obj.put(attr, ((Binder) o).getBound()) : arr.put(((Binder) o).getBound());
} else if (o.getClass().isArray() || o instanceof List) {
Object[] arg;
if (o.getClass().isArray()) {
@@ -166,8 +167,9 @@ public class JsonFactoryJre implements JsonFactory {
} else if (mname.matches("toString")) {
return jsonObject.toString();
} else if (mname.matches("toJson")) {
- String jsonName = JsonBuilderGenerator.classNameToJsonName(getDataBindingClassName(proxy.getClass()));
- return "{\"" + jsonName + "\":"+ jsonObject.toString() + "}";
+// String jsonName = JsonBuilderGenerator.classNameToJsonName(getDataBindingClassName(proxy.getClass()));
+// return "{\"" + jsonName + "\":"+ jsonObject.toString() + "}";
+ return jsonObject.toString() ;
} else if ("toQueryString".equals(mname)) {
return param(jsonObject);
} else if (largs == 1 && mname.equals("get")) {
@@ -237,7 +239,7 @@ public class JsonFactoryJre implements JsonFactory {
}
}
if (p != null) {
- ret += param(p);
+ ret += k + "=" + p.toString();
} else if (s != null) {
if (!s.isEmpty() && !"null".equalsIgnoreCase(s)) {
ret += k + "=" + s;
@@ -265,4 +267,11 @@ public class JsonFactoryJre implements JsonFactory {
InvocationHandler handler = new JsonBuilderHandler();
return (Binder)Proxy.newProxyInstance(Binder.class.getClassLoader(), new Class[] {Binder.class}, handler);
}
+
+ @SuppressWarnings("unchecked")
+ public <T extends Binder> T create(String s) {
+ Binder ret = createBinder();
+ ret.parse(Properties.wrapPropertiesString(s));
+ return (T)ret;
+ }
}
diff --git a/gwtquery-core/src/main/java/com/google/gwt/query/vm/ResponseJre.java b/gwtquery-core/src/main/java/com/google/gwt/query/vm/ResponseJre.java
new file mode 100644
index 00000000..36aafd37
--- /dev/null
+++ b/gwtquery-core/src/main/java/com/google/gwt/query/vm/ResponseJre.java
@@ -0,0 +1,66 @@
+package com.google.gwt.query.vm;
+
+import java.util.List;
+import java.util.Map;
+
+import com.google.gwt.http.client.Header;
+import com.google.gwt.http.client.Response;
+
+/**
+ * Implementation of `Response` in the JVM
+ */
+public class ResponseJre extends Response {
+ private int status;
+ private Map<String,List<String>> headers;
+ private String responseText;
+ private String statusText;
+
+ public ResponseJre(int status, String statusText, String text, Map<String,List<String>> headers) {
+ this.status = status;
+ this.headers = headers;
+ this.responseText = text;
+ this.statusText = statusText;
+ }
+
+ public String getHeader(String header) {
+ List<String> l = headers.get(header);
+ if (l != null && !l.isEmpty()) {
+ String ret = "";
+ for (String s : l) {
+ ret += ret.isEmpty() ? s : (", " + s);
+ }
+ return ret;
+ }
+ return null;
+ }
+
+ public Header[] getHeaders() {
+ Header[] ret = new Header[headers.size()];
+ int i = 0;
+ for (final String s : headers.keySet()) {
+ final String v = getHeader(s);
+ ret[i] = new Header() {
+ public String getValue() {
+ return v;
+ }
+ public String getName() {
+ return s;
+ }
+ };
+ i++;
+ }
+ return ret;
+ }
+ public String getHeadersAsString() {
+ return null;
+ }
+ public int getStatusCode() {
+ return status;
+ }
+ public String getStatusText() {
+ return statusText;
+ }
+ public String getText() {
+ return responseText;
+ }
+} \ No newline at end of file
diff --git a/gwtquery-core/src/main/super/com/google/gwt/query/super/com/google/gwt/query/client/GQ.java b/gwtquery-core/src/main/super/com/google/gwt/query/super/com/google/gwt/query/client/GQ.java
index edad9154..d694afa5 100644
--- a/gwtquery-core/src/main/super/com/google/gwt/query/super/com/google/gwt/query/client/GQ.java
+++ b/gwtquery-core/src/main/super/com/google/gwt/query/super/com/google/gwt/query/client/GQ.java
@@ -16,19 +16,21 @@
package com.google.gwt.query.client;
import com.google.gwt.core.shared.GWT;
+import com.google.gwt.query.client.Binder;
import com.google.gwt.query.client.builders.JsonBuilder;
import com.google.gwt.query.client.builders.JsonFactory;
+import com.google.gwt.query.client.plugins.ajax.AjaxTransportJs;
+import com.google.gwt.query.client.plugins.ajax.Ajax.AjaxTransport;
+import com.google.gwt.query.vm.AjaxTransportJre;
import com.google.gwt.query.vm.JsonFactoryJre;
public class GQ {
private static JsonFactory jsonFactory;
+ private static AjaxTransport ajaxTransport;
public static <T extends JsonBuilder> T create(Class<T> clz) {
- if (jsonFactory == null) {
- jsonFactory = GWT.<JsonFactory>create(JsonFactory.class);
- }
- return jsonFactory.create(clz);
+ return getFactory().create(clz);
}
public static <T extends JsonBuilder> T create(Class<T> clz, String payload) {
@@ -36,4 +38,22 @@ public class GQ {
ret.load(payload);
return ret;
}
+
+ public static <T extends Binder> T create(String s) {
+ return getFactory().create(s);
+ }
+
+ public static AjaxTransport getAjaxTransport() {
+ if (ajaxTransport == null) {
+ ajaxTransport = new AjaxTransportJs();
+ }
+ return ajaxTransport;
+ }
+
+ private static JsonFactory getFactory() {
+ if (jsonFactory == null) {
+ jsonFactory = GWT.<JsonFactory>create(JsonFactory.class);
+ }
+ return jsonFactory;
+ }
}
diff --git a/gwtquery-core/src/test/java/com/google/gwt/dev/shell/BrowserChannel.java b/gwtquery-core/src/test/java/com/google/gwt/dev/shell/BrowserChannel.java
new file mode 100644
index 00000000..9a5a925e
--- /dev/null
+++ b/gwtquery-core/src/test/java/com/google/gwt/dev/shell/BrowserChannel.java
@@ -0,0 +1,1720 @@
+/*
+ * Copyright 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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;
+
+import com.google.gwt.dev.shell.BrowserChannel.SessionHandler.ExceptionOrReturnValue;
+import com.google.gwt.dev.shell.BrowserChannel.SessionHandler.SpecialDispatchId;
+import com.google.gwt.dev.shell.BrowserChannel.Value.ValueType;
+import com.google.gwt.util.tools.Utility;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.Socket;
+import java.util.Set;
+
+/**
+ *
+ */
+public abstract class BrowserChannel {
+
+ /**
+ * An error indicating that the remote side died and we should unroll the
+ * call stack as painlessly as possible to allow cleanup.
+ */
+ public static class RemoteDeathError extends Error {
+
+ public RemoteDeathError(Throwable cause) {
+ super("Remote connection lost", cause);
+ }
+ }
+
+ /**
+ * Class representing a reference to a Java object.
+ */
+ public static class JavaObjectRef implements RemoteObjectRef {
+ private int refId;
+
+ public JavaObjectRef(int refId) {
+ this.refId = refId;
+ }
+
+ public int getRefid() {
+ return Math.abs(refId);
+ }
+
+ @Override
+ public int hashCode() {
+ return refId;
+ }
+
+ public boolean isException() {
+ return refId < 0;
+ }
+
+ @Override
+ public String toString() {
+ return "JavaObjectRef(ref=" + refId + ")";
+ }
+ }
+
+ /**
+ * Class representing a reference to a JS object.
+ */
+ public static class JsObjectRef implements RemoteObjectRef {
+
+ private int refId;
+
+ public JsObjectRef(int refId) {
+ this.refId = refId;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return (o instanceof JsObjectRef) && ((JsObjectRef) o).refId == refId;
+ }
+
+ public int getRefid() {
+ // exceptions are negative, so we get the absolute value
+ return Math.abs(refId);
+ }
+
+ @Override
+ public int hashCode() {
+ return refId;
+ }
+
+ public boolean isException() {
+ return refId < 0;
+ }
+
+ @Override
+ public String toString() {
+ return "JsObjectRef(" + refId + ")";
+ }
+ }
+
+ /**
+ * Enumeration of message type ids.
+ *
+ * <p>Ids are used instead of relying on the ordinal to avoid sychronization
+ * problems with the client.
+ */
+ public enum MessageType {
+ /**
+ * A message to invoke a method on the other side of the wire. Note that
+ * the messages are asymmetric -- see {@link InvokeOnClientMessage} and
+ * {@link InvokeOnServerMessage}.
+ */
+ INVOKE(0),
+
+ /**
+ * Returns the result of an INVOKE, INVOKE_SPECIAL, or LOAD_MODULE message.
+ */
+ RETURN(1),
+
+ /**
+ * v1 LOAD_MODULE message.
+ */
+ OLD_LOAD_MODULE(2),
+
+ /**
+ * Normal closure of the connection.
+ */
+ QUIT(3),
+
+ /**
+ * A request by the server to load JSNI source into the client's JS engine.
+ */
+ LOAD_JSNI(4),
+
+ INVOKE_SPECIAL(5),
+
+ FREE_VALUE(6),
+
+ /**
+ * Abnormal termination of the connection.
+ */
+ FATAL_ERROR(7),
+
+ CHECK_VERSIONS(8),
+
+ PROTOCOL_VERSION(9),
+
+ CHOOSE_TRANSPORT(10),
+
+ SWITCH_TRANSPORT(11),
+
+ LOAD_MODULE(12),
+
+ REQUEST_ICON(13),
+
+ USER_AGENT_ICON(14),
+
+ REQUEST_PLUGIN(15);
+
+ private final int id;
+
+ private MessageType(int id) {
+ this.id = id;
+ }
+
+ public int getId() {
+ return id;
+ }
+ }
+
+ /**
+ * Represents an object on the other side of the channel, known to this side
+ * by an reference ID.
+ */
+ public interface RemoteObjectRef {
+
+ /**
+ * @return the reference ID for this object.
+ */
+ int getRefid();
+ }
+
+ /**
+ * Hook interface for responding to messages.
+ */
+ public abstract static class SessionHandler<T extends BrowserChannel> {
+
+ /**
+ * Wrapper to return both a return value/exception and a flag as to whether
+ * an exception was thrown or not.
+ */
+ public static class ExceptionOrReturnValue {
+ private final boolean isException;
+ private final Value returnValue;
+
+ public ExceptionOrReturnValue(boolean isException, Value returnValue) {
+ this.isException = isException;
+ this.returnValue = returnValue;
+ }
+
+ public Value getReturnValue() {
+ return returnValue;
+ }
+
+ public boolean isException() {
+ return isException;
+ }
+ }
+
+ /**
+ * Enumeration of dispatch IDs on object 0 (the ServerMethods object).
+ *
+ * <p>Ids are set specifically rather than relying on the ordinal to avoid
+ * synchronization problems with the client.
+ *
+ * TODO: hasMethod/hasProperty no longer used, remove them!
+ */
+ public enum SpecialDispatchId {
+ HasMethod(0), HasProperty(1), GetProperty(2), SetProperty(3);
+
+ private final int id;
+
+ private SpecialDispatchId(int id) {
+ this.id = id;
+ }
+
+ public int getId() {
+ return id;
+ }
+ }
+
+ public abstract void freeValue(T channel, int[] ids);
+ }
+
+ /**
+ * Represents a value for BrowserChannel.
+ */
+ public static class Value {
+ /**
+ * Enum of type tags sent across the wire.
+ */
+ public enum ValueType {
+ /**
+ * Primitive values.
+ */
+ NULL(0), BOOLEAN(1), BYTE(2), CHAR(3), SHORT(4), INT(5), LONG(6),
+ FLOAT(7), DOUBLE(8), STRING(9),
+
+ /**
+ * Representations of Java or JS objects, sent as an index into a table
+ * kept on the side holding the actual object.
+ */
+ JAVA_OBJECT(10), JS_OBJECT(11),
+
+ /**
+ * A Javascript undef value, also used for void returns.
+ */
+ UNDEFINED(12);
+
+ private final int id;
+
+ private ValueType(int id) {
+ this.id = id;
+ }
+
+ byte getTag() {
+ return (byte) id;
+ }
+ }
+
+ /**
+ * Type tag value.
+ */
+ private ValueType type = ValueType.UNDEFINED;
+
+ /**
+ * Represents a value sent/received across the wire.
+ */
+ private Object value = null;
+
+ public Value() {
+ }
+
+ public Value(Object obj) {
+ convertFromJavaValue(obj);
+ }
+
+ /**
+ * Convert a Java object to a value. Objects must be primitive wrappers,
+ * Strings, or JsObjectRef/JavaObjectRef instances.
+ *
+ * @param obj value to convert.
+ */
+ public void convertFromJavaValue(Object obj) {
+ if (obj == null) {
+ type = ValueType.NULL;
+ } else if (obj instanceof Boolean) {
+ type = ValueType.BOOLEAN;
+ } else if (obj instanceof Byte) {
+ type = ValueType.BYTE;
+ } else if (obj instanceof Character) {
+ type = ValueType.CHAR;
+ } else if (obj instanceof Double) {
+ type = ValueType.DOUBLE;
+ } else if (obj instanceof Float) {
+ type = ValueType.FLOAT;
+ } else if (obj instanceof Integer) {
+ type = ValueType.INT;
+ } else if (obj instanceof Long) {
+ type = ValueType.LONG;
+ } else if (obj instanceof Short) {
+ type = ValueType.SHORT;
+ } else if (obj instanceof String) {
+ type = ValueType.STRING;
+ } else if (obj instanceof JsObjectRef) {
+ // TODO: exception handling?
+ type = ValueType.JS_OBJECT;
+ } else if (obj instanceof JavaObjectRef) {
+ // TODO: exception handling?
+ type = ValueType.JAVA_OBJECT;
+ } else {
+ type = ValueType.STRING;
+ obj = String.valueOf(obj);
+// throw new RuntimeException(
+// "Unexpected Java type in convertFromJavaValue: " + obj.getClass() + " " + obj);
+ }
+ value = obj;
+ }
+
+ /**
+ * Convert a value to the requested Java type.
+ *
+ * @param reqType type to convert to
+ * @return value as that type.
+ */
+ public Object convertToJavaType(Class<?> reqType) {
+ if (reqType.isArray()) {
+ // TODO(jat): handle arrays?
+ }
+ if (reqType.equals(Boolean.class)) {
+ assert type == ValueType.BOOLEAN;
+ return value;
+ } else if (reqType.equals(Byte.class) || reqType.equals(byte.class)) {
+ assert isNumber();
+ return Byte.valueOf(((Number) value).byteValue());
+ } else if (reqType.equals(Character.class) || reqType.equals(char.class)) {
+ if (type == ValueType.CHAR) {
+ return value;
+ } else {
+ assert isNumber();
+ return Character.valueOf((char) ((Number) value).shortValue());
+ }
+ } else if (reqType.equals(Double.class) || reqType.equals(double.class)) {
+ assert isNumber();
+ return Double.valueOf(((Number) value).doubleValue());
+ } else if (reqType.equals(Float.class) || reqType.equals(float.class)) {
+ assert isNumber();
+ return Float.valueOf(((Number) value).floatValue());
+ } else if (reqType.equals(Integer.class) || reqType.equals(int.class)) {
+ assert isNumber();
+ return Integer.valueOf(((Number) value).intValue());
+ } else if (reqType.equals(Long.class) || reqType.equals(long.class)) {
+ assert isNumber();
+ return Long.valueOf(((Number) value).longValue());
+ } else if (reqType.equals(Short.class) || reqType.equals(short.class)) {
+ assert isNumber();
+ return Short.valueOf(((Number) value).shortValue());
+ } else if (reqType.equals(String.class)) {
+ assert type == ValueType.STRING;
+ return value;
+ } else {
+ // Wants an object, caller must deal with object references.
+ return value;
+ }
+ }
+
+ public boolean getBoolean() {
+ assert type == ValueType.BOOLEAN;
+ return ((Boolean) value).booleanValue();
+ }
+
+ public byte getByte() {
+ assert type == ValueType.BYTE;
+ return ((Byte) value).byteValue();
+ }
+
+ public char getChar() {
+ assert type == ValueType.CHAR;
+ return ((Character) value).charValue();
+ }
+
+ public double getDouble() {
+ assert type == ValueType.DOUBLE;
+ return ((Double) value).doubleValue();
+ }
+
+ public float getFloat() {
+ assert type == ValueType.FLOAT;
+ return ((Float) value).floatValue();
+ }
+
+ public int getInt() {
+ assert type == ValueType.INT;
+ return ((Integer) value).intValue();
+ }
+
+ public JavaObjectRef getJavaObject() {
+ assert type == ValueType.JAVA_OBJECT;
+ return (JavaObjectRef) value;
+ }
+
+ public JsObjectRef getJsObject() {
+ assert type == ValueType.JS_OBJECT;
+ return (JsObjectRef) value;
+ }
+
+ public long getLong() {
+ assert type == ValueType.LONG;
+ return ((Long) value).longValue();
+ }
+
+ public short getShort() {
+ assert type == ValueType.SHORT;
+ return ((Short) value).shortValue();
+ }
+
+ public String getString() {
+ assert type == ValueType.STRING;
+ return (String) value;
+ }
+
+ public ValueType getType() {
+ return type;
+ }
+
+ public Object getValue() {
+ return value;
+ }
+
+ public boolean isBoolean() {
+ return type == ValueType.BOOLEAN;
+ }
+
+ public boolean isByte() {
+ return type == ValueType.BYTE;
+ }
+
+ public boolean isChar() {
+ return type == ValueType.CHAR;
+ }
+
+ public boolean isDouble() {
+ return type == ValueType.DOUBLE;
+ }
+
+ public boolean isFloat() {
+ return type == ValueType.FLOAT;
+ }
+
+ public boolean isInt() {
+ return type == ValueType.INT;
+ }
+
+ public boolean isJavaObject() {
+ return type == ValueType.JAVA_OBJECT;
+ }
+
+ public boolean isJsObject() {
+ return type == ValueType.JS_OBJECT;
+ }
+
+ public boolean isLong() {
+ return type == ValueType.LONG;
+ }
+
+ public boolean isNull() {
+ return type == ValueType.NULL;
+ }
+
+ public boolean isNumber() {
+ switch (type) {
+ case BYTE:
+ case CHAR:
+ case DOUBLE:
+ case FLOAT:
+ case INT:
+ case LONG:
+ case SHORT:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ public boolean isPrimitive() {
+ switch (type) {
+ case BOOLEAN:
+ case BYTE:
+ case CHAR:
+ case DOUBLE:
+ case FLOAT:
+ case INT:
+ case LONG:
+ case SHORT:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ public boolean isShort() {
+ return type == ValueType.SHORT;
+ }
+
+ public boolean isString() {
+ return type == ValueType.STRING;
+ }
+
+ public boolean isUndefined() {
+ return type == ValueType.UNDEFINED;
+ }
+
+ public void setBoolean(boolean val) {
+ type = ValueType.BOOLEAN;
+ value = Boolean.valueOf(val);
+ }
+
+ public void setByte(byte val) {
+ type = ValueType.BYTE;
+ value = Byte.valueOf(val);
+ }
+
+ public void setChar(char val) {
+ type = ValueType.CHAR;
+ value = Character.valueOf(val);
+ }
+
+ public void setDouble(double val) {
+ type = ValueType.DOUBLE;
+ value = Double.valueOf(val);
+ }
+
+ public void setFloat(float val) {
+ type = ValueType.FLOAT;
+ value = Float.valueOf(val);
+ }
+
+ public void setInt(int val) {
+ type = ValueType.INT;
+ value = Integer.valueOf(val);
+ }
+
+ public void setJavaObject(JavaObjectRef val) {
+ type = ValueType.JAVA_OBJECT;
+ value = val;
+ }
+
+ public void setJsObject(JsObjectRef val) {
+ type = ValueType.JS_OBJECT;
+ value = val;
+ }
+
+ public void setLong(long val) {
+ type = ValueType.BOOLEAN;
+ value = Long.valueOf(val);
+ }
+
+ public void setNull() {
+ type = ValueType.NULL;
+ value = null;
+ }
+
+ public void setShort(short val) {
+ type = ValueType.SHORT;
+ value = Short.valueOf(val);
+ }
+
+ public void setString(String val) {
+ type = ValueType.STRING;
+ value = val;
+ }
+
+ public void setUndefined() {
+ type = ValueType.UNDEFINED;
+ value = null;
+ }
+
+ @Override
+ public String toString() {
+ return type + ": " + value;
+ }
+ }
+
+ /**
+ * The initial request from the client, supplies a range of supported versions
+ * and the version from hosted.html (so stale copies on an external server
+ * can be detected).
+ */
+ protected static class CheckVersionsMessage extends Message {
+
+ public static CheckVersionsMessage receive(BrowserChannel channel)
+ throws IOException {
+ DataInputStream stream = channel.getStreamFromOtherSide();
+ int minVersion = stream.readInt();
+ int maxVersion = stream.readInt();
+ String hostedHtmlVersion = readUtf8String(stream);
+ return new CheckVersionsMessage(channel, minVersion, maxVersion,
+ hostedHtmlVersion);
+ }
+
+ private final String hostedHtmlVersion;
+
+ private final int maxVersion;
+
+ private final int minVersion;
+
+ public CheckVersionsMessage(BrowserChannel channel, int minVersion,
+ int maxVersion, String hostedHtmlVersion) {
+ super(channel);
+ this.minVersion = minVersion;
+ this.maxVersion = maxVersion;
+ this.hostedHtmlVersion = hostedHtmlVersion;
+ }
+
+ public String getHostedHtmlVersion() {
+ return hostedHtmlVersion;
+ }
+
+ public int getMaxVersion() {
+ return maxVersion;
+ }
+
+ public int getMinVersion() {
+ return minVersion;
+ }
+
+ @Override
+ public void send() throws IOException {
+ DataOutputStream stream = getBrowserChannel().getStreamToOtherSide();
+ stream.writeByte(MessageType.CHECK_VERSIONS.getId());
+ stream.writeInt(minVersion);
+ stream.writeInt(maxVersion);
+ writeUtf8String(stream, hostedHtmlVersion);
+ stream.flush();
+ }
+ }
+
+ /**
+ * A message from the client giving a list of supported connection methods
+ * and requesting the server choose one of them to switch protocol traffic to.
+ */
+ protected static class ChooseTransportMessage extends Message {
+
+ public static ChooseTransportMessage receive(BrowserChannel channel)
+ throws IOException {
+ DataInputStream stream = channel.getStreamFromOtherSide();
+ int n = stream.readInt();
+ String[] transports = new String[n];
+ for (int i = 0; i < n; ++i) {
+ transports[i] = readUtf8String(stream);
+ }
+ return new ChooseTransportMessage(channel, transports);
+ }
+
+ private final String[] transports;
+
+ public ChooseTransportMessage(BrowserChannel channel,
+ String[] transports) {
+ super(channel);
+ this.transports = transports;
+ }
+
+ public String[] getTransports() {
+ return transports;
+ }
+
+ @Override
+ public void send() throws IOException {
+ DataOutputStream stream = getBrowserChannel().getStreamToOtherSide();
+ stream.writeByte(MessageType.CHOOSE_TRANSPORT.getId());
+ stream.writeInt(transports.length);
+ for (String transport : transports) {
+ writeUtf8String(stream, transport);
+ }
+ }
+ }
+
+ /**
+ * A message reporting a connection error to the client.
+ */
+ protected static class FatalErrorMessage extends Message {
+
+ public static FatalErrorMessage receive(BrowserChannel channel)
+ throws IOException {
+ DataInputStream stream = channel.getStreamFromOtherSide();
+ // NOTE: Tag has already been read.
+ String error = readUtf8String(stream);
+ return new FatalErrorMessage(channel, error);
+ }
+
+ private final String error;
+
+ public FatalErrorMessage(BrowserChannel channel, String error) {
+ super(channel);
+ this.error = error;
+ }
+
+ public String getError() {
+ return error;
+ }
+
+ @Override
+ public void send() throws IOException {
+ DataOutputStream stream = getBrowserChannel().getStreamToOtherSide();
+ stream.writeByte(MessageType.FATAL_ERROR.getId());
+ writeUtf8String(stream, error);
+ }
+ }
+
+ /**
+ * A message asking the other side to free object references. Note that there
+ * is no response to this message, and this must only be sent immediately
+ * before an Invoke or Return message.
+ */
+ protected static class FreeMessage extends Message {
+ public static FreeMessage receive(BrowserChannel channel)
+ throws IOException {
+ DataInputStream stream = channel.getStreamFromOtherSide();
+ int numIds = stream.readInt();
+ // TODO: sanity check id count
+ int ids[] = new int[numIds];
+ for (int i = 0; i < numIds; ++i) {
+ ids[i] = stream.readInt();
+ }
+ return new FreeMessage(channel, ids);
+ }
+
+ public static void send(BrowserChannel channel, int[] ids)
+ throws IOException {
+ DataOutputStream stream = channel.getStreamToOtherSide();
+ stream.writeByte(MessageType.FREE_VALUE.getId());
+ stream.writeInt(ids.length);
+ for (int id : ids) {
+ stream.writeInt(id);
+ }
+ stream.flush();
+ }
+
+ private final int ids[];
+
+ public FreeMessage(BrowserChannel channel, int[] ids) {
+ super(channel);
+ this.ids = ids;
+ }
+
+ public int[] getIds() {
+ return ids;
+ }
+
+ @Override
+ public boolean isAsynchronous() {
+ return true;
+ }
+
+ @Override
+ public void send() throws IOException {
+ send(getBrowserChannel(), ids);
+ }
+ }
+
+ /**
+ * A request from the server to invoke a function on the client.
+ *
+ * Note that MessageType.INVOKE can refer to either this class
+ * or {@link InvokeOnServerMessage} depending on the direction, as the
+ * protocol is asymmetric (Java needs a dispatch ID, Javascript needs a
+ * name).
+ */
+ protected static class InvokeOnClientMessage extends Message {
+ public static InvokeOnClientMessage receive(BrowserChannel channel)
+ throws IOException {
+ DataInputStream stream = channel.getStreamFromOtherSide();
+ // NOTE: Tag has already been read.
+ String methodName = readUtf8String(stream);
+ Value thisRef = channel.readValue(stream);
+ int argLen = stream.readInt();
+ Value[] args = new Value[argLen];
+ for (int i = 0; i < argLen; i++) {
+ args[i] = channel.readValue(stream);
+ }
+ return new InvokeOnClientMessage(channel, methodName, thisRef, args);
+ }
+
+ private final Value[] args;
+ private final String methodName;
+ private final Value thisRef;
+
+ public InvokeOnClientMessage(BrowserChannel channel, String methodName,
+ Value thisRef, Value[] args) {
+ super(channel);
+ this.thisRef = thisRef;
+ this.methodName = methodName;
+ this.args = args;
+ }
+
+ public Value[] getArgs() {
+ return args;
+ }
+
+ public String getMethodName() {
+ return methodName;
+ }
+
+ public Value getThis() {
+ return thisRef;
+ }
+
+ @Override
+ public void send() throws IOException {
+ final DataOutputStream stream = getBrowserChannel().getStreamToOtherSide();
+
+ stream.writeByte(MessageType.INVOKE.getId());
+ writeUtf8String(stream, methodName);
+ getBrowserChannel().writeValue(stream, thisRef);
+ stream.writeInt(args.length);
+ for (int i = 0; i < args.length; i++) {
+ getBrowserChannel().writeValue(stream, args[i]);
+ }
+ stream.flush();
+ }
+ }
+
+ /**
+ * A request from the client to invoke a function on the server.
+ *
+ * Note that MessageType.INVOKE can refer to either this class
+ * or {@link InvokeOnClientMessage} depending on the direction, as the
+ * protocol is asymmetric (Java needs a dispatch ID, Javascript needs a
+ * name).
+ */
+ protected static class InvokeOnServerMessage extends Message {
+ public static InvokeOnServerMessage receive(BrowserChannel channel)
+ throws IOException {
+ DataInputStream stream = channel.getStreamFromOtherSide();
+ // NOTE: Tag has already been read.
+ int methodDispatchId = stream.readInt();
+ Value thisRef = channel.readValue(stream);
+ int argLen = stream.readInt();
+ Value[] args = new Value[argLen];
+ for (int i = 0; i < argLen; i++) {
+ args[i] = channel.readValue(stream);
+ }
+ return new InvokeOnServerMessage(channel, methodDispatchId, thisRef,
+ args);
+ }
+
+ private final Value[] args;
+ private final int methodDispatchId;
+ private final Value thisRef;
+
+ public InvokeOnServerMessage(BrowserChannel channel, int methodDispatchId,
+ Value thisRef, Value[] args) {
+ super(channel);
+ this.thisRef = thisRef;
+ this.methodDispatchId = methodDispatchId;
+ this.args = args;
+ }
+
+ public Value[] getArgs() {
+ return args;
+ }
+
+ public int getMethodDispatchId() {
+ return methodDispatchId;
+ }
+
+ public Value getThis() {
+ return thisRef;
+ }
+
+ @Override
+ public void send() throws IOException {
+ final DataOutputStream stream = getBrowserChannel().getStreamToOtherSide();
+
+ stream.writeByte(MessageType.INVOKE.getId());
+ stream.writeInt(methodDispatchId);
+ getBrowserChannel().writeValue(stream, thisRef);
+ stream.writeInt(args.length);
+ for (int i = 0; i < args.length; i++) {
+ getBrowserChannel().writeValue(stream, args[i]);
+ }
+ stream.flush();
+ }
+ }
+
+ /**
+ * A request from the to invoke a function on the other side.
+ */
+ protected static class InvokeSpecialMessage extends Message {
+ public static InvokeSpecialMessage receive(BrowserChannel channel)
+ throws IOException, BrowserChannelException {
+ final DataInputStream stream = channel.getStreamFromOtherSide();
+ // NOTE: Tag has already been read.
+ final int specialMethodInt = stream.readByte();
+ SpecialDispatchId[] ids = SpecialDispatchId.values();
+ if (specialMethodInt < 0 || specialMethodInt >= ids.length) {
+ throw new BrowserChannelException("Invalid dispatch id "
+ + specialMethodInt);
+ }
+ final SpecialDispatchId dispatchId = ids[specialMethodInt];
+ final int argLen = stream.readInt();
+ final Value[] args = new Value[argLen];
+ for (int i = 0; i < argLen; i++) {
+ args[i] = channel.readValue(stream);
+ }
+ return new InvokeSpecialMessage(channel, dispatchId, args);
+ }
+
+ private final Value[] args;
+ private final SpecialDispatchId dispatchId;
+
+ public InvokeSpecialMessage(BrowserChannel channel,
+ SpecialDispatchId dispatchId, Value[] args) {
+ super(channel);
+ this.dispatchId = dispatchId;
+ this.args = args;
+ }
+
+ public Value[] getArgs() {
+ return args;
+ }
+
+ public SpecialDispatchId getDispatchId() {
+ return dispatchId;
+ }
+
+ @Override
+ public void send() throws IOException {
+ final DataOutputStream stream = getBrowserChannel().getStreamToOtherSide();
+
+ stream.writeByte(MessageType.INVOKE_SPECIAL.getId());
+ stream.writeByte(dispatchId.getId());
+ stream.writeInt(args.length);
+ for (int i = 0; i < args.length; i++) {
+ getBrowserChannel().writeValue(stream, args[i]);
+ }
+ stream.flush();
+ }
+ }
+
+ /**
+ * A message sending JSNI code to be evaluated. Note that there is no response
+ * to this message, and this must only be sent immediately before an Invoke or
+ * Return message.
+ */
+ protected static class LoadJsniMessage extends Message {
+ public static LoadJsniMessage receive(BrowserChannel channel)
+ throws IOException {
+ DataInputStream stream = channel.getStreamFromOtherSide();
+ String js = readUtf8String(stream);
+ return new LoadJsniMessage(channel, js);
+ }
+
+ public static void send(BrowserChannel channel, String js)
+ throws IOException {
+ DataOutputStream stream = channel.getStreamToOtherSide();
+ stream.write(MessageType.LOAD_JSNI.getId());
+ writeUtf8String(stream, js);
+ stream.flush();
+ }
+
+ private final String js;
+
+ public LoadJsniMessage(BrowserChannel channel, String js) {
+ super(channel);
+ this.js = js;
+ }
+
+ public String getJsni() {
+ return js;
+ }
+
+ @Override
+ public boolean isAsynchronous() {
+ return true;
+ }
+
+ @Override
+ public void send() throws IOException {
+ send(getBrowserChannel(), js);
+ }
+ }
+
+ /**
+ * A request from the client that the server load and initialize a given
+ * module.
+ */
+ protected static class LoadModuleMessage extends Message {
+ public static LoadModuleMessage receive(BrowserChannel channel)
+ throws IOException {
+ DataInputStream stream = channel.getStreamFromOtherSide();
+ String url = readUtf8String(stream);
+ String tabKey = readUtf8String(stream);
+ String sessionKey = readUtf8String(stream);
+ String moduleName = readUtf8String(stream);
+ String userAgent = readUtf8String(stream);
+ return new LoadModuleMessage(channel, url, tabKey, sessionKey, moduleName,
+ userAgent);
+ }
+
+ private final String moduleName;
+
+ private final String sessionKey;
+
+ private final String tabKey;
+
+ private final String url;
+
+ private final String userAgent;
+
+ /**
+ * Creates a LoadModule message to be sent to the server.
+ *
+ * @param channel BrowserChannel instance
+ * @param url URL of main top-level window - may not be null
+ * @param tabKey opaque key identifying the tab in the browser, or an
+ * empty string if it cannot be determined - may not be null
+ * @param sessionKey opaque key identifying a particular session (ie,
+ * group of modules) - may not be null
+ * @param moduleName name of GWT module to load - may not be null
+ * @param userAgent user agent identifier of the browser - may not be null
+ */
+ public LoadModuleMessage(BrowserChannel channel, String url,
+ String tabKey, String sessionKey, String moduleName, String userAgent) {
+ super(channel);
+ assert url != null;
+ assert tabKey != null;
+ assert sessionKey != null;
+ assert moduleName != null;
+ assert userAgent != null;
+ this.url = url;
+ this.tabKey = tabKey;
+ this.sessionKey = sessionKey;
+ this.moduleName = moduleName;
+ this.userAgent = userAgent;
+ }
+
+ public String getModuleName() {
+ return moduleName;
+ }
+
+ public String getSessionKey() {
+ return sessionKey;
+ }
+
+ public String getTabKey() {
+ return tabKey;
+ }
+
+ public String getUrl() {
+ return url;
+ }
+
+ public String getUserAgent() {
+ return userAgent;
+ }
+
+ @Override
+ public void send() throws IOException {
+ DataOutputStream stream = getBrowserChannel().getStreamToOtherSide();
+ stream.writeByte(MessageType.LOAD_MODULE.getId());
+ writeUtf8String(stream, url);
+ writeUtf8String(stream, tabKey);
+ writeUtf8String(stream, sessionKey);
+ writeUtf8String(stream, moduleName);
+ writeUtf8String(stream, userAgent);
+ stream.flush();
+ }
+ }
+
+ /**
+ * Abstract base class of OOPHM messages.
+ */
+ protected abstract static class Message {
+ public static MessageType readMessageType(DataInputStream stream)
+ throws IOException, BrowserChannelException {
+ stream.mark(1);
+ int type = stream.readByte();
+ MessageType[] types = MessageType.values();
+ if (type < 0 || type >= types.length) {
+ stream.reset();
+ throw new BrowserChannelException("Invalid message type " + type);
+ }
+ return types[type];
+ }
+
+ private final BrowserChannel channel;
+
+ public Message(BrowserChannel channel) {
+ this.channel = channel;
+ }
+
+ public final BrowserChannel getBrowserChannel() {
+ return channel;
+ }
+
+ /**
+ * @return true if this message type is asynchronous and does not expect a
+ * return message.
+ */
+ public boolean isAsynchronous() {
+ return false;
+ }
+
+ /**
+ * @throws IOException if a subclass encounters an I/O error
+ */
+ public void send() throws IOException {
+ throw new UnsupportedOperationException(getClass().getName()
+ + " is a message format that can only be received.");
+ }
+ }
+
+ /**
+ * Provides a way of allocating JS and Java object ids without knowing
+ * which one is the remote type, so code can be shared between client and
+ * server.
+ */
+ protected interface ObjectRefFactory {
+
+ JavaObjectRef getJavaObjectRef(int refId);
+
+ JsObjectRef getJsObjectRef(int refId);
+
+ Set<Integer> getRefIdsForCleanup();
+ }
+
+ /**
+ * A request from the client that the server load and initialize a given
+ * module (original v1 version).
+ */
+ protected static class OldLoadModuleMessage extends Message {
+ public static OldLoadModuleMessage receive(BrowserChannel channel)
+ throws IOException {
+ DataInputStream stream = channel.getStreamFromOtherSide();
+ int protoVersion = stream.readInt();
+ String moduleName = readUtf8String(stream);
+ String userAgent = readUtf8String(stream);
+ return new OldLoadModuleMessage(channel, protoVersion, moduleName,
+ userAgent);
+ }
+
+ private final String moduleName;
+
+ private final int protoVersion;
+
+ private final String userAgent;
+
+ public OldLoadModuleMessage(BrowserChannel channel, int protoVersion,
+ String moduleName, String userAgent) {
+ super(channel);
+ this.protoVersion = protoVersion;
+ this.moduleName = moduleName;
+ this.userAgent = userAgent;
+ }
+
+ public String getModuleName() {
+ return moduleName;
+ }
+
+ public int getProtoVersion() {
+ return protoVersion;
+ }
+
+ public String getUserAgent() {
+ return userAgent;
+ }
+
+ @Override
+ public void send() throws IOException {
+ DataOutputStream stream = getBrowserChannel().getStreamToOtherSide();
+ stream.writeByte(MessageType.OLD_LOAD_MODULE.getId());
+ stream.writeInt(protoVersion);
+ writeUtf8String(stream, moduleName);
+ writeUtf8String(stream, userAgent);
+ stream.flush();
+ }
+ }
+
+ /**
+ * Reports the selected protocol version.
+ */
+ protected static class ProtocolVersionMessage extends Message {
+
+ public static ProtocolVersionMessage receive(BrowserChannel channel)
+ throws IOException {
+ DataInputStream stream = channel.getStreamFromOtherSide();
+ int protocolVersion = stream.readInt();
+ return new ProtocolVersionMessage(channel, protocolVersion);
+ }
+
+ private final int protocolVersion;
+
+ public ProtocolVersionMessage(BrowserChannel channel, int protocolVersion) {
+ super(channel);
+ this.protocolVersion = protocolVersion;
+ }
+
+ public int getProtocolVersion() {
+ return protocolVersion;
+ }
+
+ @Override
+ public void send() throws IOException {
+ DataOutputStream stream = getBrowserChannel().getStreamToOtherSide();
+ stream.writeByte(MessageType.PROTOCOL_VERSION.getId());
+ stream.writeInt(protocolVersion);
+ stream.flush();
+ }
+ }
+
+ /**
+ * A message signifying a soft close of the communications channel.
+ */
+ protected static class QuitMessage extends Message {
+ public static QuitMessage receive(BrowserChannel channel) {
+ return new QuitMessage(channel);
+ }
+
+ public static void send(BrowserChannel channel) throws IOException {
+ final DataOutputStream stream = channel.getStreamToOtherSide();
+ stream.writeByte(MessageType.QUIT.getId());
+ stream.flush();
+ }
+
+ public QuitMessage(BrowserChannel channel) {
+ super(channel);
+ }
+
+ @Override
+ public void send() throws IOException {
+ send(getBrowserChannel());
+ }
+ }
+
+ /**
+ * A message asking the client to send an icon suitable for use in the UI.
+ * <p>See {@link UserAgentIconMessage}.
+ */
+ protected static class RequestIconMessage extends Message {
+
+ /**
+ * Receive a RequestIconMessage, assuming the message tag has already been
+ * read.
+ *
+ * @throws IOException
+ */
+ public static RequestIconMessage receive(BrowserChannel channel)
+ throws IOException {
+ return new RequestIconMessage(channel);
+ }
+
+ public static void send(BrowserChannel channel)
+ throws IOException {
+ DataOutputStream stream = channel.getStreamToOtherSide();
+ stream.writeByte(MessageType.REQUEST_ICON.getId());
+ stream.flush();
+ }
+
+ public RequestIconMessage(BrowserChannel channel) {
+ super(channel);
+ }
+
+ @Override
+ public void send() throws IOException {
+ send(getBrowserChannel());
+ }
+ }
+
+ /**
+ * Signifies a return from a previous invoke.
+ */
+ protected static class ReturnMessage extends Message {
+ public static ReturnMessage receive(BrowserChannel channel)
+ throws IOException {
+ final DataInputStream stream = channel.getStreamFromOtherSide();
+ final boolean isException = stream.readBoolean();
+ final Value returnValue = channel.readValue(stream);
+ return new ReturnMessage(channel, isException, returnValue);
+ }
+
+ public static void send(BrowserChannel channel, boolean isException,
+ Value returnValue) throws IOException {
+ final DataOutputStream stream = channel.getStreamToOtherSide();
+ stream.writeByte(MessageType.RETURN.getId());
+ stream.writeBoolean(isException);
+ channel.writeValue(stream, returnValue);
+ stream.flush();
+ }
+
+ public static void send(BrowserChannel channel,
+ ExceptionOrReturnValue returnOrException) throws IOException {
+ send(channel, returnOrException.isException(),
+ returnOrException.getReturnValue());
+ }
+
+ private final boolean isException;
+ private final Value returnValue;
+
+ public ReturnMessage(BrowserChannel channel, boolean isException,
+ Value returnValue) {
+ super(channel);
+ this.returnValue = returnValue;
+ this.isException = isException;
+ }
+
+ public Value getReturnValue() {
+ return returnValue;
+ }
+
+ public boolean isException() {
+ return isException;
+ }
+
+ @Override
+ public void send() throws IOException {
+ send(getBrowserChannel(), isException, returnValue);
+ }
+ }
+
+ /**
+ * A response to ChooseTransport telling the client which transport should
+ * be used for the remainder of the protocol.
+ */
+ protected static class SwitchTransportMessage extends Message {
+
+ public static SwitchTransportMessage receive(BrowserChannel channel)
+ throws IOException {
+ DataInputStream stream = channel.getStreamFromOtherSide();
+ String transport = readUtf8String(stream);
+ String transportArgs = readUtf8String(stream);
+ return new SwitchTransportMessage(channel, transport, transportArgs);
+ }
+
+ private final String transport;
+
+ private final String transportArgs;
+
+ public SwitchTransportMessage(BrowserChannel channel,
+ String transport, String transportArgs) {
+ super(channel);
+ // Change nulls to empty strings
+ if (transport == null) {
+ transport = "";
+ }
+ if (transportArgs == null) {
+ transportArgs = "";
+ }
+ this.transport = transport;
+ this.transportArgs = transportArgs;
+ }
+
+ public String getTransport() {
+ return transport;
+ }
+
+ public String getTransportArgs() {
+ return transportArgs;
+ }
+
+ @Override
+ public void send() throws IOException {
+ DataOutputStream stream = getBrowserChannel().getStreamToOtherSide();
+ stream.writeByte(MessageType.SWITCH_TRANSPORT.getId());
+ writeUtf8String(stream, transport);
+ writeUtf8String(stream, transportArgs);
+ stream.flush();
+ }
+ }
+
+ /**
+ * A message supplying an icon, which fits in 24x24 and in a standard image
+ * format such as PNG or GIF, suitable for use in the UI.
+ * <p>See {@link RequestIconMessage}.
+ */
+ protected static class UserAgentIconMessage extends Message {
+ public static UserAgentIconMessage receive(BrowserChannel channel)
+ throws IOException {
+ byte[] iconBytes = null;
+ DataInputStream stream = channel.getStreamFromOtherSide();
+ int len = stream.readInt();
+ if (len > 0) {
+ iconBytes = new byte[len];
+ for (int i = 0; i < len; ++i) {
+ iconBytes[i] = stream.readByte();
+ }
+ }
+ return new UserAgentIconMessage(channel, iconBytes);
+ }
+
+ public static void send(BrowserChannel channel, byte[] iconBytes)
+ throws IOException {
+ DataOutputStream stream = channel.getStreamToOtherSide();
+ stream.writeByte(MessageType.USER_AGENT_ICON.getId());
+ if (iconBytes == null) {
+ stream.writeInt(0);
+ } else {
+ stream.writeInt(iconBytes.length);
+ for (byte b : iconBytes) {
+ stream.writeByte(b);
+ }
+ }
+ stream.flush();
+ }
+
+ private byte[] iconBytes;
+
+ public UserAgentIconMessage(BrowserChannel channel, byte[] iconBytes) {
+ super(channel);
+ this.iconBytes = iconBytes;
+ }
+
+ public byte[] getIconBytes() {
+ return iconBytes;
+ }
+
+ @Override
+ public void send() throws IOException {
+ send(getBrowserChannel(), iconBytes);
+ }
+ }
+
+ /**
+ * The current version of the protocol.
+ */
+ public static final int PROTOCOL_VERSION_CURRENT = 3;
+
+ /**
+ * The oldest protocol version supported by this code.
+ */
+ public static final int PROTOCOL_VERSION_OLDEST = 2;
+
+ /**
+ * The protocol version that added the GetIcon message.
+ */
+ public static final int PROTOCOL_VERSION_GET_ICON = 3;
+
+ public static final int SPECIAL_CLIENTMETHODS_OBJECT = 0;
+
+ public static final int SPECIAL_SERVERMETHODS_OBJECT = 0;
+
+ protected static JavaObjectRef getJavaObjectRef(int refId) {
+ return new JavaObjectRef(refId);
+ }
+
+ protected static String readUtf8String(DataInputStream stream)
+ throws IOException {
+ final int len = stream.readInt();
+ final byte[] data = new byte[len];
+ stream.readFully(data);
+ return new String(data, "UTF8");
+ }
+
+ protected static ValueType readValueType(DataInputStream stream)
+ throws IOException, BrowserChannelException {
+ int type = stream.readByte();
+ ValueType[] types = ValueType.values();
+ if (type < 0 || type >= types.length) {
+ throw new BrowserChannelException("Invalid value type " + type);
+ }
+ return types[type];
+ }
+
+ protected static void writeJavaObject(DataOutputStream stream,
+ JavaObjectRef value) throws IOException {
+ stream.writeByte(ValueType.JAVA_OBJECT.getTag());
+ stream.writeInt(value.getRefid());
+ }
+
+ protected static void writeJsObject(DataOutputStream stream,
+ JsObjectRef value) throws IOException {
+ stream.writeByte(ValueType.JS_OBJECT.getTag());
+ stream.writeInt(value.getRefid());
+ }
+
+ protected static void writeNull(DataOutputStream stream) throws IOException {
+ stream.writeByte(ValueType.NULL.getTag());
+ }
+
+ protected static void writeTaggedBoolean(DataOutputStream stream,
+ boolean value) throws IOException {
+ stream.writeByte(ValueType.BOOLEAN.getTag());
+ stream.writeBoolean(value);
+ }
+
+ protected static void writeTaggedByte(DataOutputStream stream, byte value)
+ throws IOException {
+ stream.writeByte(ValueType.BYTE.getTag());
+ stream.writeByte(value);
+ }
+
+ protected static void writeTaggedChar(DataOutputStream stream, char value)
+ throws IOException {
+ stream.writeByte(ValueType.CHAR.getTag());
+ stream.writeChar(value);
+ }
+
+ protected static void writeTaggedDouble(DataOutputStream stream, double value)
+ throws IOException {
+ stream.writeByte(ValueType.DOUBLE.getTag());
+ stream.writeDouble(value);
+ }
+
+ protected static void writeTaggedFloat(DataOutputStream stream, float value)
+ throws IOException {
+ stream.writeByte(ValueType.FLOAT.getTag());
+ stream.writeFloat(value);
+ }
+
+ protected static void writeTaggedInt(DataOutputStream stream, int value)
+ throws IOException {
+ stream.writeByte(ValueType.INT.getTag());
+ stream.writeInt(value);
+ }
+
+ protected static void writeTaggedShort(DataOutputStream stream, short value)
+ throws IOException {
+ stream.writeByte(ValueType.SHORT.getTag());
+ stream.writeShort(value);
+ }
+
+ protected static void writeTaggedString(DataOutputStream stream, String data)
+ throws IOException {
+ stream.writeByte(ValueType.STRING.getTag());
+ writeUtf8String(stream, data);
+ }
+
+ protected static void writeUtf8String(DataOutputStream stream, String data)
+ throws IOException {
+ try {
+ final byte[] bytes = data.getBytes("UTF8");
+ stream.writeInt(bytes.length);
+ stream.write(bytes);
+ } catch (UnsupportedEncodingException e) {
+ // TODO: Add description.
+ throw new RuntimeException();
+ }
+ }
+
+ private static void writeUndefined(DataOutputStream stream)
+ throws IOException {
+ stream.writeByte(ValueType.UNDEFINED.getTag());
+ }
+
+ private final ObjectRefFactory objectRefFactory;
+
+ private Socket socket;
+
+ private final DataInputStream streamFromOtherSide;
+
+ private final DataOutputStream streamToOtherSide;
+
+ public BrowserChannel(Socket socket, ObjectRefFactory objectRefFactory)
+ throws IOException {
+ this(new BufferedInputStream(socket.getInputStream()),
+ new BufferedOutputStream(socket.getOutputStream()),
+ objectRefFactory);
+ this.socket = socket;
+ }
+
+ protected BrowserChannel(InputStream inputStream, OutputStream outputStream,
+ ObjectRefFactory objectRefFactory) {
+ streamFromOtherSide = new DataInputStream(inputStream);
+ streamToOtherSide = new DataOutputStream(outputStream);
+ socket = null;
+ this.objectRefFactory = objectRefFactory;
+ }
+
+ public void endSession() {
+ Utility.close(streamFromOtherSide);
+ Utility.close(streamToOtherSide);
+ Utility.close(socket);
+ }
+
+ /**
+ * @return a set of remote object reference IDs to be freed.
+ */
+ public Set<Integer> getRefIdsForCleanup() {
+ return objectRefFactory.getRefIdsForCleanup();
+ }
+
+ public String getRemoteEndpoint() {
+ if (socket == null) {
+ return "";
+ }
+ return socket.getInetAddress().getCanonicalHostName() + ":"
+ + socket.getPort();
+ }
+
+ protected DataInputStream getStreamFromOtherSide() {
+ return streamFromOtherSide;
+ }
+
+ protected DataOutputStream getStreamToOtherSide() {
+ return streamToOtherSide;
+ }
+
+ protected Value readValue(DataInputStream stream) throws IOException {
+ ValueType tag;
+ try {
+ tag = readValueType(stream);
+ } catch (BrowserChannelException e) {
+ IOException ee = new IOException();
+ ee.initCause(e);
+ throw ee;
+ }
+ Value value = new Value();
+ switch (tag) {
+ case NULL:
+ value.setNull();
+ break;
+ case UNDEFINED:
+ value.setUndefined();
+ break;
+ case BOOLEAN:
+ value.setBoolean(stream.readByte() != 0);
+ break;
+ case BYTE:
+ value.setByte(stream.readByte());
+ break;
+ case CHAR:
+ value.setChar(stream.readChar());
+ break;
+ case FLOAT:
+ value.setFloat(stream.readFloat());
+ break;
+ case INT:
+ value.setInt(stream.readInt());
+ break;
+ case LONG:
+ value.setLong(stream.readLong());
+ break;
+ case DOUBLE:
+ value.setDouble(stream.readDouble());
+ break;
+ case SHORT:
+ value.setShort(stream.readShort());
+ break;
+ case STRING:
+ value.setString(readUtf8String(stream));
+ break;
+ case JS_OBJECT:
+ value.setJsObject(objectRefFactory.getJsObjectRef(stream.readInt()));
+ break;
+ case JAVA_OBJECT:
+ value.setJavaObject(objectRefFactory.getJavaObjectRef(
+ stream.readInt()));
+ break;
+ }
+ return value;
+ }
+
+ protected void sendFreedValues() throws IOException {
+ Set<Integer> freed = objectRefFactory.getRefIdsForCleanup();
+ int n = freed.size();
+ if (n > 0) {
+ int[] ids = new int[n];
+ int i = 0;
+ for (Integer id : freed) {
+ ids[i++] = id;
+ }
+ FreeMessage.send(this, ids);
+ }
+ }
+
+ protected void writeValue(DataOutputStream stream, Value value)
+ throws IOException {
+ if (value.isNull()) {
+ writeNull(stream);
+ } else if (value.isUndefined()) {
+ writeUndefined(stream);
+ } else if (value.isJsObject()) {
+ writeJsObject(stream, value.getJsObject());
+ } else if (value.isJavaObject()) {
+ writeJavaObject(stream, value.getJavaObject());
+ } else if (value.isBoolean()) {
+ writeTaggedBoolean(stream, value.getBoolean());
+ } else if (value.isByte()) {
+ writeTaggedByte(stream, value.getByte());
+ } else if (value.isChar()) {
+ writeTaggedChar(stream, value.getChar());
+ } else if (value.isShort()) {
+ writeTaggedShort(stream, value.getShort());
+ } else if (value.isDouble()) {
+ writeTaggedDouble(stream, value.getDouble());
+ } else if (value.isFloat()) {
+ writeTaggedFloat(stream, value.getFloat());
+ } else if (value.isInt()) {
+ writeTaggedInt(stream, value.getInt());
+ } else if (value.isString()) {
+ writeTaggedString(stream, value.getString());
+ } else {
+ assert false;
+ }
+ }
+}
diff --git a/gwtquery-core/src/test/java/com/google/gwt/query/QueryTest.gwt.xml b/gwtquery-core/src/test/java/com/google/gwt/query/QueryTest.gwt.xml
new file mode 100644
index 00000000..5b2b1d35
--- /dev/null
+++ b/gwtquery-core/src/test/java/com/google/gwt/query/QueryTest.gwt.xml
@@ -0,0 +1,21 @@
+<!--
+ Copyright 2013, The gwtquery team.
+
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ use this file except in compliance with the License. You may obtain a copy of
+ the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ 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.
+-->
+
+<module>
+ <inherits name='com.google.gwt.query.Query'/>
+ <servlet class='com.google.gwt.query.servlet.GQAjaxTestServlet' path="/test.json"/>
+ <servlet class='com.google.gwt.query.servlet.GQAjaxTestServlet' path="/test.upld"/>
+</module>
diff --git a/gwtquery-core/src/test/java/com/google/gwt/query/client/ajax/AjaxCommon.java b/gwtquery-core/src/test/java/com/google/gwt/query/client/ajax/AjaxCommon.java
new file mode 100644
index 00000000..2ff30953
--- /dev/null
+++ b/gwtquery-core/src/test/java/com/google/gwt/query/client/ajax/AjaxCommon.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2013, The gwtquery team.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.query.client.ajax;
+
+import static com.google.gwt.query.client.GQuery.*;
+
+import org.mortbay.jetty.Server;
+
+import net.sourceforge.htmlunit.corejs.javascript.Context;
+import net.sourceforge.htmlunit.corejs.javascript.Scriptable;
+
+import com.gargoylesoftware.htmlunit.javascript.host.xml.XMLHttpRequest;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.junit.client.GWTTestCase;
+import com.google.gwt.query.client.Binder;
+import com.google.gwt.query.client.Function;
+import com.google.gwt.query.client.GQ;
+import com.google.gwt.query.client.Properties;
+import com.google.gwt.query.client.plugins.ajax.Ajax;
+import com.google.gwt.query.client.plugins.ajax.Ajax.Settings;
+
+/**
+ * Tests for Deferred which can run either in JVM and GWT
+ */
+public abstract class AjaxCommon extends GWTTestCase {
+
+ public String getModuleName() {
+ return null;
+ }
+
+ protected String echoUrl, corsUrl;
+ protected Binder json, jsonData;
+ protected String servletPath = "test.json";
+
+ private void performAjaxJsonTest(Settings s) {
+ delayTestFinish(5000);
+ Ajax.ajax(s).done(new Function(){public void f() {
+ Binder p = arguments(0);
+ assertEquals("abc", p.get("a"));
+ finishTest();
+ }}).fail(new Function(){public void f() {
+ fail();
+ }});
+ }
+
+ public void testAjaxJsonPost() {
+ delayTestFinish(5000);
+ Settings s = Ajax.createSettings()
+ .setUrl(echoUrl)
+ .setData(json)
+ .setDataType("json")
+ .setUsername("testuser")
+ .setPassword("testpassword")
+ ;
+ performAjaxJsonTest(s);
+ }
+
+ public void testAjaxJsonPost_CORS() {
+ delayTestFinish(5000);
+ Settings s = Ajax.createSettings()
+ .setUrl(corsUrl)
+ .setData(json)
+ .setDataType("json");
+
+ performAjaxJsonTest(s);
+ }
+
+ public void testAjaxJsonGet() {
+ Settings s = Ajax.createSettings()
+ .setType("get")
+ .setUrl(echoUrl)
+ .setData(jsonData)
+ .setDataType("json");
+
+ performAjaxJsonTest(s);
+ }
+
+ public void testAjaxJsonGet_CORS() {
+ Settings s = Ajax.createSettings()
+ .setType("get")
+ .setUrl(corsUrl)
+ .setData(jsonData)
+ .setDataType("json");
+
+ performAjaxJsonTest(s);
+ }
+
+ public void testAjaxGetJsonP() {
+ delayTestFinish(5000);
+ Settings s = Ajax.createSettings()
+ .setType("post")
+ .setUrl(echoUrl)
+ .setData(jsonData)
+ .setDataType("jsonp");
+
+ performAjaxJsonTest(s);
+ }
+
+ public void testAjaxGetJsonP_CORS() {
+ delayTestFinish(5000);
+ Settings s = Ajax.createSettings()
+ .setType("post")
+ .setUrl(corsUrl)
+ .setData(jsonData)
+ .setDataType("jsonp");
+
+ performAjaxJsonTest(s);
+ }
+
+
+
+// public void testAjaxJson() {
+// delayTestFinish(5000);
+// Settings s = Ajax.createSettings()
+// .setType("get")
+// .setUrl(GWT.getModuleBaseURL() + "test.json")
+// .setData($$("data: {a: abc, d: ddd}"))
+// .setDataType("json");
+//
+// Ajax.ajax(s).done(new Function(){public void f() {
+// Binder p = arguments(0);
+// assertEquals("abc", p.get("a"));
+// finishTest();
+// }}).fail(new Function(){public void f() {
+// fail();
+// }});
+// }
+ public void testJsonValidService() {
+ delayTestFinish(5000);
+ // 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() {
+ Binder p = arguments(0);
+ // 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.<Binder>get("error").get("code"));
+ finishTest();
+ }
+ }, null, 0);
+ }
+
+} \ No newline at end of file
diff --git a/gwtquery-core/src/test/java/com/google/gwt/query/client/ajax/AjaxTest.java b/gwtquery-core/src/test/java/com/google/gwt/query/client/ajax/AjaxTest.java
index a21ec648..45636766 100644
--- a/gwtquery-core/src/test/java/com/google/gwt/query/client/ajax/AjaxTest.java
+++ b/gwtquery-core/src/test/java/com/google/gwt/query/client/ajax/AjaxTest.java
@@ -15,40 +15,86 @@
*/
package com.google.gwt.query.client.ajax;
-import java.util.Arrays;
-import java.util.Date;
-import java.util.List;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.Servlet;
+
+import org.mortbay.jetty.Server;
+import org.mortbay.jetty.handler.HandlerWrapper;
+import org.mortbay.jetty.servlet.DefaultServlet;
+import org.mortbay.jetty.webapp.WebAppClassLoader;
+import org.mortbay.jetty.webapp.WebAppContext;
-import com.google.gwt.junit.client.GWTTestCase;
-import com.google.gwt.query.client.Function;
import com.google.gwt.query.client.GQ;
-import com.google.gwt.query.client.Properties;
-import com.google.gwt.query.client.builders.JsonBuilder;
-import com.google.gwt.query.client.builders.Name;
-import com.google.gwt.query.client.plugins.ajax.Ajax;
+import com.google.gwt.query.servlet.GQAjaxTestServlet;
/**
* Tests for Deferred which can run either in JVM and GWT
*/
-public class AjaxTest extends GWTTestCase {
+public class AjaxTest extends AjaxCommon {
+
+ static Server server;
+ int port = 3333;
public String getModuleName() {
return null;
}
- public void testJsonValidService() {
- delayTestFinish(5000);
- // 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();
- // 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").<Properties>cast().getInt("code"));
- finishTest();
+ protected void gwtSetUp() throws Exception {
+ echoUrl = "http://127.0.0.1:" + port + "/" + servletPath;
+ corsUrl = "http://localhost:" + port + "/" + servletPath;
+ jsonData = GQ.create("data: {a: abc, d: ddd}");
+ json = GQ.create("a: abc, d: ddd");
+ startWebServer();
+ }
+
+ protected void startWebServer() throws Exception {
+ if (server == null) {
+ final Map<String, Class<? extends Servlet>> servlets = new HashMap<String, Class<? extends Servlet>>();
+ servlets.put("/" + servletPath, GQAjaxTestServlet.class);
+ server = createWebServer(port, ".", null, servlets, null);
+ }
+ }
+
+ public static Server createWebServer(final int port, final String resourceBase, final String[] classpath,
+ final Map<String, Class<? extends Servlet>> servlets, final HandlerWrapper handler) throws Exception {
+
+ final Server server = new Server(port);
+
+ final WebAppContext context = new WebAppContext();
+ context.setContextPath("/");
+ context.setResourceBase(resourceBase);
+
+ if (servlets != null) {
+ for (final Map.Entry<String, Class<? extends Servlet>> entry : servlets.entrySet()) {
+ final String pathSpec = entry.getKey();
+ final Class<? extends Servlet> servlet = entry.getValue();
+ context.addServlet(servlet, pathSpec);
+
+ // disable defaults if someone likes to register his own root servlet
+ if ("/".equals(pathSpec)) {
+ context.setDefaultsDescriptor(null);
+ context.addServlet(DefaultServlet.class, "/favicon.ico");
+ }
+ }
+ }
+
+ final WebAppClassLoader loader = new WebAppClassLoader(context);
+ if (classpath != null) {
+ for (final String path : classpath) {
+ loader.addClassPath(path);
}
- }, null, 0);
+ }
+ context.setClassLoader(loader);
+ if (handler != null) {
+ handler.setHandler(context);
+ server.setHandler(handler);
+ } else {
+ server.setHandler(context);
+ }
+ server.start();
+ return server;
}
} \ No newline at end of file
diff --git a/gwtquery-core/src/test/java/com/google/gwt/query/client/ajax/AjaxTestGwt.java b/gwtquery-core/src/test/java/com/google/gwt/query/client/ajax/AjaxTestGwt.java
index b30d53d6..022c84b1 100644
--- a/gwtquery-core/src/test/java/com/google/gwt/query/client/ajax/AjaxTestGwt.java
+++ b/gwtquery-core/src/test/java/com/google/gwt/query/client/ajax/AjaxTestGwt.java
@@ -15,15 +15,24 @@
*/
package com.google.gwt.query.client.ajax;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.query.client.GQ;
+
/**
* Test for data binding shared code run in gwt
*/
-public class AjaxTestGwt extends AjaxTest {
-
+public class AjaxTestGwt extends AjaxCommon {
@Override
public String getModuleName() {
- return "com.google.gwt.query.Query";
+ return "com.google.gwt.query.QueryTest";
+ }
+
+ @Override
+ protected void gwtSetUp() throws Exception {
+ echoUrl = (GWT.isClient() ? GWT.getHostPageBaseURL() : "http://localhost:3333/") + servletPath;
+ corsUrl = echoUrl.replaceFirst("http://[\\d\\.]+:", "http://localhost:") + "?cors=true";
+ jsonData = GQ.create("data: {a: abc, d: ddd}");
+ json = GQ.create("a: abc, d: ddd");
}
-
}
diff --git a/gwtquery-core/src/test/java/com/google/gwt/query/servlet/GQAjaxTestServlet.java b/gwtquery-core/src/test/java/com/google/gwt/query/servlet/GQAjaxTestServlet.java
new file mode 100644
index 00000000..6ba602da
--- /dev/null
+++ b/gwtquery-core/src/test/java/com/google/gwt/query/servlet/GQAjaxTestServlet.java
@@ -0,0 +1,74 @@
+package com.google.gwt.query.servlet;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Enumeration;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+public class GQAjaxTestServlet extends HttpServlet {
+
+ private static final long serialVersionUID = 1L;
+ String name = this.getClass().getSimpleName() + " ";
+
+ @Override
+ protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+ System.out.println(name + req.getMethod() + " " + req.getContentType());
+ Enumeration<String> en = req.getHeaderNames();
+ while (en.hasMoreElements()) {
+ String s = (String) en.nextElement();
+ System.out.println(name + s + " " + req.getHeader(s));
+ }
+
+
+ String t = req.getParameter("timeout");
+ if (t != null && t.matches("\\d+")) {
+ try {
+ int ms = Integer.parseInt(t);
+ System.out.println(" Sleeping: " + ms);
+ Thread.sleep(ms);
+ } catch (Exception e) {
+ }
+ }
+
+ String data = "";
+ if (req.getMethod().equalsIgnoreCase("get")) {
+ data = req.getParameter("data") != null ? req.getParameter("data") : "";
+ if (req.getParameter("callback") != null) {
+ data = req.getParameter("callback") + "(" + data + ");";
+ }
+ } else if (req.getMethod().equalsIgnoreCase("post")
+ && req.getContentType().toLowerCase().startsWith("application/json")) {
+ BufferedReader reader = req.getReader();
+ String line;
+ while ((line = reader.readLine()) != null)
+ data += line;
+ }
+
+ String origin = req.getHeader("Origin");
+ if ("true".equals(req.getParameter("cors")) && origin != null) {
+ resp.addHeader("Access-Control-Allow-Origin", origin);
+ resp.addHeader("Access-Control-Allow-Credentials", "true");
+ String method = req.getHeader("Access-Control-Request-Method");
+ if (method != null) {
+ resp.addHeader("Access-Control-Allow-Methods", method);
+ resp.setHeader("Allow", "GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS");
+ }
+ String headers = req.getHeader("Access-Control-Request-Headers");
+ if (headers != null) {
+ resp.addHeader("Access-Control-Allow-Headers", headers);
+ }
+ }
+
+ PrintWriter p = resp.getWriter();
+ p.print(data);
+ p.flush();
+ p.close();
+
+ System.out.println(name + "returns: " + data);
+ }
+}