Add new plural rules, correct Arabic and add Arabic plurals test, add
UnableToCompleteException to the MessageCatalogFormat write method, improve
error handling in AbstractLocalizableImplCreator.

Patch by: jat
Review by: rdayal


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@2435 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_ar.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_ar.java
index b58c722..839ec99 100644
--- a/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_ar.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_ar.java
@@ -17,7 +17,7 @@
 package com.google.gwt.i18n.client.impl.plurals;
 
 /**
- * Plural forms for Arabic are 0, 1, 2, 3-10, 11-99, and n.
+ * Plural forms for Arabic are 0, 1, 2, x03-x10, x11-x99.
  */
 public class DefaultRule_ar extends DefaultRule {
 
@@ -28,14 +28,14 @@
         new PluralForm("none", "Count is 0"),
         new PluralForm("one", "Count is 1"),
         new PluralForm("two", "Count is 2"),
-        new PluralForm("few", "Count is between 3 and 10"),
-        new PluralForm("many", "Count is between 11 and 99"),
+        new PluralForm("few", "Count is between x03 and x10"),
+        new PluralForm("many", "Count is between x11 and x99"),
     };
   }
 
   @Override
   public int select(int n) {
-    return n == 0 ? 1 : n == 1 ? 2 : n == 2 ? 3
-        : n >= 3 && n <= 10 ? 4 : n >= 11 && n <= 99 ? 5 : 0;
+    return n == 0 ? 1 : n == 1 ? 2 : n == 2 ? 3 : n % 100 >= 3 && n % 100 <= 10 ? 4 : n % 100 >= 11
+        && n % 100 <= 99 ? 5 : 0;
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_nb.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_nb.java
index 50a1066..faf554b 100644
--- a/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_nb.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_nb.java
@@ -18,7 +18,7 @@
 
 
 /**
- * Plural forms for Norwegian Bokmal are 1 and n, with 0 treated as plural.
+ * Plural forms for Norwegian Bokmål are 1 and n, with 0 treated as plural.
  */
 public class DefaultRule_nb extends DefaultRule {
 
diff --git a/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_se.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_se.java
new file mode 100644
index 0000000..c96cfdd
--- /dev/null
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_se.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2008 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.client.impl.plurals;
+
+
+/**
+ * Plural forms for Northern Sami are 1, 2, and n.
+ */
+public class DefaultRule_se extends DefaultRule {
+
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_1_2_n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_1_2_n.select(n);
+  }
+}
diff --git a/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_smi.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_smi.java
new file mode 100644
index 0000000..f3a9b45
--- /dev/null
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_smi.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2008 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.client.impl.plurals;
+
+
+/**
+ * Plural forms for Sami are 1, 2, and n.
+ */
+public class DefaultRule_smi extends DefaultRule {
+
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_1_2_n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_1_2_n.select(n);
+  }
+}
diff --git a/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_smj.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_smj.java
new file mode 100644
index 0000000..49990e6
--- /dev/null
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_smj.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2008 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.client.impl.plurals;
+
+
+/**
+ * Plural forms for Lule Sami are 1, 2, and n.
+ */
+public class DefaultRule_smj extends DefaultRule {
+
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_1_2_n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_1_2_n.select(n);
+  }
+}
diff --git a/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_sms.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_sms.java
new file mode 100644
index 0000000..82dcdb7
--- /dev/null
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_sms.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2008 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.client.impl.plurals;
+
+
+/**
+ * Plural forms for Skolt Sami are 1, 2, and n.
+ */
+public class DefaultRule_sms extends DefaultRule {
+
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_1_2_n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_1_2_n.select(n);
+  }
+}
diff --git a/user/src/com/google/gwt/i18n/rebind/AbstractLocalizableImplCreator.java b/user/src/com/google/gwt/i18n/rebind/AbstractLocalizableImplCreator.java
index 3b1f1b8..71104da 100644
--- a/user/src/com/google/gwt/i18n/rebind/AbstractLocalizableImplCreator.java
+++ b/user/src/com/google/gwt/i18n/rebind/AbstractLocalizableImplCreator.java
@@ -54,6 +54,7 @@
     JClassType constantsClass;
     JClassType messagesClass;
     JClassType constantsWithLookupClass;
