Revision: 10143
Author:   [email protected]
Date:     Thu May  5 11:56:28 2011
Log:      Add runtime-locale support for Localizable subtypes.

This is a prerequisitive for later work moving things from being generated at compile-time to being mostly pre-generated during the CLDR import step. What can't be pre-generated is the runtime locales support, so this provides the ability to support runtime locales for pre-generated code.

Review at http://gwt-code-reviews.appspot.com/1425816

Review by: [email protected]
http://code.google.com/p/google-web-toolkit/source/detail?r=10143

Added:
 /trunk/user/test/com/google/gwt/i18n/rebind/LocalizableGeneratorTest.java
Modified:
/trunk/user/src/com/google/gwt/i18n/client/impl/cldr/LocalizedNamesImplBase.java
 /trunk/user/src/com/google/gwt/i18n/rebind/LocaleInfoGenerator.java
 /trunk/user/src/com/google/gwt/i18n/rebind/LocalizableGenerator.java
 /trunk/user/src/com/google/gwt/i18n/tools/I18NSync.java
 /trunk/user/test/com/google/gwt/i18n/I18NSuite.java

=======================================
--- /dev/null
+++ /trunk/user/test/com/google/gwt/i18n/rebind/LocalizableGeneratorTest.java Thu May 5 11:56:28 2011
@@ -0,0 +1,255 @@
+/*
+ * 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.i18n.rebind;
+
+import com.google.gwt.codegen.server.AbortablePrintWriter;
+import com.google.gwt.codegen.server.CodeGenContext;
+import com.google.gwt.codegen.server.JavaSourceWriterBuilder;
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.NotFoundException;
+import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.dev.javac.TypeOracleTestingUtils;
+import com.google.gwt.dev.javac.testing.impl.MockJavaResource;
+import com.google.gwt.dev.shell.FailErrorLogger;
+import com.google.gwt.i18n.server.GwtLocaleFactoryImpl;
+import com.google.gwt.i18n.shared.GwtLocale;
+import com.google.gwt.i18n.shared.GwtLocaleFactory;
+
+import junit.framework.TestCase;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+/**
+ * Test {@link LocaleInfoGenerator}.
+ */
+public class LocalizableGeneratorTest extends TestCase {
+
+  private static final MockJavaResource LOCALIZABLE = new MockJavaResource(
+      "com.google.gwt.i18n.shared.Localizable") {
+    @Override
+    public CharSequence getContent() {
+      StringBuffer code = new StringBuffer();
+      code.append("package com.google.gwt.i18n.shared;\n");
+      code.append("public interface Localizable { }\n");
+      return code;
+    }
+  };
+
+  private static final MockJavaResource TEST = new MockJavaResource(
+      "foo.Test") {
+    @Override
+    public CharSequence getContent() {
+      StringBuffer code = new StringBuffer();
+      code.append("package foo;\n");
+      code.append("import com.google.gwt.i18n.shared.Localizable;\n");
+      code.append("import java.util.Map;\n");
+      code.append("public interface Test extends Localizable {\n");
+      code.append("  void foo();\n");
+      code.append("  Map<String, String> bar(Map<String, String> map);\n");
+      code.append("  <T> T baz(Map<String, T> list, String key);\n");
+      code.append("}\n");
+      return code;
+    }
+  };
+
+  private static final MockJavaResource TEST_CLASS = new MockJavaResource(
+      "foo.TestClass") {
+    @Override
+    public CharSequence getContent() {
+      StringBuffer code = new StringBuffer();
+      code.append("package foo;\n");
+      code.append("import com.google.gwt.i18n.shared.Localizable;\n");
+      code.append("public class TestClass implements Localizable {\n");
+      code.append("  public void foo() {}\n");
+      code.append("  public final void fooFinal() {}\n");
+      code.append("  protected void bar() {}\n");
+      code.append("  private void baz() {}\n");
+      code.append("  void biff() {}\n");
+      code.append("}\n");
+      return code;
+    }
+  };
+
+  private static final MockJavaResource TEST_EN = new MockJavaResource(
+      "foo.Test_en") {
+    @Override
+    public CharSequence getContent() {
+      StringBuffer code = new StringBuffer();
+      code.append("package foo;\n");
+      code.append("import java.util.Map;\n");
+      code.append("public class Test_en implements Test {\n");
+      code.append("  public void foo() {}\n");
+ code.append(" public Map<String, String> bar(Map<String, String> map) { return null; }\n"); + code.append(" public <T> T baz(Map<String, T> list, String key) { return null; }\n");
+      code.append("}\n");
+      return code;
+    }
+  };
+
+  private static final MockJavaResource TEST_EN_GB = new MockJavaResource(
+      "foo.Test_en_GB") {
+    @Override
+    public CharSequence getContent() {
+      StringBuffer code = new StringBuffer();
+      code.append("package foo;\n");
+      code.append("public class Test_en_GB extends Test_en {\n");
+      code.append("  @Override public void foo() {}\n");
+      code.append("}\n");
+      return code;
+    }
+  };
+
+  private static final MockJavaResource TEST_EN_US = new MockJavaResource(
+      "foo.Test_en_US") {
+    @Override
+    public CharSequence getContent() {
+      StringBuffer code = new StringBuffer();
+      code.append("package foo;\n");
+      code.append("public class Test_en_US extends Test_en {\n");
+      code.append("  @Override public void foo() {}\n");
+      code.append("}\n");
+      return code;
+    }
+  };
+
+  private Map<String, StringWriter> bufs;
+
+  private CodeGenContext ctx;
+
+  private GwtLocaleFactory factory;
+  private JClassType test;
+
+  private JClassType testClass;
+
+  private TypeOracle typeOracle;
+
+  public void testNotOverridable() {
+    LocalizableGenerator gen = new LocalizableGenerator();
+    GwtLocale en = factory.fromString("en");
+ Map<String, Set<GwtLocale>> localeMap = new TreeMap<String, Set<GwtLocale>>();
+    String genClass = gen.generateRuntimeSelection(ctx, testClass,
+        testClass.getQualifiedSourceName(), en, localeMap);
+    assertEquals("foo.TestClass_en_runtimeSelection", genClass);
+    StringWriter buf = bufs.get("TestClass_en_runtimeSelection");
+    String genText = buf.toString();
+    assertTrue("Should have delegated foo", genText.contains("foo("));
+ assertFalse("Should not have delegated fooFinal", genText.contains("fooFinal("));
+    assertTrue("Should have delegated bar", genText.contains("bar("));
+    assertFalse("Should not have delegated baz", genText.contains("baz("));
+    assertTrue("Should have delegated biff", genText.contains("biff("));
+  }
+
+  public void testRuntimeSelection() throws IOException {
+    LocalizableGenerator gen = new LocalizableGenerator();
+    GwtLocale en = factory.fromString("en");
+    GwtLocale en_US = factory.fromString("en_US");
+    GwtLocale en_US_POSIX = factory.fromString("en_US_POSIX");
+    GwtLocale en_GB = factory.fromString("en_GB");
+    GwtLocale en_PK = factory.fromString("en_PK");
+ Map<String, Set<GwtLocale>> localeMap = new TreeMap<String, Set<GwtLocale>>(); + localeMap.put("foo.Test_en_US", new TreeSet<GwtLocale>(Arrays.asList(en_US, en_US_POSIX))); + localeMap.put("foo.Test_en_GB", new TreeSet<GwtLocale>(Arrays.asList(en_GB))); + localeMap.put("foo.Test_en", new TreeSet<GwtLocale>(Arrays.asList(en_PK))); + String genClass = gen.generateRuntimeSelection(ctx, test, test.getQualifiedSourceName(), en,
+        localeMap);
+    assertEquals("foo.Test_en_runtimeSelection", genClass);
+    StringWriter buf = bufs.get("Test_en_runtimeSelection");
+    String genText = buf.toString();
+    String ensureStartString = "void ensureInstance() {\n";
+    int ensurePos = genText.indexOf(ensureStartString);
+    assertTrue("Did not find ensureInstance", ensurePos >= 0);
+    ensurePos += ensureStartString.length();
+    String ensureEndString = "  }\n}\n";
+    int ensureEndPos = genText.length() - ensureEndString.length();
+    assertEquals(ensureEndString, genText.substring(ensureEndPos));
+    String ensureBody = genText.substring(ensurePos, ensureEndPos);
+ BufferedReader reader = new BufferedReader(new StringReader(ensureBody));
+    // skip past prolog
+    String line = reader.readLine();
+    while (!line.contains("getLocaleName()")) {
+      line = reader.readLine();
+    }
+ assertEquals("if (\"en_GB\".equals(locale)) {", reader.readLine().trim()); + assertEquals("instance = new foo.Test_en_GB();", reader.readLine().trim());
+    assertEquals("return;", reader.readLine().trim());
+    assertEquals("}", reader.readLine().trim());
+    assertEquals("if (\"en_US\".equals(locale)", reader.readLine().trim());
+ assertEquals("|| \"en_US_POSIX\".equals(locale)) {", reader.readLine().trim()); + assertEquals("instance = new foo.Test_en_US();", reader.readLine().trim());
+    assertEquals("return;", reader.readLine().trim());
+    assertEquals("}", reader.readLine().trim());
+    assertEquals("instance = new foo.Test();", reader.readLine().trim());
+    assertNull(reader.readLine());
+  }
+
+  @Override
+  protected void setUp() throws NotFoundException {
+    factory = new GwtLocaleFactoryImpl();
+    TreeLogger logger = new FailErrorLogger();
+    typeOracle = TypeOracleTestingUtils.buildStandardTypeOracleWith(
+ logger, LOCALIZABLE, TEST, TEST_EN, TEST_EN_US, TEST_EN_GB, TEST_CLASS);
+    test = typeOracle.getType("foo.Test");
+    testClass = typeOracle.getType("foo.TestClass");
+    bufs = new HashMap<String, StringWriter>();
+    ctx = new CodeGenContext() {
+ public JavaSourceWriterBuilder addClass(String pkgName, String className) {
+        return addClass(null, pkgName, className);
+      }
+
+ public JavaSourceWriterBuilder addClass(String superPath, String pkgName, String className) {
+        StringWriter buf = new StringWriter();
+        bufs.put(className, buf);
+ AbortablePrintWriter apw = new AbortablePrintWriter(new PrintWriter(buf));
+        return new JavaSourceWriterBuilder(apw, pkgName, className);
+      }
+
+      public void error(String msg) {
+        fail(msg);
+      }
+
+      public void error(String msg, Throwable cause) {
+        fail(msg);
+      }
+
+      public void error(Throwable cause) {
+        fail(cause.getMessage());
+      }
+
+      public void warn(String msg) {
+        System.out.println(msg);
+      }
+
+      public void warn(String msg, Throwable cause) {
+        System.out.println(msg);
+      }
+
+      public void warn(Throwable cause) {
+        System.out.println(cause.getMessage());
+      }
+    };
+  }
+}
=======================================
--- /trunk/user/src/com/google/gwt/i18n/client/impl/cldr/LocalizedNamesImplBase.java Fri Oct 22 12:18:01 2010 +++ /trunk/user/src/com/google/gwt/i18n/client/impl/cldr/LocalizedNamesImplBase.java Thu May 5 06:23:27 2011
@@ -52,6 +52,13 @@
       return super.getRegionNameImpl(regionCode);
     }
   }
