Re-roll of r10955 after fixing internal build breakage.
Make GWT.create/etc usable on server.
Patch by: jat
Review by: cromwellian
Public review: http://gwt-code-reviews.appspot.com/1677803
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@10968 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/UnifyAst.java b/dev/core/src/com/google/gwt/dev/jjs/impl/UnifyAst.java
index 06490a1..b6967c4 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/UnifyAst.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/UnifyAst.java
@@ -429,7 +429,7 @@
private JExpression handleMagicMethodCall(JMethodCall x) {
JMethod target = x.getTarget();
String sig = target.getEnclosingType().getName() + '.' + target.getSignature();
- if (GWT_CREATE.equals(sig)) {
+ if (GWT_CREATE.equals(sig) || OLD_GWT_CREATE.equals(sig)) {
return handleGwtCreate(x);
} else if (IMPL_GET_NAME_OF.equals(sig)) {
return handleImplNameOf(x);
@@ -445,29 +445,38 @@
"java.lang.Class.isClassMetadataEnabled()Z";
private static final String GWT_CREATE =
- "com.google.gwt.core.client.GWT.create(Ljava/lang/Class;)Ljava/lang/Object;";
+ "com.google.gwt.core.shared.GWT.create(Ljava/lang/Class;)Ljava/lang/Object;";
- private static final String GWT_IS_CLIENT = "com.google.gwt.core.client.GWT.isClient()Z";
+ private static final String GWT_IS_CLIENT = "com.google.gwt.core.shared.GWT.isClient()Z";
- private static final String GWT_IS_PROD_MODE = "com.google.gwt.core.client.GWT.isProdMode()Z";
+ private static final String GWT_IS_PROD_MODE = "com.google.gwt.core.shared.GWT.isProdMode()Z";
- private static final String GWT_IS_SCRIPT = "com.google.gwt.core.client.GWT.isScript()Z";
+ private static final String GWT_IS_SCRIPT = "com.google.gwt.core.shared.GWT.isScript()Z";
private static final String IMPL_GET_NAME_OF =
"com.google.gwt.core.client.impl.Impl.getNameOf(Ljava/lang/String;)Ljava/lang/String;";
+ private static final String OLD_GWT_CREATE =
+ "com.google.gwt.core.client.GWT.create(Ljava/lang/Class;)Ljava/lang/Object;";
+
+ private static final String OLD_GWT_IS_CLIENT = "com.google.gwt.core.client.GWT.isClient()Z";
+
+ private static final String OLD_GWT_IS_PROD_MODE = "com.google.gwt.core.client.GWT.isProdMode()Z";
+
+ private static final String OLD_GWT_IS_SCRIPT = "com.google.gwt.core.client.GWT.isScript()Z";
+
/**
* Methods for which the call site must be replaced with magic AST nodes.
*/
private static final Set<String> MAGIC_METHOD_CALLS = new LinkedHashSet<String>(Arrays.asList(
- GWT_CREATE, IMPL_GET_NAME_OF));
+ GWT_CREATE, OLD_GWT_CREATE, IMPL_GET_NAME_OF));
/**
* Methods with magic implementations that the compiler must insert.
*/
private static final Set<String> MAGIC_METHOD_IMPLS = new LinkedHashSet<String>(Arrays.asList(
- GWT_IS_CLIENT, GWT_IS_PROD_MODE, GWT_IS_SCRIPT, CLASS_DESIRED_ASSERTION_STATUS,
- CLASS_IS_CLASS_METADATA_ENABLED));
+ GWT_IS_CLIENT, OLD_GWT_IS_CLIENT, GWT_IS_PROD_MODE, OLD_GWT_IS_PROD_MODE, GWT_IS_SCRIPT,
+ OLD_GWT_IS_SCRIPT, CLASS_DESIRED_ASSERTION_STATUS, CLASS_IS_CLASS_METADATA_ENABLED));
private final Map<String, CompiledClass> classFileMap;
private final Map<String, CompiledClass> classFileMapBySource;
@@ -907,7 +916,8 @@
magicMethodCalls.add(method);
}
if (MAGIC_METHOD_IMPLS.contains(sig)) {
- if (sig.startsWith("com.google.gwt.core.client.GWT.")) {
+ if (sig.startsWith("com.google.gwt.core.client.GWT.")
+ || sig.startsWith("com.google.gwt.core.shared.GWT.")) {
// GWT.isClient, GWT.isScript, GWT.isProdMode all true.
implementMagicMethod(method, JBooleanLiteral.TRUE);
} else {
diff --git a/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java b/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java
index 367144d..b68db44 100644
--- a/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java
+++ b/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java
@@ -732,7 +732,9 @@
* space (thus, they bridge across the spaces).
*/
private static final Class<?>[] BRIDGE_CLASSES = new Class<?>[]{
- ShellJavaScriptHost.class, GWTBridge.class};
+ // Have to include the shared GWTBridge class since the client one
+ // inherits from it, otherwise we get verify errors
+ ShellJavaScriptHost.class, GWTBridge.class, com.google.gwt.core.shared.GWTBridge.class};
private static final boolean CLASS_DUMP = Boolean.getBoolean("gwt.dev.classDump");
diff --git a/dev/core/super/com/google/gwt/core/client/GWTBridge.java b/dev/core/super/com/google/gwt/core/client/GWTBridge.java
index 75f8035..925922a 100644
--- a/dev/core/super/com/google/gwt/core/client/GWTBridge.java
+++ b/dev/core/super/com/google/gwt/core/client/GWTBridge.java
@@ -18,18 +18,9 @@
/**
* When running in Development Mode, acts as a bridge from {@link GWT} into the
* Development Mode environment.
+ *
+ * For code that may run anywhere besides the client, use
+ * {@link com.google.gwt.core.shared.GWTBridge} instead.
*/
-public abstract class GWTBridge {
-
- public abstract <T> T create(Class<?> classLiteral);
-
- public String getThreadUniqueID() {
- return "";
- }
-
- public abstract String getVersion();
-
- public abstract boolean isClient();
-
- public abstract void log(String message, Throwable e);
+public abstract class GWTBridge extends com.google.gwt.core.shared.GWTBridge {
}
diff --git a/dev/core/super/com/google/gwt/core/shared/GWTBridge.java b/dev/core/super/com/google/gwt/core/shared/GWTBridge.java
new file mode 100644
index 0000000..6e6f82d
--- /dev/null
+++ b/dev/core/super/com/google/gwt/core/shared/GWTBridge.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2011 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.core.shared;
+
+/**
+ * When running in Development Mode, acts as a bridge from GWT into the
+ * Development Mode environment.
+ */
+public abstract class GWTBridge {
+
+ public abstract <T> T create(Class<?> classLiteral);
+
+ public String getThreadUniqueID() {
+ return "";
+ }
+
+ public abstract String getVersion();
+
+ public abstract boolean isClient();
+
+ public abstract void log(String message, Throwable e);
+}
diff --git a/servlet/build.xml b/servlet/build.xml
index 21a57be..e35993e 100755
--- a/servlet/build.xml
+++ b/servlet/build.xml
@@ -28,6 +28,7 @@
<include name="com/google/gwt/dev/util/Name*.class" />
<include name="com/google/gwt/dev/util/StringKey.class" />
<include name="com/google/gwt/util/tools/shared/**" />
+ <include name="com/google/gwt/core/shared/**" />
</fileset>
<fileset dir="${gwt.user.bin}">
<exclude name="**/rebind/**" />
diff --git a/user/src/com/google/gwt/core/Core.gwt.xml b/user/src/com/google/gwt/core/Core.gwt.xml
index 85e3a25..7279e77 100644
--- a/user/src/com/google/gwt/core/Core.gwt.xml
+++ b/user/src/com/google/gwt/core/Core.gwt.xml
@@ -26,6 +26,8 @@
<inherits name="com.google.gwt.core.EmulateJsStack" />
<inherits name="com.google.gwt.core.AsyncFragmentLoader" />
+ <source path="client" />
+ <source path="shared" />
<super-source path="translatable" />
<define-linker name="sso" class="com.google.gwt.core.linker.SingleScriptLinker" />
diff --git a/user/src/com/google/gwt/core/client/GWT.java b/user/src/com/google/gwt/core/client/GWT.java
index 736f3e3..98330df 100644
--- a/user/src/com/google/gwt/core/client/GWT.java
+++ b/user/src/com/google/gwt/core/client/GWT.java
@@ -45,6 +45,7 @@
*/
private static final class DefaultUncaughtExceptionHandler implements
UncaughtExceptionHandler {
+ @Override
public void onUncaughtException(Throwable e) {
log("Uncaught exception escaped", e);
}
@@ -57,12 +58,6 @@
public static final String HOSTED_MODE_PERMUTATION_STRONG_NAME = "HostedMode";
/**
- * Always <code>null</code> in Production Mode; in Development Mode provides
- * the implementation for certain methods.
- */
- private static GWTBridge sGWTBridge = null;
-
- /**
* Defaults to <code>null</code> in Production Mode and an instance of
* {@link DefaultUncaughtExceptionHandler} in Development Mode.
*/
@@ -80,22 +75,14 @@
*
* @param classLiteral a class literal specifying the base class to be
* instantiated
- * @return the new instance, which must be typecast to the requested class.
+ * @return the new instance, which must be cast to the requested class
*/
public static <T> T create(Class<?> classLiteral) {
- if (sGWTBridge == null) {
- /*
- * In Production Mode, the compiler directly replaces calls to this method
- * with a new Object() type expression of the correct rebound type.
- */
- throw new UnsupportedOperationException(
- "ERROR: GWT.create() is only usable in client code! It cannot be called, "
- + "for example, from server code. If you are running a unit test, "
- + "check that your test case extends GWTTestCase and that GWT.create() "
- + "is not called from within an initializer or constructor.");
- } else {
- return sGWTBridge.<T> create(classLiteral);
- }
+ /*
+ * In Production Mode, the compiler directly replaces calls to this method
+ * with a new Object() type expression of the correct rebound type.
+ */
+ return com.google.gwt.core.shared.GWT.create(classLiteral);
}
/**
@@ -195,18 +182,15 @@
* gwt-dev.
*/
public static String getUniqueThreadId() {
- if (sGWTBridge != null) {
- return sGWTBridge.getThreadUniqueID();
- }
- return "";
+ return com.google.gwt.core.shared.GWT.getUniqueThreadId();
}
public static String getVersion() {
- if (sGWTBridge == null) {
- return getVersion0();
- } else {
- return sGWTBridge.getVersion();
+ String version = com.google.gwt.core.shared.GWT.getVersion();
+ if (version == null) {
+ version = getVersion0();
}
+ return version;
}
/**
@@ -217,8 +201,7 @@
* GWTTestCase test.
*/
public static boolean isClient() {
- // Replaced with "true" by GWT compiler.
- return sGWTBridge != null && sGWTBridge.isClient();
+ return com.google.gwt.core.shared.GWT.isClient();
}
/**
@@ -227,16 +210,14 @@
* in a plain JVM.
*/
public static boolean isProdMode() {
- // Replaced with "true" by GWT compiler.
- return false;
+ return com.google.gwt.core.shared.GWT.isProdMode();
}
/**
* Determines whether or not the running program is script or bytecode.
*/
public static boolean isScript() {
- // Replaced with "true" by GWT compiler.
- return false;
+ return com.google.gwt.core.shared.GWT.isScript();
}
/**
@@ -244,7 +225,7 @@
* are optimized out in Production Mode.
*/
public static void log(String message) {
- log(message, null);
+ com.google.gwt.core.shared.GWT.log(message);
}
/**
@@ -252,11 +233,10 @@
* are optimized out in Production Mode.
*/
public static void log(String message, Throwable e) {
- if (sGWTBridge != null) {
- sGWTBridge.log(message, e);
- }
+ com.google.gwt.core.shared.GWT.log(message, e);
}
+
/**
* The same as {@link #runAsync(RunAsyncCallback)}, except with an extra
* parameter to provide a name for the call. The name parameter should be
@@ -293,7 +273,7 @@
* Production Mode.
*/
static void setBridge(GWTBridge bridge) {
- sGWTBridge = bridge;
+ com.google.gwt.core.shared.GWT.setBridge(bridge);
if (bridge != null) {
setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler());
}
diff --git a/user/src/com/google/gwt/core/server/GwtServletBase.java b/user/src/com/google/gwt/core/server/GwtServletBase.java
new file mode 100644
index 0000000..5c5267f
--- /dev/null
+++ b/user/src/com/google/gwt/core/server/GwtServletBase.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2012 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.core.server;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Base servlet for GWT server-side code which extracts properties from the
+ * request and sets them for this thread.
+ * <p>
+ * For now, subclasses should override {@link #init()} and set the locale
+ * configuration fields - eventually this will be read from a deploy artifact.
+ */
+public class GwtServletBase extends HttpServlet {
+
+ // These defaults should be kept in sync with I18N.gwt.xml
+ protected String[] localeSearchOrder = new String[] {
+ "queryparam", "cookie", "meta", "useragent",
+ };
+ protected String defaultLocale = "default";
+ protected String localeCookie = null;
+ protected String localeQueryParam = "locale";
+
+ @Override
+ public void init() throws ServletException {
+ // TODO(jat): implement reading config from a deploy artifact
+ }
+
+ /**
+ * Fetch a cookie from the HTTP request.
+ *
+ * @param req
+ * @param cookieName
+ * @return the value of the cookie or null if not found
+ */
+ protected final String getCookie(HttpServletRequest req, String cookieName) {
+ Cookie[] cookies = req.getCookies();
+ if (cookies == null) {
+ return null;
+ }
+ for (Cookie cookie : cookies) {
+ if (cookie.getName().equals(cookieName)) {
+ return cookie.getValue();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Get the GWT locale to use from this request.
+ *
+ * @param req
+ * @return the GWT locale to use as a String
+ */
+ protected String getGwtLocale(HttpServletRequest req) {
+ // set the locale
+ String locale = null;
+ for (String localeMethod : localeSearchOrder) {
+ if ("cookie".equals(localeMethod)) {
+ if (localeCookie != null) {
+ locale = getCookie(req, localeCookie);
+ }
+ } else if ("queryparam".equals(localeMethod)) {
+ if (localeQueryParam != null) {
+ locale = req.getParameter(localeQueryParam);
+ }
+ } else if ("useragent".equals(localeMethod)) {
+ // TODO(jat): implement Accept-Language processing
+ } else if ("usemeta".equals(localeMethod)) {
+ // ignored on the server
+ } else {
+ // TODO(jat): log ignored method?
+ }
+ if (locale != null) {
+ return locale;
+ }
+ }
+ return defaultLocale;
+ }
+
+ @Override
+ protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
+ IOException {
+ setGwtProperties(req);
+ super.service(req, resp);
+ }
+
+ /**
+ * Sets all GWT properties from the request.
+ * <p>
+ * If this method is overriden, this version should be called first and then
+ * any modifications to property values should be done.
+ *
+ * @param req
+ */
+ protected void setGwtProperties(HttpServletRequest req) {
+ ServerGwtBridge.getInstance().setThreadProperty("locale", getGwtLocale(req));
+ // TODO(jat): other properties, such as user.agent?
+ }
+}
diff --git a/user/src/com/google/gwt/core/server/LocalizableInstantiator.java b/user/src/com/google/gwt/core/server/LocalizableInstantiator.java
new file mode 100644
index 0000000..f5eb0fb
--- /dev/null
+++ b/user/src/com/google/gwt/core/server/LocalizableInstantiator.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2012 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.core.server;
+
+import com.google.gwt.core.server.ServerGwtBridge.ClassInstantiator;
+import com.google.gwt.core.server.ServerGwtBridge.ClassInstantiatorBase;
+import com.google.gwt.core.server.ServerGwtBridge.Properties;
+import com.google.gwt.i18n.shared.GwtLocale;
+
+/**
+ * Instantiator that knows how to lookup locale-specific implementations.
+ *
+ * It tries pkg.class_locale (including nested classes),
+ * pkg.impl.class_locale, and pkg.classImpl_locale, following the inheritance
+ * chain for the requested locale.
+ */
+class LocalizableInstantiator extends ClassInstantiatorBase implements ClassInstantiator {
+
+ @Override
+ public <T> T create(Class<?> clazz, Properties properties) {
+ String pkgName = clazz.getPackage().getName();
+ Class<?> enclosingClass = clazz.getEnclosingClass();
+ String className = clazz.getSimpleName();
+ GwtLocale locale = ServerGwtBridge.getLocale(properties);
+ for (GwtLocale search : locale.getCompleteSearchList()) {
+ String suffix = "_" + search.getAsString();
+ T obj = tryCreate(pkgName + "." + className + suffix);
+ if (obj != null) {
+ return obj;
+ }
+ obj = tryCreate(pkgName + ".impl." + className + suffix);
+ if (obj != null) {
+ return obj;
+ }
+ obj = tryCreate(pkgName + "." + className + "Impl" + suffix);
+ if (obj != null) {
+ return obj;
+ }
+ if (enclosingClass != null) {
+ obj = tryCreate(enclosingClass.getCanonicalName() + "$" + className + suffix);
+ if (obj != null) {
+ return obj;
+ }
+ }
+ }
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/user/src/com/google/gwt/core/server/ObjectNew.java b/user/src/com/google/gwt/core/server/ObjectNew.java
new file mode 100644
index 0000000..a95851c
--- /dev/null
+++ b/user/src/com/google/gwt/core/server/ObjectNew.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2012 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.core.server;
+
+import com.google.gwt.core.server.ServerGwtBridge.ClassInstantiatorBase;
+import com.google.gwt.core.server.ServerGwtBridge.Properties;
+
+/**
+ * A class instantiator that simple news the requested class. Used as a last
+ * resort.
+ */
+final class ObjectNew extends ClassInstantiatorBase {
+ @SuppressWarnings("unchecked")
+ @Override
+ public <T> T create(Class<?> clazz, Properties properties) {
+ return tryCreate((Class<T>) clazz);
+ }
+}
\ No newline at end of file
diff --git a/user/src/com/google/gwt/core/server/ServerGwtBridge.java b/user/src/com/google/gwt/core/server/ServerGwtBridge.java
new file mode 100644
index 0000000..21050a2
--- /dev/null
+++ b/user/src/com/google/gwt/core/server/ServerGwtBridge.java
@@ -0,0 +1,347 @@
+/*
+ * Copyright 2012 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.core.server;
+
+import com.google.gwt.core.shared.GWT;
+import com.google.gwt.core.shared.GWTBridge;
+import com.google.gwt.i18n.server.GwtLocaleFactoryImpl;
+import com.google.gwt.i18n.shared.GwtLocale;
+import com.google.gwt.i18n.shared.GwtLocaleFactory;
+import com.google.gwt.i18n.shared.Localizable;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Stack;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Implements GWT.* methods for the server.
+ */
+public class ServerGwtBridge extends GWTBridge {
+
+ /**
+ * Something that knows how to provide an instance of a requested class.
+ */
+ public interface ClassInstantiator {
+
+ /**
+ * Create an instance given a base class. The created class may be a
+ * subtype of the requested class.
+ *
+ * @param <T>
+ * @param baseClass
+ * @param properties
+ * @return instance or null if unable to create
+ */
+ <T> T create(Class<?> baseClass, Properties properties);
+ }
+
+ /**
+ * Helper class that provides some wrappers for looking up and instantiating
+ * a class.
+ */
+ public abstract static class ClassInstantiatorBase implements ClassInstantiator {
+
+ /**
+ * @param <T>
+ * @param clazz
+ * @return class instance or null
+ */
+ protected <T> T tryCreate(Class<T> clazz) {
+ try {
+ T obj = clazz.newInstance();
+ return obj;
+ } catch (InstantiationException e) {
+ } catch (IllegalAccessException e) {
+ }
+ return null;
+ }
+
+ /**
+ * @param <T>
+ * @param className
+ * @return class instance or null
+ */
+ protected <T> T tryCreate(String className) {
+ try {
+ Class<?> clazz = Class.forName(className);
+ @SuppressWarnings("unchecked")
+ T obj = (T) tryCreate(clazz);
+ return obj;
+ } catch (ClassNotFoundException e) {
+ }
+ return null;
+ }
+ }
+
+ /**
+ * An interface for accessing property values.
+ */
+ public interface Properties {
+
+ /**
+ * Get the value of a property.
+ *
+ * @param name
+ * @return property value, or null
+ */
+ String getProperty(String name);
+ }
+
+ /**
+ * A node in the tree of registered classes, keeping track of class
+ * instantiators for each type. {@link Object} is at the root of the
+ * tree, and children are not related to each other but inherit from
+ * their parent.
+ */
+ private static class Node {
+ public final Class<?> type;
+ public final ArrayList<Node> children;
+ public final ArrayList<ClassInstantiator> instantiators;
+
+ public Node(Class<?> type) {
+ this.type = type;
+ children = new ArrayList<Node>();
+ instantiators = new ArrayList<ClassInstantiator>();
+ }
+ }
+
+ private static class PropertiesImpl implements Properties {
+ private final Object lock = new Object[0];
+ private final Map<String, String> map = new HashMap<String, String>();
+
+ @Override
+ public String getProperty(String name) {
+ synchronized (lock) {
+ return map.get(name);
+ }
+ }
+
+ public void setProperty(String name, String value) {
+ synchronized (lock) {
+ map.put(name, value);
+ }
+ }
+ }
+
+ /**
+ * Lookup a property first in thread-local properties, then in global
+ * properties.
+ */
+ private class PropertyLookup implements Properties {
+
+ @Override
+ public String getProperty(String name) {
+ String val = threadProperties.get().getProperty(name);
+ if (val == null) {
+ val = globalProperties.getProperty(name);
+ }
+ return val;
+ }
+ }
+
+ private static Object instanceLock = new Object[0];
+ private static ServerGwtBridge instance = null;
+
+ private static final GwtLocaleFactory factory = new GwtLocaleFactoryImpl();
+
+ private static final Logger LOGGER = Logger.getLogger(ServerGwtBridge.class.getName());
+
+ /**
+ * Get the singleton {@link ServerGwtBridge} instance, creating it if
+ * necessary. The instance will be registered via
+ * {@link GWT#setBridge(GWTBridge)} and will have the default instantiators
+ * registered on it.
+ *
+ * @return the singleton {@link ServerGwtBridge} instance
+ */
+ public static ServerGwtBridge getInstance() {
+ synchronized (instanceLock) {
+ if (instance == null) {
+ instance = new ServerGwtBridge();
+ GWT.setBridge(instance);
+ }
+ return instance;
+ }
+ }
+
+ public static GwtLocale getLocale(Properties properties) {
+ String propVal = properties.getProperty("locale");
+ if (propVal == null) {
+ propVal = "default";
+ }
+ return factory.fromString(propVal);
+ }
+
+ /**
+ * Root of the tree of registered classes and their instantiators.
+ */
+ private final Node root = new Node(Object.class);
+
+ // lock for instantiators
+ private final Object instantiatorsLock = new Object[0];
+
+ private final ThreadLocal<PropertiesImpl> threadProperties;
+
+ private final PropertiesImpl globalProperties = new PropertiesImpl();
+
+ private Properties properties = new PropertyLookup();
+
+ // @VisibleForTesting
+ ServerGwtBridge() {
+ threadProperties = new ThreadLocal<PropertiesImpl>() {
+ @Override
+ protected PropertiesImpl initialValue() {
+ return new PropertiesImpl();
+ }
+ };
+
+ // register built-in instantiators
+ register(Object.class, new ObjectNew());
+ register(Localizable.class, new LocalizableInstantiator());
+ }
+
+ @Override
+ public <T> T create(Class<?> classLiteral) {
+ synchronized (instantiatorsLock) {
+ // Start at the root, and find the bottom-most node that our type
+ // is assignable to.
+ Stack<Node> stack = new Stack<Node>();
+ stack.push(root);
+ boolean found;
+ do {
+ found = false;
+ Node node = stack.peek();
+ for (Node child : node.children) {
+ if (child.type.isAssignableFrom(classLiteral)) {
+ found = true;
+ stack.push(child);
+ break;
+ }
+ }
+ } while (found);
+
+ // Try each instantiator until we find one that can create the
+ // type, walking up the tree.
+ while (!stack.isEmpty()) {
+ Node node = stack.pop();
+ for (ClassInstantiator inst : node.instantiators) {
+ T obj = inst.create(classLiteral, properties);
+ if (obj != null) {
+ return obj;
+ }
+ }
+ }
+ throw new RuntimeException("No instantiator created " + classLiteral.getCanonicalName());
+ }
+ }
+
+ /**
+ * Get the value of the named property, preferring a value specific to this
+ * thread (see {@link #setThreadProperty(String, String)}) over one that is
+ * set globally (see {@link #setGlobalProperty(String, String)}).
+ *
+ * @param property
+ * @return the property's value or null if none
+ */
+ public String getProperty(String property) {
+ return properties.getProperty(property);
+ }
+
+ @Override
+ public String getVersion() {
+ return "unknown";
+ }
+
+ @Override
+ public boolean isClient() {
+ return false;
+ }
+
+ @Override
+ public void log(String message, Throwable e) {
+ LOGGER.log(Level.INFO, message, e);
+ }
+
+ /**
+ * Register an instantiator to be used for any subtypes of a given base class.
+ *
+ * @param baseClass
+ * @param instantiator
+ */
+ public void register(Class<?> baseClass, ClassInstantiator instantiator) {
+ synchronized (instantiatorsLock) {
+ // find the deepest node which is baseClass or a supertype
+ Node node = root;
+ boolean found;
+ do {
+ found = false;
+ for (Node child : node.children) {
+ if (child.type.isAssignableFrom(baseClass)) {
+ found = true;
+ node = child;
+ }
+ }
+ } while (found);
+ // add the instantiator to the found node if it is an exact match, or
+ // create a new one if not and insert it in the proper place
+ Node nodeToAdd = node;
+ if (node.type != baseClass) {
+ nodeToAdd = new Node(baseClass);
+ // check if this node's children extend baseClass, if so we need
+ // to insert a new node between them
+ boolean needsAdd = true;
+ for (Node child : node.children) {
+ if (baseClass.isAssignableFrom(child.type)) {
+ nodeToAdd.children.add(child);
+ int childPosition = node.children.indexOf(child);
+ node.children.set(childPosition, nodeToAdd);
+ needsAdd = false;
+ break;
+ }
+ }
+ if (needsAdd) {
+ node.children.add(nodeToAdd);
+ }
+ }
+ nodeToAdd.instantiators.add(0, instantiator);
+ }
+ }
+
+ /**
+ * Set a property value globally. This value will be overridden by any
+ * thread-specific property value of the same name.
+ *
+ * @param property
+ * @param value
+ */
+ public void setGlobalProperty(String property, String value) {
+ globalProperties.setProperty(property, value);
+ }
+
+ /**
+ * Set a property value for only the current thread. This value will override
+ * any global property value of the same name.
+ *
+ * @param property
+ * @param value
+ */
+ public void setThreadProperty(String property, String value) {
+ threadProperties.get().setProperty(property, value);
+ }
+}
diff --git a/user/src/com/google/gwt/core/shared/GWT.java b/user/src/com/google/gwt/core/shared/GWT.java
new file mode 100644
index 0000000..b63747c
--- /dev/null
+++ b/user/src/com/google/gwt/core/shared/GWT.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2012 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.core.shared;
+
+/**
+ * Supports core functionality that in some cases requires direct support from
+ * the compiler and runtime systems such as runtime type information and
+ * deferred binding.
+ */
+public final class GWT {
+
+ /**
+ * Always <code>null</code> in Production Mode; in Development Mode provides
+ * the implementation for certain methods.
+ */
+ private static GWTBridge sGWTBridge = null;
+
+ /**
+ * Instantiates a class via deferred binding.
+ *
+ * <p>
+ * The argument to {@link #create(Class)} <i>must</i> be a class literal
+ * because the Production Mode compiler must be able to statically determine
+ * the requested type at compile-time. This can be tricky because using a
+ * {@link Class} variable may appear to work correctly in Development Mode.
+ * </p>
+ *
+ * @param classLiteral a class literal specifying the base class to be
+ * instantiated
+ * @return the new instance, which must be cast to the requested class
+ */
+ public static <T> T create(Class<?> classLiteral) {
+ if (sGWTBridge == null) {
+ /*
+ * In Production Mode, the compiler directly replaces calls to this method
+ * with a new Object() type expression of the correct rebound type.
+ */
+ throw new UnsupportedOperationException(
+ "ERROR: GWT.create() is only usable in client code! It cannot be called, "
+ + "for example, from server code. If you are running a unit test, "
+ + "check that your test case extends GWTTestCase and that GWT.create() "
+ + "is not called from within an initializer or constructor.");
+ } else {
+ return sGWTBridge.<T> create(classLiteral);
+ }
+ }
+
+ /**
+ * Returns the empty string when running in Production Mode, but returns a
+ * unique string for each thread in Development Mode (for example, different
+ * windows accessing the dev mode server will each have a unique id, and
+ * hitting refresh without restarting dev mode will result in a new unique id
+ * for a particular window.
+ *
+ * TODO(unnurg): Remove this function once Dev Mode rewriting classes are in
+ * gwt-dev.
+ */
+ public static String getUniqueThreadId() {
+ if (sGWTBridge != null) {
+ return sGWTBridge.getThreadUniqueID();
+ }
+ return "";
+ }
+
+ /**
+ * Get a human-readable representation of the GWT version used, or null if
+ * this is running on the client.
+ *
+ * @return a human-readable version number, such as {@code "2.5"}
+ */
+ public static String getVersion() {
+ return sGWTBridge == null ? null : sGWTBridge.getVersion();
+ }
+
+ /**
+ * Returns <code>true</code> when running inside the normal GWT environment,
+ * either in Development Mode or Production Mode. Returns <code>false</code>
+ * if this code is running in a plain JVM. This might happen when running
+ * shared code on the server, or during the bootstrap sequence of a
+ * GWTTestCase test.
+ */
+ public static boolean isClient() {
+ // Replaced with "true" by GWT compiler.
+ return sGWTBridge != null && sGWTBridge.isClient();
+ }
+
+ /**
+ * Returns <code>true</code> when running in production mode. Returns
+ * <code>false</code> when running either in development mode, or when running
+ * in a plain JVM.
+ */
+ public static boolean isProdMode() {
+ // Replaced with "true" by GWT compiler.
+ return false;
+ }
+
+ /**
+ * Determines whether or not the running program is script or bytecode.
+ */
+ public static boolean isScript() {
+ // Replaced with "true" by GWT compiler.
+ return false;
+ }
+
+ /**
+ * Logs a message to the development shell logger in Development Mode. Calls
+ * are optimized out in Production Mode.
+ */
+ public static void log(String message) {
+ log(message, null);
+ }
+
+ /**
+ * Logs a message to the development shell logger in Development Mode. Calls
+ * are optimized out in Production Mode.
+ */
+ public static void log(String message, Throwable e) {
+ if (sGWTBridge != null) {
+ sGWTBridge.log(message, e);
+ }
+ }
+
+ /**
+ * Called via reflection in Development Mode; do not ever call this method in
+ * Production Mode. May be called in server code to initialize server bridge.
+ */
+ public static void setBridge(GWTBridge bridge) {
+ sGWTBridge = bridge;
+ }
+}
diff --git a/user/test/com/google/gwt/core/CoreSuite.java b/user/test/com/google/gwt/core/CoreSuite.java
index 4be1c9e..39cb829 100644
--- a/user/test/com/google/gwt/core/CoreSuite.java
+++ b/user/test/com/google/gwt/core/CoreSuite.java
@@ -16,6 +16,7 @@
package com.google.gwt.core;
import com.google.gwt.core.client.GWTTest;
+import com.google.gwt.core.client.GwtServletBaseTest;
import com.google.gwt.core.client.HttpThrowableReporterTest;
import com.google.gwt.core.client.JavaScriptExceptionTest;
import com.google.gwt.core.client.JsArrayMixedTest;
@@ -41,6 +42,7 @@
// $JUnit-BEGIN$
suite.addTestSuite(AsyncFragmentLoaderTest.class);
+ suite.addTestSuite(GwtServletBaseTest.class);
suite.addTestSuite(GWTTest.class);
suite.addTestSuite(HttpThrowableReporterTest.class);
suite.addTestSuite(JavaScriptExceptionTest.class);
diff --git a/user/test/com/google/gwt/core/GwtServletBaseTest.gwt.xml b/user/test/com/google/gwt/core/GwtServletBaseTest.gwt.xml
new file mode 100644
index 0000000..383eb34
--- /dev/null
+++ b/user/test/com/google/gwt/core/GwtServletBaseTest.gwt.xml
@@ -0,0 +1,24 @@
+<!-- -->
+<!-- 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 -->
+<!-- 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. License for the specific language governing permissions and -->
+<!-- limitations under the License. -->
+
+<module>
+ <!-- Inherit the JUnit support -->
+ <inherits name='com.google.gwt.junit.JUnit'/>
+
+ <!-- Include client-side source for the test cases -->
+ <source path="client"/>
+
+ <!-- servlet for testing -->
+ <servlet path='/servlet' class='com.google.gwt.core.server.TestServlet'/>
+</module>
diff --git a/user/test/com/google/gwt/core/client/GwtServletBaseTest.java b/user/test/com/google/gwt/core/client/GwtServletBaseTest.java
new file mode 100644
index 0000000..965df27
--- /dev/null
+++ b/user/test/com/google/gwt/core/client/GwtServletBaseTest.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2012 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.core.client;
+
+import com.google.gwt.http.client.Request;
+import com.google.gwt.http.client.RequestBuilder;
+import com.google.gwt.http.client.RequestCallback;
+import com.google.gwt.http.client.RequestException;
+import com.google.gwt.http.client.Response;
+import com.google.gwt.junit.client.GWTTestCase;
+
+/**
+ * Test {@link com.google.gwt.core.server.GwtServletBase}.
+ */
+public class GwtServletBaseTest extends GWTTestCase {
+
+ private static final int TIMEOUT_IN_MS = 5000;
+
+ private static RequestBuilder getRequest(String urlLocale) {
+ StringBuilder url = new StringBuilder();
+ url.append(GWT.getModuleBaseURL()).append("servlet");
+ if (urlLocale != null) {
+ url.append("?locale=").append(urlLocale);
+ }
+ return new RequestBuilder(RequestBuilder.GET, url.toString());
+ }
+
+ @Override
+ public String getModuleName() {
+ return "com.google.gwt.core.GwtServletBaseTest";
+ }
+
+ public void testCookieLocaleDa() throws RequestException {
+ // there is no localization for da, so check that it falls back to default
+ checkCookieLocale("da", "default");
+ }
+
+ public void testCookieLocaleDe() throws RequestException {
+ checkCookieLocale("de", "de");
+ }
+
+ public void testCookieLocaleEnGb() throws RequestException {
+ // there is no localization for en_GB, so check that it falls back to en
+ checkCookieLocale("en_GB", "en");
+ }
+
+ public void testCookieLocaleEnUs() throws RequestException {
+ checkCookieLocale("en_US", "en_US");
+ }
+
+ public void testUrlLocaleDa() throws RequestException {
+ // there is no localization for da, so check that it falls back to default
+ checkUrlLocale("da", "default");
+ }
+
+ public void testUrlLocaleDe() throws RequestException {
+ checkUrlLocale("de", "de");
+ }
+
+ public void testUrlLocaleEnGb() throws RequestException {
+ // there is no localization for en_GB, so check that it falls back to en
+ checkUrlLocale("en_GB", "en");
+ }
+
+ public void testUrlLocaleEnUs() throws RequestException {
+ checkUrlLocale("en_US", "en_US");
+ }
+
+ public void testUrlLocaleOverridesCookie() throws RequestException {
+ RequestBuilder req = getRequest("en");
+ setCookieAndCheck("fr", "en", req);
+ }
+
+ /**
+ * @param locale
+ * @throws RequestException
+ */
+ private void checkCookieLocale(String locale, final String expected) throws RequestException {
+ RequestBuilder req = getRequest(null);
+ setCookieAndCheck(locale, expected, req);
+ }
+
+ /**
+ * @param expected
+ * @param req
+ * @throws RequestException
+ */
+ private void checkServletResponse(final String expected, RequestBuilder req)
+ throws RequestException {
+ delayTestFinish(TIMEOUT_IN_MS);
+ req.sendRequest(null, new RequestCallback() {
+ @Override
+ public void onError(Request request, Throwable exception) {
+ fail(exception.toString());
+ }
+
+ @Override
+ public void onResponseReceived(Request request, Response response) {
+ assertEquals(200, response.getStatusCode());
+ assertEquals(expected, response.getText());
+ finishTest();
+ }
+ });
+ }
+
+ /**
+ * @param locale
+ * @throws RequestException
+ */
+ private void checkUrlLocale(String locale, final String expected) throws RequestException {
+ RequestBuilder req = getRequest(locale);
+ setCookieAndCheck(null, expected, req);
+ }
+
+ private void setCookieAndCheck(String cookieLocale, final String expected, final RequestBuilder req)
+ throws RequestException {
+ if (cookieLocale == null) {
+ cookieLocale = "";
+ }
+ RequestBuilder cookieReq = new RequestBuilder(RequestBuilder.POST, GWT.getModuleBaseURL()
+ + "servlet");
+ cookieReq.sendRequest(cookieLocale, new RequestCallback() {
+ @Override
+ public void onError(Request request, Throwable exception) {
+ fail(exception.toString());
+ }
+
+ @Override
+ public void onResponseReceived(Request request, Response response) {
+ assertEquals(200, response.getStatusCode());
+ try {
+ checkServletResponse(expected, req);
+ } catch (RequestException e) {
+ fail(e.toString());
+ }
+ }
+ });
+ }
+}
diff --git a/user/test/com/google/gwt/core/server/Baz.java b/user/test/com/google/gwt/core/server/Baz.java
new file mode 100644
index 0000000..55698a5
--- /dev/null
+++ b/user/test/com/google/gwt/core/server/Baz.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2012 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.core.server;
+
+import com.google.gwt.i18n.shared.Localizable;
+
+/**
+ * Test interface for localizations.
+ */
+public interface Baz extends Localizable {
+ String locale();
+}
diff --git a/user/test/com/google/gwt/core/server/Baz_.java b/user/test/com/google/gwt/core/server/Baz_.java
new file mode 100644
index 0000000..e4021c9
--- /dev/null
+++ b/user/test/com/google/gwt/core/server/Baz_.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2012 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.core.server;
+
+/**
+ * Test implementation class for default localization of {@link Baz}.
+ */
+public class Baz_ implements Baz {
+ @Override
+ public String locale() {
+ return "default";
+ }
+}
diff --git a/user/test/com/google/gwt/core/server/Baz_en.java b/user/test/com/google/gwt/core/server/Baz_en.java
new file mode 100644
index 0000000..f23073d
--- /dev/null
+++ b/user/test/com/google/gwt/core/server/Baz_en.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2012 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.core.server;
+
+/**
+ * Test implementation class for en localization of {@link Baz}.
+ */
+public class Baz_en implements Baz {
+ @Override
+ public String locale() {
+ return "en";
+ }
+}
diff --git a/user/test/com/google/gwt/core/server/Baz_en_US.java b/user/test/com/google/gwt/core/server/Baz_en_US.java
new file mode 100644
index 0000000..35e591b
--- /dev/null
+++ b/user/test/com/google/gwt/core/server/Baz_en_US.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2012 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.core.server;
+
+/**
+ * Test implementation class for en_US localization of {@link Baz}.
+ */
+public class Baz_en_US implements Baz {
+ @Override
+ public String locale() {
+ return "en_US";
+ }
+}
diff --git a/user/test/com/google/gwt/core/server/ServerGwtBridgeTest.java b/user/test/com/google/gwt/core/server/ServerGwtBridgeTest.java
new file mode 100644
index 0000000..70e91a5
--- /dev/null
+++ b/user/test/com/google/gwt/core/server/ServerGwtBridgeTest.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright 2011 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.core.server;
+
+import com.google.gwt.core.server.ServerGwtBridge.ClassInstantiatorBase;
+import com.google.gwt.core.server.ServerGwtBridge.Properties;
+import com.google.gwt.core.shared.GWT;
+import com.google.gwt.i18n.shared.Localizable;
+
+import junit.framework.TestCase;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.logging.Formatter;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+import java.util.logging.Logger;
+import java.util.logging.StreamHandler;
+
+/**
+ * Test for {@link ServerGwtBridge}.
+ */
+public class ServerGwtBridgeTest extends TestCase {
+
+ /**
+ * Test interface to automatically find implementation classes.
+ */
+ public interface Bar {
+ }
+
+ /**
+ * Test implementation for {@link Bar}.
+ */
+ public static class BarImpl implements Bar {
+ }
+
+ /**
+ * Test interface for no implementations.
+ */
+ public interface Boo {
+ }
+
+ /**
+ * Test interface for localizations.
+ */
+ public interface Foo extends Localizable {
+ String locale();
+ }
+
+ /**
+ * Test implementation class for default localization of {@link Foo}.
+ */
+ public static class Foo_ implements Foo {
+ @Override
+ public String locale() {
+ return "default";
+ }
+ }
+
+ /**
+ * Test implementation class for en localization of {@link Foo}.
+ */
+ public static class Foo_en implements Foo {
+ @Override
+ public String locale() {
+ return "en";
+ }
+ }
+
+ /**
+ * Test implementation class for en_US localization of {@link Foo}.
+ */
+ public static class Foo_en_US implements Foo {
+ @Override
+ public String locale() {
+ return "en_US";
+ }
+ }
+
+ private final ServerGwtBridge bridge = ServerGwtBridge.getInstance();
+ private final String de = "de";
+ private final String defaultLocale = "default";
+ private final String en = "en";
+ private final String en_GB = "en_GB";
+ private final String en_US = "en_US";
+ private final String en_US_POSIX = "en_US_POSIX";
+
+ public void testBazDe() {
+ bridge.setGlobalProperty("locale", de);
+ Baz foo = bridge.create(Baz.class);
+ assertEquals("default", foo.locale());
+ }
+
+ public void testBazDefault() {
+ bridge.setGlobalProperty("locale", defaultLocale);
+ Baz foo = bridge.create(Baz.class);
+ assertEquals("default", foo.locale());
+ }
+
+ public void testBazEn() {
+ bridge.setGlobalProperty("locale", en);
+ Baz foo = bridge.create(Baz.class);
+ assertEquals("en", foo.locale());
+ }
+
+ public void testBazEnGb() {
+ bridge.setGlobalProperty("locale", en_GB);
+ Baz foo = bridge.create(Baz.class);
+ assertEquals("en", foo.locale());
+ }
+
+ public void testBazEnUs() {
+ bridge.setGlobalProperty("locale", en_US);
+ Baz foo = bridge.create(Baz.class);
+ assertEquals("en_US", foo.locale());
+ }
+
+ public void testBazEnUsPosix() {
+ bridge.setGlobalProperty("locale", en_US_POSIX);
+ Baz foo = bridge.create(Baz.class);
+ assertEquals("en_US", foo.locale());
+ }
+
+ public void testFooDe() {
+ bridge.setGlobalProperty("locale", de);
+ Foo foo = bridge.create(Foo.class);
+ assertEquals("default", foo.locale());
+ }
+
+ public void testFooDefault() {
+ bridge.setGlobalProperty("locale", defaultLocale);
+ Foo foo = bridge.create(Foo.class);
+ assertEquals("default", foo.locale());
+ }
+
+ public void testFooEn() {
+ bridge.setGlobalProperty("locale", en);
+ Foo foo = bridge.create(Foo.class);
+ assertEquals("en", foo.locale());
+ }
+
+ public void testFooEnGb() {
+ bridge.setGlobalProperty("locale", en_GB);
+ Foo foo = bridge.create(Foo.class);
+ assertEquals("en", foo.locale());
+ }
+
+ public void testFooEnUs() {
+ bridge.setGlobalProperty("locale", en_US);
+ Foo foo = bridge.create(Foo.class);
+ assertEquals("en_US", foo.locale());
+ }
+
+ public void testFooEnUsPosix() {
+ bridge.setGlobalProperty("locale", en_US_POSIX);
+ Foo foo = bridge.create(Foo.class);
+ assertEquals("en_US", foo.locale());
+ }
+
+ public void testLogging() throws IOException {
+ // setup the logger
+ Logger logger = Logger.getLogger(ServerGwtBridge.class.getName());
+ logger.setLevel(Level.INFO);
+ logger.setUseParentHandlers(false);
+ for (Handler handler : logger.getHandlers()) {
+ logger.removeHandler(handler);
+ }
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ StreamHandler handler = new StreamHandler(baos, new Formatter() {
+ private String lineSeparator = System.getProperty("line.separator", "\n");
+
+ @Override
+ public synchronized String format(LogRecord record) {
+ StringBuffer buf = new StringBuffer();
+ String msg = formatMessage(record);
+ buf.append(record.getLevel().getName()).append(": ").append(msg).append(lineSeparator);
+ if (record.getThrown() != null) {
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ record.getThrown().printStackTrace(pw);
+ pw.close();
+ buf.append(sw.toString());
+ }
+ return buf.toString();
+ }
+ });
+ logger.addHandler(handler);
+
+ // log some things
+ GWT.log("msg1");
+ Throwable t = new RuntimeException("foo");
+ t.fillInStackTrace();
+ GWT.log("msg2", t);
+ handler.flush();
+
+ // check what we logged
+ BufferedReader reader = new BufferedReader(new InputStreamReader(
+ new ByteArrayInputStream(baos.toByteArray())));
+ assertEquals("INFO: msg1", reader.readLine());
+ assertEquals("INFO: msg2", reader.readLine());
+ assertEquals(RuntimeException.class.getName() + ": foo", reader.readLine());
+ assertTrue(reader.readLine().startsWith("\tat " + ServerGwtBridgeTest.class.getName()));
+ }
+
+ /**
+ * Check that when there are multiple instantiators for a given class, the
+ * most recently registered one has priority.
+ */
+ public void testLastOverrides() {
+ ServerGwtBridge thisBridge = new ServerGwtBridge();
+ thisBridge.register(Object.class, new ClassInstantiatorBase() {
+ @SuppressWarnings("unchecked")
+ @Override
+ public <T> T create(Class<?> baseClass, Properties properties) {
+ return (T) tryCreate(BarImpl.class);
+ }
+ });
+ Object obj = thisBridge.create(Boo.class);
+ assertNotNull(obj);
+ assertTrue(obj instanceof BarImpl);
+ }
+
+ public void testObjectClass() {
+ bridge.setGlobalProperty("locale", defaultLocale);
+ Bar bar = bridge.create(BarImpl.class);
+ assertNotNull(bar);
+ assertTrue(bar instanceof BarImpl);
+ }
+
+ public void testObjectInterface() {
+ bridge.setGlobalProperty("locale", defaultLocale);
+ Bar bar = bridge.create(BarImpl.class);
+ assertNotNull(bar);
+ assertTrue(bar instanceof BarImpl);
+ }
+
+ public void testObjectInterfaceNoImpl() {
+ bridge.setGlobalProperty("locale", defaultLocale);
+ try {
+ @SuppressWarnings("unused")
+ Boo boo = bridge.create(Boo.class);
+ fail("expected exception");
+ } catch (RuntimeException expected) {
+ }
+ }
+}
diff --git a/user/test/com/google/gwt/core/server/TestServlet.java b/user/test/com/google/gwt/core/server/TestServlet.java
new file mode 100644
index 0000000..e009436
--- /dev/null
+++ b/user/test/com/google/gwt/core/server/TestServlet.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2012 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.core.server;
+
+import com.google.gwt.core.shared.GWT;
+import com.google.gwt.i18n.shared.Localizable;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * A servlet that returns a localized string based on the inferred locale.
+ */
+public class TestServlet extends GwtServletBase {
+
+ /**
+ * Test interface for server-side localization.
+ */
+ public interface Test extends Localizable {
+ String result();
+ }
+
+ /**
+ * Implementation for the default locale.
+ */
+ public static class Test_ implements Test {
+ @Override
+ public String result() {
+ return "default";
+ }
+ }
+
+ /**
+ * Implementation for the de locale.
+ */
+ public static class Test_de implements Test {
+ @Override
+ public String result() {
+ return "de";
+ }
+ }
+
+ /**
+ * Implementation for the en locale.
+ */
+ public static class Test_en implements Test {
+ @Override
+ public String result() {
+ return "en";
+ }
+ }
+
+ /**
+ * Implementation for the en_US locale.
+ */
+ public static class Test_en_US implements Test {
+ @Override
+ public String result() {
+ return "en_US";
+ }
+ }
+
+ /**
+ * Implementation for the fr locale.
+ */
+ public static class Test_fr implements Test {
+ @Override
+ public String result() {
+ return "fr";
+ }
+ }
+
+ @Override
+ public void init() throws ServletException {
+ localeCookie = "LOCALE";
+ }
+
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp)
+ throws IOException {
+ resp.setContentType("text/plain");
+ Test t = GWT.create(Test.class);
+ PrintWriter writer = resp.getWriter();
+ writer.print(t.result());
+ writer.close();
+ }
+
+ // POST is only used to set the locale cookie for the next GET
+ @Override
+ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
+ IOException {
+ BufferedReader reader = req.getReader();
+ String locale = reader.readLine();
+ reader.close();
+ resp.setHeader("Set-Cookie", "LOCALE=" + locale);
+ resp.setContentType("text/plain");
+ PrintWriter writer = resp.getWriter();
+ writer.close();
+ }
+}