+    boolean seenError = false;
     try {
       constantsClass = oracle.getType(LocalizableGenerator.CONSTANTS_NAME);
       constantsWithLookupClass = oracle.getType(LocalizableGenerator.CONSTANTS_WITH_LOOKUP_NAME);
@@ -133,59 +134,79 @@
     // Generate a translatable output file if requested.
     Generate generate = targetClass.getAnnotation(Generate.class);
     if (generate != null) {
-      try {
-        String path = generate.fileName();
-        if (Generate.DEFAULT.equals(path)) {
-          path = targetClass.getPackage().getName() + "."
-          + targetClass.getName().replace('.', '_');
-        } else if (path.endsWith(File.pathSeparator)) {
-          path = path + targetClass.getName().replace('.', '_');
-        }
-        String[] genLocales = generate.locales();
-        boolean found = false;
-        if (genLocales.length != 0) {
-          // verify the current locale is in the list
-          for (String genLocale : genLocales) {
-            if (genLocale.equals(locale)) {
-              found = true;
-              break;
-            }
+      String path = generate.fileName();
+      if (Generate.DEFAULT.equals(path)) {
+        path = targetClass.getPackage().getName() + "."
+        + targetClass.getName().replace('.', '_');
+      } else if (path.endsWith(File.pathSeparator)) {
+        path = path + targetClass.getName().replace('.', '_');
+      }
+      String[] genLocales = generate.locales();
+      boolean found = false;
+      if (genLocales.length != 0) {
+        // verify the current locale is in the list
+        for (String genLocale : genLocales) {
+          if (genLocale.equals(locale)) {
+            found = true;
+            break;
           }
-        } else {
-          // Since they want all locales, this is guaranteed to be one of them.
-          found = true;
         }
-        if (found) {
-          for (String genClassName : generate.format()) {
+      } else {
+        // Since they want all locales, this is guaranteed to be one of them.
+        found = true;
+      }
+      if (found) {
+        for (String genClassName : generate.format()) {
+          MessageCatalogFormat msgWriter = null;
+          try {
             Class<? extends MessageCatalogFormat> msgFormatClass = Class.forName(
                 genClassName).asSubclass(MessageCatalogFormat.class);
-            MessageCatalogFormat msgWriter = msgFormatClass.newInstance();
-            // Make generator-specific changes to a temporary copy of the path.
-            String genPath = path;
-            if (genLocales.length != 1) {
-              // If the user explicitly specified only one locale, do not add the locale.
-              genPath += '_' + locale;
-            }
-            genPath += msgWriter.getExtension();
-            OutputStream outStr = context.tryCreateResource(logger, genPath);
-            if (outStr != null) {
-              logger.log(TreeLogger.INFO, "Generating " + genPath + " from "
-                  + className + " for locale " + locale, null);
-              PrintWriter out = new PrintWriter(new BufferedWriter(
+            msgWriter = msgFormatClass.newInstance();
+          } catch (InstantiationException e) {
+            logger.log(TreeLogger.WARN, "Error instantiating @Generate class " + genClassName, e);
+            continue;
+          } catch (IllegalAccessException e) {
+            logger.log(TreeLogger.WARN, "@Generate class " + genClassName + " illegal access", e);
+            continue;
+          } catch (ClassNotFoundException e) {
+            logger.log(TreeLogger.WARN, "@Generate class " + genClassName + " not found");
+            continue;
+          }
+          // Make generator-specific changes to a temporary copy of the path.
+          String genPath = path;
+          if (genLocales.length != 1) {
+            // If the user explicitly specified only one locale, do not add the locale.
+            genPath += '_' + locale;
+          }
+          genPath += msgWriter.getExtension();
+          OutputStream outStr = context.tryCreateResource(logger, genPath);
+          if (outStr != null) {
+            TreeLogger branch = logger.branch(TreeLogger.INFO, "Generating " + genPath
+                + " from " + className + " for locale " + locale, null);
+            PrintWriter out = null;
+            try {
+              out = new PrintWriter(new BufferedWriter(
                   new OutputStreamWriter(outStr, "UTF-8")), false);
-              msgWriter.write(logger, resource, out, targetClass);
+            } catch (UnsupportedEncodingException e) {
+              throw error(logger, e.getMessage());
+            }
+            try {
+              msgWriter.write(branch, resource, out, targetClass);
               out.flush();
               context.commitResource(logger, outStr).setPrivate(true);
+            } catch (UnableToCompleteException e) {
+              // msgWriter should have already logged an error message.
+              // Keep going for now so we can find other errors.
+              seenError = true;
             }
           }
         }
-      } catch (InstantiationException e) {
-      } catch (IllegalAccessException e) {
-      } catch (ClassNotFoundException e) {
-      } catch (UnsupportedEncodingException e) {
-        throw error(logger, e.getMessage());
       }
     }
+    if (seenError) {
+      // If one of our generators had a fatal error, don't complete normally.
+      throw new UnableToCompleteException();
+    }
     return packageName + "." + className;
   }
 
@@ -208,7 +229,7 @@
    * True if the class being generated uses Constants-style annotations/quoting.
    */
   private boolean isConstants;
-  
+
   /**
    * Constructor for <code>AbstractLocalizableImplCreator</code>.
    * 
diff --git a/user/src/com/google/gwt/i18n/rebind/MessagesMethodCreator.java b/user/src/com/google/gwt/i18n/rebind/MessagesMethodCreator.java
index 06bd300..5f81269 100644
--- a/user/src/com/google/gwt/i18n/rebind/MessagesMethodCreator.java
+++ b/user/src/com/google/gwt/i18n/rebind/MessagesMethodCreator.java
@@ -51,17 +51,17 @@
   private static class StringGenerator {
 
     /**
-     * True if we are in the middle of a string literal
+     * True if we are in the middle of a string literal.
      */
     private boolean inString;
     
     /**
-     * True if we have produced any output
+     * True if we have produced any output.
      */
     private boolean producedOutput;
     
     /**
-     * Output string buffer
+     * Output string buffer.
      */
     private StringBuffer buf;
 
diff --git a/user/src/com/google/gwt/i18n/rebind/format/MessageCatalogFormat.java b/user/src/com/google/gwt/i18n/rebind/format/MessageCatalogFormat.java
index a3ff4b2..c61ac17 100644
--- a/user/src/com/google/gwt/i18n/rebind/format/MessageCatalogFormat.java
+++ b/user/src/com/google/gwt/i18n/rebind/format/MessageCatalogFormat.java
@@ -16,6 +16,7 @@
 package com.google.gwt.i18n.rebind.format;
 
 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.i18n.rebind.AbstractResource;
 
@@ -43,9 +44,12 @@
    * @param out the PrintWriter to generate output on
    * @param messageInterface the interface to create (so additional
    *     annotations may be accessed)
+   * @throws UnableToCompleteException if a fatal error prevents generating
+   *     the output file.  In this case, the implementation must have already
+   *     logged an appropriate ERROR message to the logger.
    */
   void write(TreeLogger logger, AbstractResource resource, PrintWriter out,
-      JClassType messageInterface);
+      JClassType messageInterface) throws UnableToCompleteException;
   
   /**
    * @return the extension to use for this file type, including the dot
diff --git a/user/test/com/google/gwt/i18n/I18NSuite.java b/user/test/com/google/gwt/i18n/I18NSuite.java
index dc2c553..77155de 100644
--- a/user/test/com/google/gwt/i18n/I18NSuite.java
+++ b/user/test/com/google/gwt/i18n/I18NSuite.java
@@ -15,6 +15,7 @@
  */
 package com.google.gwt.i18n;
 
+import com.google.gwt.i18n.client.ArabicPluralsTest;
 import com.google.gwt.i18n.client.DateTimeFormat_de_Test;
 import com.google.gwt.i18n.client.DateTimeParse_en_Test;
 import com.google.gwt.i18n.client.DateTimeParse_zh_CN_Test;
@@ -40,6 +41,7 @@
 
     // $JUnit-BEGIN$
     suite.addTestSuite(AbstractResourceTest.class);
+    suite.addTestSuite(ArabicPluralsTest.class);
     suite.addTestSuite(ConstantMapTest.class);
     suite.addTestSuite(DateTimeFormat_de_Test.class);
     suite.addTestSuite(DateTimeParse_en_Test.class);
diff --git a/user/test/com/google/gwt/i18n/client/ArabicPluralsTest.java b/user/test/com/google/gwt/i18n/client/ArabicPluralsTest.java
new file mode 100644
index 0000000..4144016
--- /dev/null
+++ b/user/test/com/google/gwt/i18n/client/ArabicPluralsTest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2008 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.client;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.junit.client.GWTTestCase;
+
+/**
+ * Tests Arabic plurals, which are one of the most complicated.
+ */
+public class ArabicPluralsTest extends GWTTestCase {
+
+  public String getModuleName() {
+    return "com.google.gwt.i18n.I18NTest_ar";
+  }
+
+  public void testPlurals() {
+    // Note that all text is actually in English, but written according to
+    // Arabic plural rules.
+    TestAnnotatedMessages m = GWT.create(TestAnnotatedMessages.class);
+    assertEquals("No widgets", m.pluralWidgetsOther(0));
+    assertEquals("A widget", m.pluralWidgetsOther(1));
+    assertEquals("Both widgets", m.pluralWidgetsOther(2));
+    assertEquals("A few widgets - 3", m.pluralWidgetsOther(3));
+    assertEquals("A few widgets - 10", m.pluralWidgetsOther(10));
+    assertEquals("Many widgets - 11", m.pluralWidgetsOther(11));
+    assertEquals("Many widgets - 99", m.pluralWidgetsOther(99));
+    assertEquals("100 widgets", m.pluralWidgetsOther(100));
+    assertEquals("101 widgets", m.pluralWidgetsOther(101));
+    assertEquals("102 widgets", m.pluralWidgetsOther(102));
+    assertEquals("A few widgets - 103", m.pluralWidgetsOther(103));
+  }
+}
diff --git a/user/test/com/google/gwt/i18n/client/TestAnnotatedConstants.java b/user/test/com/google/gwt/i18n/client/TestAnnotatedConstants.java
index 352d7e9..9520a81 100644
--- a/user/test/com/google/gwt/i18n/client/TestAnnotatedConstants.java
+++ b/user/test/com/google/gwt/i18n/client/TestAnnotatedConstants.java
@@ -27,7 +27,7 @@
  */
 @DefaultLocale("en-US")
 @GenerateKeys("com.google.gwt.i18n.rebind.keygen.MethodNameKeyGenerator") // default
-@Generate(format = "com.google.gwt.i18n.rebind.format.Properties")
+@Generate(format = "com.google.gwt.i18n.rebind.format.PropertiesFormat")
 public interface TestAnnotatedConstants extends Constants {
 
   @DefaultIntValue(14)
diff --git a/user/test/com/google/gwt/i18n/client/TestAnnotatedConstantsGenMD5.java b/user/test/com/google/gwt/i18n/client/TestAnnotatedConstantsGenMD5.java
index 77ec6ac..896b2e2 100644
--- a/user/test/com/google/gwt/i18n/client/TestAnnotatedConstantsGenMD5.java
+++ b/user/test/com/google/gwt/i18n/client/TestAnnotatedConstantsGenMD5.java
@@ -27,7 +27,7 @@
  */
 @DefaultLocale("en-US")
 @GenerateKeys // ("com.google.gwt.i18n.rebind.MD5") - default
-@Generate(format = "com.google.gwt.i18n.rebind.format.Properties")
+@Generate(format = "com.google.gwt.i18n.rebind.format.PropertiesFormat")
 public interface TestAnnotatedConstantsGenMD5 extends Constants {
 
   @DefaultIntValue(14)
diff --git a/user/test/com/google/gwt/i18n/client/TestAnnotatedMessages.java b/user/test/com/google/gwt/i18n/client/TestAnnotatedMessages.java
index 0dbe991..437a559 100644
--- a/user/test/com/google/gwt/i18n/client/TestAnnotatedMessages.java
+++ b/user/test/com/google/gwt/i18n/client/TestAnnotatedMessages.java
@@ -28,7 +28,7 @@
 @DefaultLocale("en-US")
 //@GenerateKeys("com.google.gwt.i18n.rebind.keygen.MD5KeyGenerator")
 @GenerateKeys("com.google.gwt.i18n.rebind.keygen.MethodNameKeyGenerator") // default
-@Generate(format = "com.google.gwt.i18n.rebind.format.Properties")
+@Generate(format = "com.google.gwt.i18n.rebind.format.PropertiesFormat")
 public interface TestAnnotatedMessages extends Messages {
 
   @DefaultMessage("Test me")
diff --git a/user/test/com/google/gwt/i18n/client/TestAnnotatedMessages_ar.properties b/user/test/com/google/gwt/i18n/client/TestAnnotatedMessages_ar.properties
new file mode 100644
index 0000000..1433ab0
--- /dev/null
+++ b/user/test/com/google/gwt/i18n/client/TestAnnotatedMessages_ar.properties
@@ -0,0 +1,7 @@
+pluralWidgetsOther={0} widgets
+pluralWidgetsOther[none]=No widgets
+pluralWidgetsOther[one]=A widget
+pluralWidgetsOther[two]=Both widgets
+pluralWidgetsOther[few]=A few widgets - {0}
+pluralWidgetsOther[many]=Many widgets - {0}
+    
\ No newline at end of file