+
+  @Override
+  protected String[] loadLikelyRegionCodes() {
+    // If this override isn't here, LocalizableGenerator-produced overrides
+    // fail with a visibility error in both javac and eclipse.
+    return super.loadLikelyRegionCodes();
+  }

   @Override
   protected final void loadNameMap() {
=======================================
--- /trunk/user/src/com/google/gwt/i18n/rebind/LocaleInfoGenerator.java Wed Jan 5 11:56:03 2011 +++ /trunk/user/src/com/google/gwt/i18n/rebind/LocaleInfoGenerator.java Thu May 5 06:23:27 2011
@@ -15,6 +15,7 @@
  */
 package com.google.gwt.i18n.rebind;

+import com.google.gwt.codegen.server.CodeGenUtils;
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.core.client.JavaScriptObject;
 import com.google.gwt.core.ext.Generator;
@@ -41,8 +42,8 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
-import java.util.Set;
 import java.util.Map.Entry;
+import java.util.Set;

 /**
* Generator used to generate an implementation of the LocaleInfoImpl class,
@@ -197,9 +198,9 @@
             displayName = displayNames.getProperty(localeName);
           }
           if (displayName != null && displayName.length() != 0) {
-            localeName = quoteQuotes(localeName);
-            displayName = quoteQuotes(displayName);
- writer.println(" nativeDisplayNamesJava.put(\"" + localeName + "\", \"" + displayName + "\");");
+            writer.println("      nativeDisplayNamesJava.put("
+                + CodeGenUtils.asStringLiteral(localeName) + ", "
+                + CodeGenUtils.asStringLiteral(displayName) + ");");
           }
         }
       }
@@ -228,12 +229,11 @@
             displayName = displayNames.getProperty(localeName);
           }
           if (displayName != null && displayName.length() != 0) {
-            localeName = quoteQuotes(localeName);
-            displayName = quoteQuotes(displayName);
             if (needComma) {
               writer.println(",");
             }
- writer.print(" \"" + localeName + "\": \"" + displayName + "\""); + writer.print(" " + CodeGenUtils.asStringLiteral(localeName) + ": "
+                + CodeGenUtils.asStringLiteral(displayName));
             needComma = true;
           }
         }
@@ -280,7 +280,7 @@
       if (queryParam != null) {
         writer.println("@Override");
         writer.println("public String getLocaleQueryParam() {");
-        writer.println("  return \"" + quoteQuotes(queryParam) + "\";");
+ writer.println(" return " + CodeGenUtils.asStringLiteral(queryParam) + ";");
         writer.println("}");
         writer.println();
       }
@@ -288,7 +288,7 @@
       if (cookie != null) {
         writer.println("@Override");
         writer.println("public String getLocaleCookieName() {");
-        writer.println("  return \"" + quoteQuotes(cookie) + "\";");
+ writer.println(" return " + CodeGenUtils.asStringLiteral(cookie) + ";");
         writer.println("}");
         writer.println();
       }
@@ -298,14 +298,14 @@
       // Avoid warnings for trying to create the same type multiple times
       GeneratorContext subContext = new CachedGeneratorContext(context);
generateConstantsLookup(logger, subContext, writer, localizableGenerator,
-          runtimeLocales, locale,
+          runtimeLocales, localeUtils, locale,
           "com.google.gwt.i18n.client.impl.cldr.DateTimeFormatInfoImpl");
       writer.println("}");
       writer.println();
       writer.println("@Override");
       writer.println("public NumberConstants getNumberConstants() {");
generateConstantsLookup(logger, subContext, writer, localizableGenerator,
-          runtimeLocales, locale,
+          runtimeLocales, localeUtils, locale,
           "com.google.gwt.i18n.client.constants.NumberConstantsImpl");
       writer.println("}");
       writer.commit(logger);
@@ -319,22 +319,23 @@
    * @param writer
    * @param localizableGenerator
    * @param runtimeLocales
+   * @param localeUtils
    * @param locale
    * @throws UnableToCompleteException
    */
   private void generateConstantsLookup(TreeLogger logger,
       GeneratorContext context, SourceWriter writer,
LocalizableGenerator localizableGenerator, Set<GwtLocale> runtimeLocales,
-      GwtLocale locale, String typeName)
+      LocaleUtils localeUtils, GwtLocale locale, String typeName)
       throws UnableToCompleteException {
     writer.indent();
     boolean fetchedRuntimeLocale = false;
Map<String, Set<GwtLocale>> localeMap = new HashMap<String, Set<GwtLocale>>();
     generateOneLocale(logger, context, localizableGenerator, typeName,
-        localeMap, locale);
+        localeUtils, localeMap, locale);
     for (GwtLocale runtimeLocale : runtimeLocales) {
       generateOneLocale(logger, context, localizableGenerator, typeName,
-          localeMap, runtimeLocale);
+          localeUtils, localeMap, runtimeLocale);
     }
     if (localeMap.size() > 1) {
       for (Entry<String, Set<GwtLocale>> entry : localeMap.entrySet()) {
@@ -372,16 +373,17 @@
    * @param context
    * @param localizableGenerator
    * @param typeName
+   * @param localeUtils
    * @param localeMap
    * @param locale
    * @throws UnableToCompleteException
    */
private void generateOneLocale(TreeLogger logger, GeneratorContext context,
       LocalizableGenerator localizableGenerator, String typeName,
-      Map<String, Set<GwtLocale>> localeMap, GwtLocale locale)
+ LocaleUtils localeUtils, Map<String, Set<GwtLocale>> localeMap, GwtLocale locale)
       throws UnableToCompleteException {
     String generatedClass = localizableGenerator.generate(logger, context,
-        typeName, locale.toString());
+        typeName, localeUtils, locale);
     if (generatedClass == null) {
       logger.log(TreeLogger.ERROR, "Failed to generate " + typeName
           + " in locale " + locale.toString());
@@ -395,8 +397,4 @@
     }
     locales.add(locale);
   }
