Changes to make GWT i18n support "real" default locales.  Introduces a .gwt.xml tag <set-property-fallback name="prop" value="chosenvalue"/>, which can be used in property providers as a template substitution for /*-FALLBACK-*/.  In i18n, this is used to select a "real" locale to be returned in situations when "default" would otherwise have been used.  Also modified AbstractLocalizableImplCreator so that generators running in GwtLocale.DEFAULT will run on this "real" locale, even if <set-property> has been used so that the literal "default" is not being generated.

Review by: jat

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@5568 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/core/ext/SelectionProperty.java b/dev/core/src/com/google/gwt/core/ext/SelectionProperty.java
index fda8cfc..ba1fca6 100644
--- a/dev/core/src/com/google/gwt/core/ext/SelectionProperty.java
+++ b/dev/core/src/com/google/gwt/core/ext/SelectionProperty.java
@@ -19,7 +19,10 @@
 import java.util.SortedSet;
 
 /**
- * A named deferred binding (property, value) pair.
+ * A named deferred binding (property, value) pair for use in generators.
+ * 
+ * @see com.google.gwt.core.ext.linker.SelectionProperty A similarly-named
+ * analog for linkers.
  */
 public interface SelectionProperty {
   
@@ -38,9 +41,15 @@
   String getCurrentValue();
 
   /**
+   * Gets the fallback value for the property
+   * @return the fallback, or ""
+   */
+  String getFallbackValue(); 
+
+  /**
    * Returns the possible values for the property in sorted order.
    * 
    * @return a SortedSet of Strings containing the possible property values.
    */
-  SortedSet<String> getPossibleValues(); 
+  SortedSet<String> getPossibleValues();
 }
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/SelectionProperty.java b/dev/core/src/com/google/gwt/core/ext/linker/SelectionProperty.java
index 4c6aaac..e0f17bf 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/SelectionProperty.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/SelectionProperty.java
@@ -22,6 +22,9 @@
  * may not have a single value applied across all permutations.
  * 
  * SelectionProperty implementations must support object identity comparisons.
+ * 
+ * @see com.google.gwt.core.ext.SelectionProperty A similarly-named interface used
+ * in generators.
  */
 public interface SelectionProperty {
   /**
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardSelectionProperty.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardSelectionProperty.java
index 5f71786..b3ed31f 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardSelectionProperty.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardSelectionProperty.java
@@ -28,6 +28,8 @@
  * {@link BindingProperty}.
  */
 public class StandardSelectionProperty implements SelectionProperty {
+  private static final String FALLBACK_TOKEN = "/*-FALLBACK-*/";
+
   private final String activeValue;
   private final String name;
   private final String provider;
@@ -40,7 +42,9 @@
       activeValue = null;
     }
     name = p.getName();
-    provider = p.getProvider() == null ? null : p.getProvider().getBody();
+    String fallback = p.getFallback();
+    provider = p.getProvider() == null ? null : 
+        p.getProvider().getBody().replace(FALLBACK_TOKEN, fallback);
     values = Collections.unmodifiableSortedSet(new TreeSet<String>(
         Arrays.asList(p.getDefinedValues())));
   }
