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