-
-  private String quoteQuotes(String val) {
-    return val.replace("\"", "\\\"");
-  }
-}
+}
=======================================
--- /trunk/user/src/com/google/gwt/i18n/rebind/LocalizableGenerator.java Thu Sep 9 08:24:17 2010 +++ /trunk/user/src/com/google/gwt/i18n/rebind/LocalizableGenerator.java Thu May 5 06:23:27 2011
@@ -15,29 +15,84 @@
  */
 package com.google.gwt.i18n.rebind;

-import com.google.gwt.core.ext.BadPropertyValueException;
+import com.google.gwt.codegen.rebind.GwtCodeGenContext;
+import com.google.gwt.codegen.server.CodeGenContext;
+import com.google.gwt.codegen.server.CodeGenUtils;
+import com.google.gwt.codegen.server.JavaSourceWriterBuilder;
+import com.google.gwt.codegen.server.SourceWriter;
 import com.google.gwt.core.ext.Generator;
 import com.google.gwt.core.ext.GeneratorContext;
 import com.google.gwt.core.ext.PropertyOracle;
-import com.google.gwt.core.ext.SelectionProperty;
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JMethod;
+import com.google.gwt.core.ext.typeinfo.JParameter;
+import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
+import com.google.gwt.core.ext.typeinfo.JType;
 import com.google.gwt.core.ext.typeinfo.NotFoundException;
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
 import com.google.gwt.i18n.client.Constants;
 import com.google.gwt.i18n.client.ConstantsWithLookup;