diff --git a/dev/core/src/com/google/gwt/dev/cfg/BindingProperty.java b/dev/core/src/com/google/gwt/dev/cfg/BindingProperty.java
index af8970f..57a03c3 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/BindingProperty.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/BindingProperty.java
@@ -27,9 +27,12 @@
  */
 public class BindingProperty extends Property {
 
+  private static final String EMPTY = "";
+  
   private SortedSet<String> allowedValues;
   private final SortedSet<String> definedValues = new TreeSet<String>();
   private PropertyProvider provider;
+  private String fallback;
 
   {
     /*
@@ -41,6 +44,7 @@
 
   public BindingProperty(String name) {
     super(name);
+    fallback = EMPTY;
   }
 
   public void addDefinedValue(String newValue) {
@@ -61,6 +65,9 @@
     return definedValues.toArray(new String[definedValues.size()]);
   }
 
+  public String getFallback() {
+    return fallback;
+  }
   public PropertyProvider getProvider() {
     return provider;
   }
@@ -95,6 +102,10 @@
     allowedValues = temp;
   }
 
+  public void setFallback(String token) {
+    fallback = token;
+  }
+
   public void setProvider(PropertyProvider provider) {
     this.provider = provider;
   }
diff --git a/dev/core/src/com/google/gwt/dev/cfg/ModuleDefSchema.java b/dev/core/src/com/google/gwt/dev/cfg/ModuleDefSchema.java
index a3520f5..ac273e5 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/ModuleDefSchema.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/ModuleDefSchema.java
@@ -107,6 +107,10 @@
 
     protected final String __set_property_2_value = null;
 
+    protected final String __set_property_fallback_1_name = null;
+
+    protected final String __set_property_fallback_2_value = null;
+
     protected final String __source_1_path = "";
 
     protected final String __source_2_includes = "";
@@ -497,6 +501,25 @@
       return null;
     }
 
+    protected Schema __set_property_fallback_begin(BindingProperty prop,
+        PropertyValue value) throws UnableToCompleteException {
+      boolean error = true;
+      for (String possibleValue : prop.getAllowedValues()) {
+        if (possibleValue.equals(value.token)) {
+          error = false;
+          break;
+        }
+      }
+      if (error) {
+        logger.log(TreeLogger.ERROR, "The fallback value '" + value.token
+            + "' was not previously defined for property '"
+            + prop.getName() + "'");
+        throw new UnableToCompleteException();
+      }
+      prop.setFallback(value.token);
+      return null;
+    }
+
     /**
      * Indicates which subdirectories contain translatable source without
      * necessarily adding a sourcepath entry.
diff --git a/dev/core/src/com/google/gwt/dev/cfg/StaticPropertyOracle.java b/dev/core/src/com/google/gwt/dev/cfg/StaticPropertyOracle.java
index c257f62..0d550aa 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/StaticPropertyOracle.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/StaticPropertyOracle.java
@@ -138,6 +138,7 @@
     for (int i = 0; i < orderedProps.length; i++) {
       final BindingProperty prop = orderedProps[i];
       final String name = prop.getName();
+      final String fallback = prop.getFallback();
       if (name.equals(propertyName)) {
         final String value = orderedPropValues[i];
         String[] values = prop.getDefinedValues();
@@ -151,6 +152,10 @@
             return value;
           }
 
+          public String getFallbackValue() {
+            return fallback;
+          }
+
           public String getName() {
             return name;
           }
diff --git a/dev/core/src/com/google/gwt/dev/shell/ModuleSpacePropertyOracle.java b/dev/core/src/com/google/gwt/dev/shell/ModuleSpacePropertyOracle.java
index 58440f0..b744f20 100644
--- a/dev/core/src/com/google/gwt/dev/shell/ModuleSpacePropertyOracle.java
+++ b/dev/core/src/com/google/gwt/dev/shell/ModuleSpacePropertyOracle.java
@@ -119,6 +119,7 @@
       final BindingProperty cprop = (BindingProperty) prop;
       final String name = cprop.getName();
       final String value = computePropertyValue(logger, propertyName, cprop);
+      final String fallback = cprop.getFallback();
       final SortedSet<String> possibleValues = new TreeSet<String>();
       for (String v : cprop.getDefinedValues()) {
         possibleValues.add(v);
@@ -129,6 +130,10 @@
           return value;
         }
 
+        public String getFallbackValue() {
+          return fallback;
+        }
+
         public String getName() {
           return name;
         }
diff --git a/dev/core/test/com/google/gwt/core/ext/linker/impl/StandardSelectionPropertyTest.java b/dev/core/test/com/google/gwt/core/ext/linker/impl/StandardSelectionPropertyTest.java
new file mode 100644
index 0000000..2fafd95
--- /dev/null
+++ b/dev/core/test/com/google/gwt/core/ext/linker/impl/StandardSelectionPropertyTest.java
@@ -0,0 +1,65 @@
+/*

+ * Copyright 2009 Google Inc.

+ * 

+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not

+ * use this file except in compliance with the License. You may obtain a copy of

+ * the License at

+ * 

+ * http://www.apache.org/licenses/LICENSE-2.0

+ * 

+ * Unless required by applicable law or agreed to in writing, software

+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT

+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the

+ * License for the specific language governing permissions and limitations under

+ * the License.

+ */

+package com.google.gwt.core.ext.linker.impl;

+

+import com.google.gwt.dev.cfg.BindingProperty;

+import com.google.gwt.dev.cfg.PropertyProvider;

+

+import junit.framework.TestCase;

+

+/**

+ * Tests for {@link StandardSelectionProperty}.

+ */

+public class StandardSelectionPropertyTest extends TestCase {

+

+  private static final String FBV = "FBV";

+

+  private static final String PROVIDER_MULTIFALLBACK =

+      "provider text with fallback=/*-FALLBACK-*/, repeated /*-FALLBACK-*//*-FALLBACK-*/";

+  private static final String PROVIDER_MULTIFALLBACK_EMPTY =

+    "provider text with fallback=, repeated ";

+  private static final String PROVIDER_MULTIFALLBACK_FBV =

+    "provider text with fallback=FBV, repeated FBVFBV";

+

+  private static final String PROVIDER_NOFALLBACK = "provider text without fallback";

+

+  public void testNoFallback() {

+    BindingProperty bp = new BindingProperty("doesNotUseFallback");

+    PropertyProvider provider = new PropertyProvider(PROVIDER_NOFALLBACK);

+    bp.setProvider(provider);

+    StandardSelectionProperty property = new StandardSelectionProperty(bp);

+    assertEquals(PROVIDER_NOFALLBACK, property.getPropertyProvider());

+

+    provider = new PropertyProvider(PROVIDER_MULTIFALLBACK);

+    bp.setProvider(provider);

+    property = new StandardSelectionProperty(bp);

+    assertEquals(PROVIDER_MULTIFALLBACK_EMPTY, property.getPropertyProvider());

+  }

+

+  public void testWithFallback() {

+    BindingProperty bp = new BindingProperty("doesUseFallback");

+    bp.setFallback(FBV);

+    PropertyProvider provider = new PropertyProvider(PROVIDER_NOFALLBACK);

+    bp.setProvider(provider);

+    StandardSelectionProperty property = new StandardSelectionProperty(bp);

+    assertEquals(PROVIDER_NOFALLBACK, property.getPropertyProvider());

+

+    provider = new PropertyProvider(PROVIDER_MULTIFALLBACK);

+    bp.setProvider(provider);

+    property = new StandardSelectionProperty(bp);

+    assertEquals(PROVIDER_MULTIFALLBACK_FBV, property.getPropertyProvider());

+  }

+}

diff --git a/samples/i18n/src/com/google/gwt/sample/i18n/I18N.gwt.xml b/samples/i18n/src/com/google/gwt/sample/i18n/I18N.gwt.xml
index ae0afae..cf6ecc9 100644
--- a/samples/i18n/src/com/google/gwt/sample/i18n/I18N.gwt.xml
+++ b/samples/i18n/src/com/google/gwt/sample/i18n/I18N.gwt.xml
@@ -14,7 +14,8 @@
 
 <module rename-to="i18n">
 	<inherits name="com.google.gwt.user.User" />
-    <inherits name="com.google.gwt.i18n.I18N"/>
+  <inherits name="com.google.gwt.i18n.I18N"/>
 	<entry-point class="com.google.gwt.sample.i18n.client.I18N" />
 	<extend-property name="locale" values="en,fr" />
+	<set-property-fallback name="locale" value="fr"/>
 </module>
diff --git a/user/src/com/google/gwt/i18n/I18N.gwt.xml b/user/src/com/google/gwt/i18n/I18N.gwt.xml
index 9ce67a7..8829121 100644
--- a/user/src/com/google/gwt/i18n/I18N.gwt.xml
+++ b/user/src/com/google/gwt/i18n/I18N.gwt.xml
@@ -23,6 +23,7 @@
     <![CDATA[
       try {
       var locale;
+      var defaultLocale = "/*-FALLBACK-*/" || 'default';
 
       // Look for the locale as a url argument
       if (locale == null) {
@@ -48,22 +49,23 @@
         // Look for an override computed by other means in the selection script
         locale = $wnd['__gwt_Locale'];
       } else {
-        $wnd['__gwt_Locale'] = locale || 'default';
+        $wnd['__gwt_Locale'] = locale || defaultLocale;
       }
       
       if (locale == null) {
-        return "default";
+        return defaultLocale;
       }
 
       while (!__gwt_isKnownPropertyValue("locale",  locale)) {
         var lastIndex = locale.lastIndexOf("_");
         if (lastIndex == -1) {
-              locale = "default";
+          locale = defaultLocale;
           break;
         } else {
           locale = locale.substring(0,lastIndex);
         }
       }
+
       return locale;
     } catch(e){
       alert("Unexpected exception in locale detection, using default: " + e);
@@ -91,4 +93,16 @@
    -->
   <define-configuration-property name="runtime.locales" is-multi-valued="true"/>
   <set-configuration-property name="runtime.locales" value=""/>
+
+  <!--
+      A "real" locale to be served by default (i.e. if the browser either
+      doesn't have a requested locale, or it cannot be satisfied with any
+      of the available locales).  The non-internationalized value "default"
+      is actually deficient for any actual locale, so users should set this
+      when then either <extend-property> or <define-property> user.agents for
+      their available translations.  You should still have a locale named
+      "default" (because various tools expect that to be valid), but it will
+      be generated as the locale specified here.
+  -->
+  <set-property-fallback name="locale" value="default"/>
 </module>
diff --git a/user/src/com/google/gwt/i18n/rebind/AbstractLocalizableImplCreator.java b/user/src/com/google/gwt/i18n/rebind/AbstractLocalizableImplCreator.java
index b562788..74460f6 100644
--- a/user/src/com/google/gwt/i18n/rebind/AbstractLocalizableImplCreator.java
+++ b/user/src/com/google/gwt/i18n/rebind/AbstractLocalizableImplCreator.java
@@ -17,7 +17,9 @@
 
 import static com.google.gwt.i18n.rebind.AnnotationUtil.getClassAnnotation;
 
+import com.google.gwt.core.ext.BadPropertyValueException;
 import com.google.gwt.core.ext.GeneratorContext;
+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;
@@ -150,6 +152,20 @@
       if (genLocales.length != 0) {
         // verify the current locale is in the list
         for (String genLocale : genLocales) {
+          if (GwtLocale.DEFAULT_LOCALE.equals(genLocale)) {
+            // Locale "default" gets special handling because of property
+            // fallbacks; "default" might be mapped to any real locale.
+            try {
+              SelectionProperty localeProp = 
+                  context.getPropertyOracle().getSelectionProperty(logger, "locale");
+              String defaultLocale = localeProp.getFallbackValue();
+              if (defaultLocale.length() > 0) {
+                genLocale = defaultLocale;
+              }
+            } catch (BadPropertyValueException e) {
+              throw error(logger, "Could not get 'locale' property");
+            }
+          }
           if (genLocale.equals(locale.toString())) {
             found = true;
             break;