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)}&#160;<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();
+  }
+}