+import com.google.gwt.i18n.client.LocaleInfo;
 import com.google.gwt.i18n.client.Messages;
 import com.google.gwt.i18n.shared.GwtLocale;

+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
 /**
* Generator used to bind classes extending the <code>Localizable</code> and
  * <code>Constants</code> interfaces.
  */
 public class LocalizableGenerator extends Generator {
+
   /**
-   * GWT method to override default use of method name as resource key.
+ * Comparator for methods - sorts first by visibility, then name, then number
+   * of arguments, then argument names.
    */
+  public class JMethodComparator implements Comparator<JMethod> {
+
+    public int compare(JMethod a, JMethod b) {
+      if (a.isPublic() != b.isPublic()) {
+        return a.isPublic() ? -1 : 1;
+      }
+      if (a.isDefaultAccess() != b.isDefaultAccess()) {
+        return a.isDefaultAccess() ? -1 : 1;
+      }
+      if (a.isProtected() != b.isProtected()) {
+        return a.isProtected() ? -1 : 1;
+      }
+      int c = a.getName().compareTo(b.getName());
+      if (c != 0) {
+        return c;
+      }
+      JParameter[] aParams = a.getParameters();
+      JParameter[] bParams = b.getParameters();
+      c = aParams.length - bParams.length;
+      if (c != 0) {
+        return c;
+      }
+      for (int i = 0; i < aParams.length; ++i) {
+        c = aParams[i].getName().compareTo(bParams[i].getName());
+        if (c != 0) {
+          return c;
+        }
+      }
+      return 0;
+    }
+  }
+
+   /**
+    * Obsolete comment for GWT metadata - needs to be removed once all
+    * references have been removed.
+    */
   public static final String GWT_KEY = "gwt.key";

   public static final String CONSTANTS_NAME = Constants.class.getName();
@@ -46,11 +101,6 @@

   public static final String MESSAGES_NAME = Messages.class.getName();

-  /**
-   * The token representing the locale property controlling Localization.
-   */
-  private static final String PROP_LOCALE = "locale";
-
private LocalizableLinkageCreator linkageCreator = new LocalizableLinkageCreator();

   /**
@@ -59,7 +109,8 @@
    * @param logger error logger
    * @param context generator context
    * @param typeName target type name
-   * @return generated class name
+ * @return a fully-qualified classname of the generated implementation, or
+   *     null to use the base class
    * @throws UnableToCompleteException
    */
   @Override
@@ -67,38 +118,26 @@
       String typeName) throws UnableToCompleteException {
     // Get the current locale
     PropertyOracle propertyOracle = context.getPropertyOracle();
-    String locale;
-    try {
-      // Look at the code for the "locale" property provider in
-      // I18N.gwt.xml to see the possible values for the locale
-      // property. Basically,
-      //
- // 1) If the locale is specified by the user using a request parameter
-      // or a meta tag, AND
-      // 2) The locale matches or is a parent of one of the locales
-      // exposed in the application's gwt.xml file, THEN
-      //
-      // the value returned by getPropertyValue() will be:
-      //
-      // a) the locale specified by the user, OR
-      // b) the parent locale, if an exact match between the user-specified
-      // locale and the exposed locales cannot be found
-      //
-      // If the locale is not specified by the user as a request parameter
-      // or via a meta tag, or if the locale is formatted incorrectly,
-      // getPropertyValue() will return "default".
-      SelectionProperty localeProp
-          = propertyOracle.getSelectionProperty(logger, PROP_LOCALE);
-      locale = localeProp.getCurrentValue();
-    } catch (BadPropertyValueException e) {
-      logger.log(TreeLogger.ERROR, "Could not parse specified locale", e);
-      throw new UnableToCompleteException();
-    }
-    return generate(logger, context, typeName, locale);
+ LocaleUtils localeUtils = LocaleUtils.getInstance(logger, propertyOracle,
+        context);
+    GwtLocale locale = localeUtils.getCompileLocale();
+    return generate(logger, context, typeName, localeUtils, locale);
   }

+  /**
+   * Generate an implementation for a given type.
+   *
+   * @param logger
+   * @param context
+   * @param typeName
+   * @param localeUtils
+   * @param locale
+ * @return a fully-qualified classname of the generated implementation, or
+   *     null to use the base class
+   * @throws UnableToCompleteException
+   */
   public final String generate(TreeLogger logger, GeneratorContext context,
- String typeName, String localeName) throws UnableToCompleteException { + String typeName, LocaleUtils localeUtils, GwtLocale locale) throws UnableToCompleteException {
     TypeOracle typeOracle = context.getTypeOracle();
     JClassType targetClass;
     try {
@@ -108,14 +147,205 @@
       throw new UnableToCompleteException();
     }

- GwtLocale locale = LocaleUtils.getLocaleFactory().fromString(localeName);
-
// Link current locale and interface type to correct implementation class. String generatedClass = AbstractLocalizableImplCreator.generateConstantOrMessageClass(
         logger, context, locale, targetClass);
     if (generatedClass != null) {
       return generatedClass;
     }
-    return linkageCreator.linkWithImplClass(logger, targetClass, locale);
+
+    // Now that we know it is a regular Localizable class, handle runtime
+    // locales
+    Set<GwtLocale> runtimeLocales = localeUtils.getRuntimeLocales();
+ String returnedClass = linkageCreator.linkWithImplClass(logger, targetClass, locale);
+    if (!runtimeLocales.isEmpty()) {
+ Map<String, Set<GwtLocale>> localeMap = new TreeMap<String, Set<GwtLocale>>();
+      Set<GwtLocale> localeSet = new TreeSet<GwtLocale>();
+      localeSet.add(locale);
+      localeMap.put(returnedClass, localeSet);
+      for (GwtLocale rtLocale : runtimeLocales) {
+ String rtClass = linkageCreator.linkWithImplClass(logger, targetClass, rtLocale);
+        localeSet = localeMap.get(rtClass);
+        if (localeSet == null) {
+          localeSet = new TreeSet<GwtLocale>();
+          localeMap.put(rtClass, localeSet);
+        }
+        localeSet.add(rtLocale);
+      }
+      if (localeMap.size() > 1) {
+        CodeGenContext genCtx = new GwtCodeGenContext(logger, context);
+ returnedClass = generateRuntimeSelection(genCtx, targetClass, returnedClass, locale,
+            localeMap);
+      }
+    }
+
+    return returnedClass;
+  }
+
+  /**
+ * Generate a runtime-selection implementation of the target class if needed, + * delegating all overridable methods to an instance chosen at runtime based
+   * on the map of locales to implementing classes.
+   *
+   * @param ctx code generator context
+   * @param targetClass class being GWT.create'd
+   * @param defaultClass the default implementation to use
+   * @param locale compile-time locale for this runtime selection
+ * @param localeMap map of target class names to the set of locales that are + * mapped to that implementation (for deterministic code generation, both
+   *     the map and set should be ordered)
+ * @return fully qualified classname of the runtime selection implementation
+   */
+  // @VisibleForTesting
+ String generateRuntimeSelection(CodeGenContext ctx, JClassType targetClass, String defaultClass,
+      GwtLocale locale, Map<String, Set<GwtLocale>> localeMap) {
+ String className = targetClass.getName().replace('.', '_') + '_' + locale.getAsString()
+        + "_runtimeSelection";
+    String pkgName = targetClass.getPackage().getName();
+    JavaSourceWriterBuilder builder = ctx.addClass(pkgName, className);
+    if (builder != null) {
+ writeRuntimeSelection(builder, targetClass, defaultClass, locale, localeMap);
+    }
+    return pkgName + '.' + className;
+  }
+
+  /**
+ * Generate a runtime-selection implementation of the target class, delegating + * all overridable methods to an instance chosen at runtime based on the map
+   * of locales to implementing classes.
+   *
+   * @param builder source writer builder
+   * @param targetClass class being GWT.create'd
+   * @param defaultClass the default implementation to use
+   * @param locale compile-time locale for this runtime selection
+ * @param localeMap map of target class names to the set of locales that are + * mapped to that implementation (for deterministic code generation, both
+   *     the map and set should be ordered)
+   */
+  // @VisibleForTesting
+ void writeRuntimeSelection(JavaSourceWriterBuilder builder, JClassType targetClass, + String defaultClass, GwtLocale locale, Map<String, Set<GwtLocale>> localeMap) {
+    boolean isInterface = targetClass.isInterface() != null;
+    if (isInterface) {
+ builder.addImplementedInterface(targetClass.getQualifiedSourceName());
+    } else {
+      builder.setSuperclass(targetClass.getQualifiedSourceName());
+    }
+    SourceWriter writer = builder.createSourceWriter();
+    writer.println();
+ writer.println("private " + targetClass.getQualifiedSourceName() + " instance;");
+    for (JMethod method : collectOverridableMethods(targetClass)) {
+      writer.println();
+      if (!isInterface) {
+        writer.println("@Override");
+      }
+ writer.println(method.getReadableDeclaration(false, true, true, true, true) + " {");
+      writer.indent();
+      writer.println("ensureInstance();");
+      if (method.getReturnType() != JPrimitiveType.VOID) {
+        writer.print("return ");
+      }
+      writer.print("instance." + method.getName() + '(');
+      boolean first = true;
+      for (JParameter param : method.getParameters()) {
+        if (first) {
+          first = false;
+        } else {
+          writer.print(", ");
+        }
+        writer.print(param.getName());
+      }
+      writer.println(");");
+      writer.outdent();
+      writer.println("}");
+    }
+    writer.println();
+    writer.println("private void ensureInstance() {");
+    writer.indent();
+    writer.println("if (instance != null) {");
+    writer.indentln("return;");
+    writer.println("}");
+    writer.println("String locale = " + LocaleInfo.class.getCanonicalName()
+        + ".getCurrentLocale().getLocaleName();");
+ String targetClassName = targetClass.getQualifiedSourceName() + '_' + locale.getAsString();
+    for (Map.Entry<String, Set<GwtLocale>> entry : localeMap.entrySet()) {
+      String implClassName = entry.getKey();
+ if (defaultClass.equals(implClassName) || targetClassName.equals(implClassName)) {
+        continue;
+      }
+      String prefix = "if (";
+      for (GwtLocale match : entry.getValue()) {
+ writer.print(prefix + CodeGenUtils.asStringLiteral(match.toString()) + ".equals(locale)");
+        prefix = "\n    || ";
+      }
+      writer.println(") {");
+      writer.indent();
+      writer.println("instance = new " + implClassName + "();");
+      writer.println("return;");
+      writer.outdent();
+      writer.println("}");
+    }
+    writer.println("instance = new " + defaultClass + "();");
+    writer.outdent();
+    writer.println("}");
+    writer.close();
+  }
+
+  /**
+   * @param targetClass
+ * @return a set of overrideable methods, in the order they should appear in
+   *     generated source
+   */
+ private TreeSet<JMethod> collectOverridableMethods(JClassType targetClass) { + TreeSet<JMethod> overrides = new TreeSet<JMethod>(new JMethodComparator());
+    Set<String> seenSignatures = new HashSet<String>();
+    // collect methods from superclass until we get to object
+ for (JClassType clazz = targetClass; clazz != null; clazz = clazz.getSuperclass()) {
+      if ("java.lang.Object".equals(clazz.getQualifiedSourceName())) {
+        break;
+      }
+      for (JMethod method : clazz.getMethods()) {
+        String signature = getSignature(method);
+        if (!seenSignatures.contains(signature)) {
+          seenSignatures.add(signature);
+ if (!method.isPrivate() && !method.isFinal() && !method.isStatic()) {
+            overrides.add(method);
+          }
+        }
+      }
+    }
+    // collect methods from superinterfaces until hitting Localizable
+    ArrayList<JClassType> todo = new ArrayList<JClassType>();
+    todo.addAll(Arrays.asList(targetClass.getImplementedInterfaces()));
+    while (!todo.isEmpty()) {
+      JClassType clazz = todo.remove(0);
+      for (JMethod method : clazz.getMethods()) {
+        String signature = getSignature(method);
+        if (!seenSignatures.contains(signature)) {
+          seenSignatures.add(signature);
+ if (!method.isPrivate() && !method.isFinal() && !method.isStatic()) {
+            overrides.add(method);
+          }
+        }
+      }
+      if (!"Localizable".equals(clazz.getSimpleSourceName())) {
+        todo.addAll(Arrays.asList(clazz.getImplementedInterfaces()));
+      }
+    }
+    return overrides;
+  }
+
+  /**
+   * @param method
+   * @return JNI signature of the method
+   */
+  private String getSignature(JMethod method) {
+    StringBuilder buf = new StringBuilder();
+    buf.append(method.getName()).append('(');
+    for (JParameter param : method.getParameters()) {
+      JType type = param.getType();
+      buf.append(type.getJNISignature());
+    }
+    return buf.append(')').toString();
   }
 }
=======================================
--- /trunk/user/src/com/google/gwt/i18n/tools/I18NSync.java Thu Jul 29 09:26:40 2010 +++ /trunk/user/src/com/google/gwt/i18n/tools/I18NSync.java Thu May 5 06:23:27 2011
@@ -21,7 +21,6 @@
 import com.google.gwt.i18n.client.Messages;
 import com.google.gwt.i18n.rebind.AbstractLocalizableInterfaceCreator;
 import com.google.gwt.i18n.rebind.ConstantsInterfaceCreator;
-import com.google.gwt.i18n.rebind.LocalizableGenerator;
 import com.google.gwt.i18n.rebind.MessagesInterfaceCreator;
 import com.google.gwt.util.tools.ArgHandlerExtra;
 import com.google.gwt.util.tools.ArgHandlerString;
@@ -117,11 +116,6 @@
       return true;
     }
   }
-
-  /**
-   * Created Key.
-   */
-  public static final String ID = "@" + LocalizableGenerator.GWT_KEY + " ";

   /**
    * Creates a <code>Constants</code> interface from a class name. The
=======================================
--- /trunk/user/test/com/google/gwt/i18n/I18NSuite.java Mon May 2 20:14:38 2011 +++ /trunk/user/test/com/google/gwt/i18n/I18NSuite.java Thu May 5 06:23:27 2011
@@ -47,6 +47,7 @@
 import com.google.gwt.i18n.client.TimeZoneInfoTest;
 import com.google.gwt.i18n.client.TimeZoneTest;
 import com.google.gwt.i18n.rebind.LocaleUtilsTest;
+import com.google.gwt.i18n.rebind.LocalizableGeneratorTest;
 import com.google.gwt.i18n.server.GwtLocaleTest;
 import com.google.gwt.i18n.server.MessageFormatParserTest;
 import com.google.gwt.i18n.server.PropertyCatalogFactoryTest;
@@ -99,6 +100,7 @@
     suite.addTestSuite(LocaleInfo_ar_Test.class);
     suite.addTestSuite(LocaleInfoTest.class);
     suite.addTestSuite(LocaleUtilsTest.class);
+    suite.addTestSuite(LocalizableGeneratorTest.class);
     suite.addTestSuite(LocalizedNames_default_Test.class);
     suite.addTestSuite(LocalizedNames_en_Test.class);
     suite.addTestSuite(MessageFormatParserTest.class);

--
http://groups.google.com/group/Google-Web-Toolkit-Contributors

Reply via email to