aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNarayan Kamath <narayan@google.com>2014-06-03 16:24:30 +0100
committerNarayan Kamath <narayan@google.com>2014-07-08 18:04:25 +0100
commit8d05787d6a4b5762d790ccd2a9ed9dc8885986ef (patch)
tree62124f23889f13a3996160f1069e3fa72a29df10
parent5d1b0c9f348c25f01c6f2f5be04d2409409c08fe (diff)
downloadmessageformat-8d05787d6a4b5762d790ccd2a9ed9dc8885986ef.tar.gz
Import message format.
Imported from mscherer's SVN repository: svn checkout http://source.icu-project.org/repos/icu/icu4j/branches/markus/simplemsg at revision 35802: Local changes include : - Removal of commented code. - Addition of android makefiles - JUnit test + caliper benchmark. Scenario{vm=app_process, trial=0, benchmark=Genders} 45870.31 ns; σ=58.09 ns @ 3 trials Scenario{vm=app_process, trial=0, benchmark=Plurals} 109590.42 ns; σ=209.97 ns @ 3 trials Change-Id: I868f18bd3cbeb2581e799ef68ff2ad4e701e033e
-rw-r--r--Android.mk36
-rw-r--r--benchmarks/com/android/messageformat/MessageFormatBenchmark.java39
-rw-r--r--import.sh17
-rw-r--r--src/com/android/messageformat/MessageFormat.java38
-rw-r--r--src/com/ibm/icu/impl/ICUConfig.java77
-rw-r--r--src/com/ibm/icu/impl/ICUData.java113
-rw-r--r--src/com/ibm/icu/impl/PatternProps.java (renamed from src/main/com/android/i18n/PatternProps.java)0
-rw-r--r--src/com/ibm/icu/simple/LocaleElements_plurals.java2308
-rw-r--r--src/com/ibm/icu/simple/MessageFormat.java (renamed from src/main/com/android/i18n/MessageFormat.java)327
-rw-r--r--src/com/ibm/icu/simple/PluralFormat.java474
-rw-r--r--src/com/ibm/icu/simple/PluralRules.java (renamed from src/main/com/android/i18n/PluralRules.java)260
-rw-r--r--src/com/ibm/icu/simple/PluralRulesLoader.java180
-rw-r--r--src/com/ibm/icu/text/MessagePattern.java (renamed from src/main/com/android/i18n/MessagePattern.java)7
-rw-r--r--src/com/ibm/icu/text/SelectFormat.java384
-rw-r--r--src/com/ibm/icu/util/Freezable.java320
-rw-r--r--src/com/ibm/icu/util/ICUCloneNotSupportedException.java62
-rw-r--r--src/com/ibm/icu/util/ICUException.java60
-rw-r--r--src/com/ibm/icu/util/ICUUncheckedIOException.java66
-rw-r--r--src/com/ibm/icu/util/Output.java45
-rw-r--r--tests/src/com/android/messageformat/SimpleMessageFormatTest.java82
20 files changed, 4608 insertions, 287 deletions
diff --git a/Android.mk b/Android.mk
new file mode 100644
index 0000000..b55e954
--- /dev/null
+++ b/Android.mk
@@ -0,0 +1,36 @@
+# Copyright (C) 2014 The Android Open Source Project
+#
+# 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.
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := messageformat
+LOCAL_SRC_FILES := $(call all-java-files-under, src/)
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := messageformat-tests
+LOCAL_STATIC_JAVA_LIBRARIES := messageformat junit-targetdex
+LOCAL_SRC_FILES := $(call all-java-files-under, tests/src/)
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# Also build a host side library
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src/)
+
+LOCAL_MODULE := messageformat_host
+
+include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/benchmarks/com/android/messageformat/MessageFormatBenchmark.java b/benchmarks/com/android/messageformat/MessageFormatBenchmark.java
new file mode 100644
index 0000000..79847cb
--- /dev/null
+++ b/benchmarks/com/android/messageformat/MessageFormatBenchmark.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * 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.android.messageformat;
+
+import com.google.caliper.SimpleBenchmark;
+import java.util.Locale;
+
+public class MessageFormatBenchmark extends SimpleBenchmark {
+ public void timePlurals(int nreps) throws Exception {
+ final Locale sr = new Locale("sr");
+ for (int i = 0; i < nreps; ++i) {
+ String msg = "{num,plural,offset:1 =1{only {name}}=2{{name} and one other}" +
+ "one{{name} and #-one others}few{{name} and #-few others}" +
+ "other{{name} and #... others}}";
+ MessageFormat.formatNamedArgs(sr, msg, "num", 1, "name", "Peter");
+ }
+ }
+
+ public void timeGenders(int nreps) throws Exception {
+ for (int i = 0; i < nreps; ++i) {
+ String msg = "{gender,select,female{her book}male{his book}other{their book}}";
+ MessageFormat.formatNamedArgs(Locale.US, msg, "gender", "female");
+ }
+ }
+}
diff --git a/import.sh b/import.sh
new file mode 100644
index 0000000..b3f5093
--- /dev/null
+++ b/import.sh
@@ -0,0 +1,17 @@
+#!/bin/bash
+TOP=$1
+mkdir -p src/com/ibm/icu/impl
+mkdir -p src/com/ibm/icu/simple
+mkdir -p src/com/ibm/icu/text
+mkdir -p src/com/ibm/icu/util
+cp ${TOP}/main/classes/core/src/com/ibm/icu/impl/PatternProps.java src/com/ibm/icu/impl
+cp ${TOP}/main/classes/core/src/com/ibm/icu/impl/ICUConfig.java src/com/ibm/icu/impl
+cp ${TOP}/main/classes/core/src/com/ibm/icu/impl/ICUData.java src/com/ibm/icu/impl
+cp ${TOP}/main/classes/core/src/com/ibm/icu/simple/*.java src/com/ibm/icu/simple/
+cp ${TOP}/main/classes/core/src/com/ibm/icu/text/MessagePattern.java src/com/ibm/icu/text
+cp ${TOP}/main/classes/core/src/com/ibm/icu/text/SelectFormat.java src/com/ibm/icu/text
+cp ${TOP}/main/classes/core/src/com/ibm/icu/util/ICUUncheckedIOException.java src/com/ibm/icu/util
+cp ${TOP}/main/classes/core/src/com/ibm/icu/util/ICUCloneNotSupportedException.java src/com/ibm/icu/util
+cp ${TOP}/main/classes/core/src/com/ibm/icu/util/ICUException.java src/com/ibm/icu/util
+cp ${TOP}/main/classes/core/src/com/ibm/icu/util/Output.java src/com/ibm/icu/util
+cp ${TOP}/main/classes/core/src/com/ibm/icu/util/Freezable.java src/com/ibm/icu/util
diff --git a/src/com/android/messageformat/MessageFormat.java b/src/com/android/messageformat/MessageFormat.java
new file mode 100644
index 0000000..a12b7ff
--- /dev/null
+++ b/src/com/android/messageformat/MessageFormat.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * 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.android.messageformat;
+
+import java.util.Locale;
+
+public final class MessageFormat {
+ /**
+ * Formats a message pattern string with a variable number of name/value pair arguments.
+ * Creates an ICU MessageFormat for the locale and pattern,
+ * and formats with the arguments.
+ *
+ * @param locale Locale for number formatting and plural selection etc.
+ * @param msg an ICU-MessageFormat-syntax string
+ * @param nameValuePairs (argument name, argument value) pairs
+ */
+ public static final String formatNamedArgs(Locale locale, String msg, Object... nameValuePairs) {
+ return com.ibm.icu.simple.MessageFormat.formatNamedArgs(locale, msg, nameValuePairs);
+ }
+
+ // Non instantiable
+ private MessageFormat() {
+ }
+}
diff --git a/src/com/ibm/icu/impl/ICUConfig.java b/src/com/ibm/icu/impl/ICUConfig.java
new file mode 100644
index 0000000..b875286
--- /dev/null
+++ b/src/com/ibm/icu/impl/ICUConfig.java
@@ -0,0 +1,77 @@
+/*
+ *******************************************************************************
+ * Copyright (C) 2008-2010, International Business Machines Corporation and *
+ * others. All Rights Reserved. *
+ *******************************************************************************
+ */
+package com.ibm.icu.impl;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.AccessControlException;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.MissingResourceException;
+import java.util.Properties;
+
+/**
+ * ICUConfig is a class used for accessing ICU4J runtime configuration.
+ */
+public class ICUConfig {
+ public static final String CONFIG_PROPS_FILE = "/com/ibm/icu/ICUConfig.properties";
+ private static final Properties CONFIG_PROPS;
+
+ static {
+ CONFIG_PROPS = new Properties();
+ try {
+ InputStream is = ICUData.getStream(CONFIG_PROPS_FILE);
+ if (is != null) {
+ CONFIG_PROPS.load(is);
+ }
+ } catch (MissingResourceException mre) {
+ // If it does not exist, ignore.
+ } catch (IOException ioe) {
+ // Any IO errors, ignore
+ }
+ }
+
+ /**
+ * Get ICU configuration property value for the given name.
+ * @param name The configuration property name
+ * @return The configuration property value, or null if it does not exist.
+ */
+ public static String get(String name) {
+ return get(name, null);
+ }
+
+ /**
+ * Get ICU configuration property value for the given name.
+ * @param name The configuration property name
+ * @param def The default value
+ * @return The configuration property value. If the property does not
+ * exist, <code>def</code> is returned.
+ */
+ public static String get(String name, String def) {
+ String val = null;
+ final String fname = name;
+ if (System.getSecurityManager() != null) {
+ try {
+ val = AccessController.doPrivileged(new PrivilegedAction<String>() {
+ public String run() {
+ return System.getProperty(fname);
+ }
+ });
+ } catch (AccessControlException e) {
+ // ignore
+ // TODO log this message
+ }
+ } else {
+ val = System.getProperty(name);
+ }
+
+ if (val == null) {
+ val = CONFIG_PROPS.getProperty(name, def);
+ }
+ return val;
+ }
+}
diff --git a/src/com/ibm/icu/impl/ICUData.java b/src/com/ibm/icu/impl/ICUData.java
new file mode 100644
index 0000000..b47b278
--- /dev/null
+++ b/src/com/ibm/icu/impl/ICUData.java
@@ -0,0 +1,113 @@
+/*
+ *******************************************************************************
+ * Copyright (C) 2004-2009, International Business Machines Corporation and *
+ * others. All Rights Reserved. *
+ *******************************************************************************
+ *
+ * Created on Feb 4, 2004
+ *
+ */
+package com.ibm.icu.impl;
+
+import java.io.InputStream;
+import java.net.URL;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.MissingResourceException;
+
+/**
+ * Provides access to ICU data files as InputStreams. Implements security checking.
+ */
+public final class ICUData {
+ /*
+ * Return a URL to the ICU resource names resourceName. The
+ * resource name should either be an absolute path, or a path relative to
+ * com.ibm.icu.impl (e.g., most likely it is 'data/foo'). If required
+ * is true, throw an MissingResourceException instead of returning a null result.
+ */
+ public static boolean exists(final String resourceName) {
+ URL i = null;
+ if (System.getSecurityManager() != null) {
+ i = AccessController.doPrivileged(new PrivilegedAction<URL>() {
+ public URL run() {
+ return ICUData.class.getResource(resourceName);
+ }
+ });
+ } else {
+ i = ICUData.class.getResource(resourceName);
+ }
+ return i != null;
+ }
+
+ private static InputStream getStream(final Class<?> root, final String resourceName, boolean required) {
+ InputStream i = null;
+
+ if (System.getSecurityManager() != null) {
+ i = AccessController.doPrivileged(new PrivilegedAction<InputStream>() {
+ public InputStream run() {
+ return root.getResourceAsStream(resourceName);
+ }
+ });
+ } else {
+ i = root.getResourceAsStream(resourceName);
+ }
+
+ if (i == null && required) {
+ throw new MissingResourceException("could not locate data " +resourceName, root.getPackage().getName(), resourceName);
+ }
+ return i;
+ }
+
+ private static InputStream getStream(final ClassLoader loader, final String resourceName, boolean required) {
+ InputStream i = null;
+ if (System.getSecurityManager() != null) {
+ i = AccessController.doPrivileged(new PrivilegedAction<InputStream>() {
+ public InputStream run() {
+ return loader.getResourceAsStream(resourceName);
+ }
+ });
+ } else {
+ i = loader.getResourceAsStream(resourceName);
+ }
+ if (i == null && required) {
+ throw new MissingResourceException("could not locate data", loader.toString(), resourceName);
+ }
+ return i;
+ }
+
+ public static InputStream getStream(ClassLoader loader, String resourceName){
+ return getStream(loader,resourceName, false);
+ }
+
+ public static InputStream getRequiredStream(ClassLoader loader, String resourceName){
+ return getStream(loader, resourceName, true);
+ }
+
+ /*
+ * Convenience override that calls getStream(ICUData.class, resourceName, false);
+ */
+ public static InputStream getStream(String resourceName) {
+ return getStream(ICUData.class, resourceName, false);
+ }
+
+ /*
+ * Convenience method that calls getStream(ICUData.class, resourceName, true).
+ */
+ public static InputStream getRequiredStream(String resourceName) {
+ return getStream(ICUData.class, resourceName, true);
+ }
+
+ /*
+ * Convenience override that calls getStream(root, resourceName, false);
+ */
+ public static InputStream getStream(Class<?> root, String resourceName) {
+ return getStream(root, resourceName, false);
+ }
+
+ /*
+ * Convenience method that calls getStream(root, resourceName, true).
+ */
+ public static InputStream getRequiredStream(Class<?> root, String resourceName) {
+ return getStream(root, resourceName, true);
+ }
+}
diff --git a/src/main/com/android/i18n/PatternProps.java b/src/com/ibm/icu/impl/PatternProps.java
index 7da0f4c..7da0f4c 100644
--- a/src/main/com/android/i18n/PatternProps.java
+++ b/src/com/ibm/icu/impl/PatternProps.java
diff --git a/src/com/ibm/icu/simple/LocaleElements_plurals.java b/src/com/ibm/icu/simple/LocaleElements_plurals.java
new file mode 100644
index 0000000..31161a3
--- /dev/null
+++ b/src/com/ibm/icu/simple/LocaleElements_plurals.java
@@ -0,0 +1,2308 @@
+package com.ibm.icu.simple;
+
+import java.util.ListResourceBundle;
+
+public class LocaleElements_plurals extends ListResourceBundle {
+
+ /**
+ * Overrides ListResourceBundle
+ */
+ public final Object[][] getContents() {
+ return contents;
+ }
+
+ private static Object[][] contents = {
+ {
+ "locales",
+ new Object[][]{
+ {
+ "af",
+ "set8",
+ },
+ {
+ "ak",
+ "set6",
+ },
+ {
+ "am",
+ "set1",
+ },
+ {
+ "ar",
+ "set33",
+ },
+ {
+ "asa",
+ "set8",
+ },
+ {
+ "ast",
+ "set3",
+ },
+ {
+ "az",
+ "set8",
+ },
+ {
+ "be",
+ "set26",
+ },
+ {
+ "bem",
+ "set8",
+ },
+ {
+ "bez",
+ "set8",
+ },
+ {
+ "bg",
+ "set8",
+ },
+ {
+ "bh",
+ "set6",
+ },
+ {
+ "bm",
+ "set0",
+ },
+ {
+ "bn",
+ "set1",
+ },
+ {
+ "bo",
+ "set0",
+ },
+ {
+ "br",
+ "set30",
+ },
+ {
+ "brx",
+ "set8",
+ },
+ {
+ "bs",
+ "set20",
+ },
+ {
+ "ca",
+ "set3",
+ },
+ {
+ "cgg",
+ "set8",
+ },
+ {
+ "chr",
+ "set8",
+ },
+ {
+ "ckb",
+ "set8",
+ },
+ {
+ "cs",
+ "set24",
+ },
+ {
+ "cy",
+ "set34",
+ },
+ {
+ "da",
+ "set10",
+ },
+ {
+ "de",
+ "set3",
+ },
+ {
+ "dv",
+ "set8",
+ },
+ {
+ "dz",
+ "set0",
+ },
+ {
+ "ee",
+ "set8",
+ },
+ {
+ "el",
+ "set8",
+ },
+ {
+ "en",
+ "set3",
+ },
+ {
+ "eo",
+ "set8",
+ },
+ {
+ "es",
+ "set8",
+ },
+ {
+ "et",
+ "set3",
+ },
+ {
+ "eu",
+ "set8",
+ },
+ {
+ "fa",
+ "set1",
+ },
+ {
+ "ff",
+ "set2",
+ },
+ {
+ "fi",
+ "set3",
+ },
+ {
+ "fil",
+ "set13",
+ },
+ {
+ "fo",
+ "set8",
+ },
+ {
+ "fr",
+ "set2",
+ },
+ {
+ "fur",
+ "set8",
+ },
+ {
+ "fy",
+ "set3",
+ },
+ {
+ "ga",
+ "set31",
+ },
+ {
+ "gd",
+ "set21",
+ },
+ {
+ "gl",
+ "set3",
+ },
+ {
+ "gsw",
+ "set8",
+ },
+ {
+ "gu",
+ "set1",
+ },
+ {
+ "guw",
+ "set6",
+ },
+ {
+ "gv",
+ "set32",
+ },
+ {
+ "ha",
+ "set8",
+ },
+ {
+ "haw",
+ "set8",
+ },
+ {
+ "he",
+ "set23",
+ },
+ {
+ "hi",
+ "set1",
+ },
+ {
+ "hr",
+ "set20",
+ },
+ {
+ "hu",
+ "set8",
+ },
+ {
+ "hy",
+ "set2",
+ },
+ {
+ "id",
+ "set0",
+ },
+ {
+ "ig",
+ "set0",
+ },
+ {
+ "ii",
+ "set0",
+ },
+ {
+ "in",
+ "set0",
+ },
+ {
+ "is",
+ "set11",
+ },
+ {
+ "it",
+ "set3",
+ },
+ {
+ "iu",
+ "set17",
+ },
+ {
+ "iw",
+ "set23",
+ },
+ {
+ "ja",
+ "set0",
+ },
+ {
+ "jbo",
+ "set0",
+ },
+ {
+ "jgo",
+ "set8",
+ },
+ {
+ "ji",
+ "set3",
+ },
+ {
+ "jmc",
+ "set8",
+ },
+ {
+ "jv",
+ "set0",
+ },
+ {
+ "jw",
+ "set0",
+ },
+ {
+ "ka",
+ "set8",
+ },
+ {
+ "kab",
+ "set2",
+ },
+ {
+ "kaj",
+ "set8",
+ },
+ {
+ "kcg",
+ "set8",
+ },
+ {
+ "kde",
+ "set0",
+ },
+ {
+ "kea",
+ "set0",
+ },
+ {
+ "kk",
+ "set8",
+ },
+ {
+ "kkj",
+ "set8",
+ },
+ {
+ "kl",
+ "set8",
+ },
+ {
+ "km",
+ "set0",
+ },
+ {
+ "kn",
+ "set1",
+ },
+ {
+ "ko",
+ "set0",
+ },
+ {
+ "ks",
+ "set8",
+ },
+ {
+ "ksb",
+ "set8",
+ },
+ {
+ "ksh",
+ "set16",
+ },
+ {
+ "ku",
+ "set8",
+ },
+ {
+ "kw",
+ "set17",
+ },
+ {
+ "ky",
+ "set8",
+ },
+ {
+ "lag",
+ "set15",
+ },
+ {
+ "lb",
+ "set8",
+ },
+ {
+ "lg",
+ "set8",
+ },
+ {
+ "lkt",
+ "set0",
+ },
+ {
+ "ln",
+ "set6",
+ },
+ {
+ "lo",
+ "set0",
+ },
+ {
+ "lt",
+ "set27",
+ },
+ {
+ "lv",
+ "set14",
+ },
+ {
+ "mas",
+ "set8",
+ },
+ {
+ "mg",
+ "set6",
+ },
+ {
+ "mgo",
+ "set8",
+ },
+ {
+ "mk",
+ "set12",
+ },
+ {
+ "ml",
+ "set8",
+ },
+ {
+ "mn",
+ "set8",
+ },
+ {
+ "mo",
+ "set19",
+ },
+ {
+ "mr",
+ "set1",
+ },
+ {
+ "ms",
+ "set0",
+ },
+ {
+ "mt",
+ "set28",
+ },
+ {
+ "my",
+ "set0",
+ },
+ {
+ "nah",
+ "set8",
+ },
+ {
+ "naq",
+ "set17",
+ },
+ {
+ "nb",
+ "set8",
+ },
+ {
+ "nd",
+ "set8",
+ },
+ {
+ "ne",
+ "set8",
+ },
+ {
+ "nl",
+ "set3",
+ },
+ {
+ "nn",
+ "set8",
+ },
+ {
+ "nnh",
+ "set8",
+ },
+ {
+ "no",
+ "set8",
+ },
+ {
+ "nqo",
+ "set0",
+ },
+ {
+ "nr",
+ "set8",
+ },
+ {
+ "nso",
+ "set6",
+ },
+ {
+ "ny",
+ "set8",
+ },
+ {
+ "nyn",
+ "set8",
+ },
+ {
+ "om",
+ "set8",
+ },
+ {
+ "or",
+ "set8",
+ },
+ {
+ "os",
+ "set8",
+ },
+ {
+ "pa",
+ "set6",
+ },
+ {
+ "pap",
+ "set8",
+ },
+ {
+ "pl",
+ "set25",
+ },
+ {
+ "prg",
+ "set14",
+ },
+ {
+ "ps",
+ "set8",
+ },
+ {
+ "pt",
+ "set4",
+ },
+ {
+ "pt_PT",
+ "set9",
+ },
+ {
+ "rm",
+ "set8",
+ },
+ {
+ "ro",
+ "set19",
+ },
+ {
+ "rof",
+ "set8",
+ },
+ {
+ "root",
+ "set0",
+ },
+ {
+ "ru",
+ "set29",
+ },
+ {
+ "rwk",
+ "set8",
+ },
+ {
+ "sah",
+ "set0",
+ },
+ {
+ "saq",
+ "set8",
+ },
+ {
+ "se",
+ "set17",
+ },
+ {
+ "seh",
+ "set8",
+ },
+ {
+ "ses",
+ "set0",
+ },
+ {
+ "sg",
+ "set0",
+ },
+ {
+ "sh",
+ "set20",
+ },
+ {
+ "shi",
+ "set18",
+ },
+ {
+ "si",
+ "set5",
+ },
+ {
+ "sk",
+ "set24",
+ },
+ {
+ "sl",
+ "set22",
+ },
+ {
+ "sma",
+ "set17",
+ },
+ {
+ "smi",
+ "set17",
+ },
+ {
+ "smj",
+ "set17",
+ },
+ {
+ "smn",
+ "set17",
+ },
+ {
+ "sms",
+ "set17",
+ },
+ {
+ "sn",
+ "set8",
+ },
+ {
+ "so",
+ "set8",
+ },
+ {
+ "sq",
+ "set8",
+ },
+ {
+ "sr",
+ "set20",
+ },
+ {
+ "ss",
+ "set8",
+ },
+ {
+ "ssy",
+ "set8",
+ },
+ {
+ "st",
+ "set8",
+ },
+ {
+ "sv",
+ "set3",
+ },
+ {
+ "sw",
+ "set3",
+ },
+ {
+ "syr",
+ "set8",
+ },
+ {
+ "ta",
+ "set8",
+ },
+ {
+ "te",
+ "set8",
+ },
+ {
+ "teo",
+ "set8",
+ },
+ {
+ "th",
+ "set0",
+ },
+ {
+ "ti",
+ "set6",
+ },
+ {
+ "tig",
+ "set8",
+ },
+ {
+ "tk",
+ "set8",
+ },
+ {
+ "tl",
+ "set13",
+ },
+ {
+ "tn",
+ "set8",
+ },
+ {
+ "to",
+ "set0",
+ },
+ {
+ "tr",
+ "set8",
+ },
+ {
+ "ts",
+ "set8",
+ },
+ {
+ "tzm",
+ "set7",
+ },
+ {
+ "ug",
+ "set8",
+ },
+ {
+ "uk",
+ "set29",
+ },
+ {
+ "ur",
+ "set3",
+ },
+ {
+ "uz",
+ "set8",
+ },
+ {
+ "ve",
+ "set8",
+ },
+ {
+ "vi",
+ "set0",
+ },
+ {
+ "vo",
+ "set8",
+ },
+ {
+ "vun",
+ "set8",
+ },
+ {
+ "wa",
+ "set6",
+ },
+ {
+ "wae",
+ "set8",
+ },
+ {
+ "wo",
+ "set0",
+ },
+ {
+ "xh",
+ "set8",
+ },
+ {
+ "xog",
+ "set8",
+ },
+ {
+ "yi",
+ "set3",
+ },
+ {
+ "yo",
+ "set0",
+ },
+ {
+ "zh",
+ "set0",
+ },
+ {
+ "zu",
+ "set1",
+ },
+ },
+ },
+ {
+ "locales_ordinals",
+ new Object[][]{
+ {
+ "af",
+ "set35",
+ },
+ {
+ "am",
+ "set35",
+ },
+ {
+ "ar",
+ "set35",
+ },
+ {
+ "az",
+ "set48",
+ },
+ {
+ "bg",
+ "set35",
+ },
+ {
+ "bn",
+ "set50",
+ },
+ {
+ "ca",
+ "set46",
+ },
+ {
+ "cs",
+ "set35",
+ },
+ {
+ "cy",
+ "set51",
+ },
+ {
+ "da",
+ "set35",
+ },
+ {
+ "de",
+ "set35",
+ },
+ {
+ "el",
+ "set35",
+ },
+ {
+ "en",
+ "set44",
+ },
+ {
+ "es",
+ "set35",
+ },
+ {
+ "et",
+ "set35",
+ },
+ {
+ "eu",
+ "set35",
+ },
+ {
+ "fa",
+ "set35",
+ },
+ {
+ "fi",
+ "set35",
+ },
+ {
+ "fil",
+ "set37",
+ },
+ {
+ "fr",
+ "set37",
+ },
+ {
+ "fy",
+ "set35",
+ },
+ {
+ "gl",
+ "set35",
+ },
+ {
+ "gu",
+ "set49",
+ },
+ {
+ "he",
+ "set35",
+ },
+ {
+ "hi",
+ "set49",
+ },
+ {
+ "hr",
+ "set35",
+ },
+ {
+ "hu",
+ "set38",
+ },
+ {
+ "hy",
+ "set37",
+ },
+ {
+ "id",
+ "set35",
+ },
+ {
+ "in",
+ "set35",
+ },
+ {
+ "is",
+ "set35",
+ },
+ {
+ "it",
+ "set41",
+ },
+ {
+ "iw",
+ "set35",
+ },
+ {
+ "ja",
+ "set35",
+ },
+ {
+ "ka",
+ "set42",
+ },
+ {
+ "kk",
+ "set40",
+ },
+ {
+ "km",
+ "set35",
+ },
+ {
+ "kn",
+ "set35",
+ },
+ {
+ "ko",
+ "set35",
+ },
+ {
+ "ky",
+ "set35",
+ },
+ {
+ "lo",
+ "set37",
+ },
+ {
+ "lt",
+ "set35",
+ },
+ {
+ "lv",
+ "set35",
+ },
+ {
+ "mk",
+ "set47",
+ },
+ {
+ "ml",
+ "set35",
+ },
+ {
+ "mn",
+ "set35",
+ },
+ {
+ "mo",
+ "set37",
+ },
+ {
+ "mr",
+ "set45",
+ },
+ {
+ "ms",
+ "set37",
+ },
+ {
+ "my",
+ "set35",
+ },
+ {
+ "nb",
+ "set35",
+ },
+ {
+ "ne",
+ "set39",
+ },
+ {
+ "nl",
+ "set35",
+ },
+ {
+ "pa",
+ "set35",
+ },
+ {
+ "pl",
+ "set35",
+ },
+ {
+ "prg",
+ "set35",
+ },
+ {
+ "pt",
+ "set35",
+ },
+ {
+ "ro",
+ "set37",
+ },
+ {
+ "root",
+ "set35",
+ },
+ {
+ "ru",
+ "set35",
+ },
+ {
+ "sh",
+ "set35",
+ },
+ {
+ "si",
+ "set35",
+ },
+ {
+ "sk",
+ "set35",
+ },
+ {
+ "sl",
+ "set35",
+ },
+ {
+ "sq",
+ "set43",
+ },
+ {
+ "sr",
+ "set35",
+ },
+ {
+ "sv",
+ "set36",
+ },
+ {
+ "sw",
+ "set35",
+ },
+ {
+ "ta",
+ "set35",
+ },
+ {
+ "te",
+ "set35",
+ },
+ {
+ "th",
+ "set35",
+ },
+ {
+ "tl",
+ "set37",
+ },
+ {
+ "tr",
+ "set35",
+ },
+ {
+ "uk",
+ "set35",
+ },
+ {
+ "ur",
+ "set35",
+ },
+ {
+ "uz",
+ "set35",
+ },
+ {
+ "vi",
+ "set37",
+ },
+ {
+ "zh",
+ "set35",
+ },
+ {
+ "zu",
+ "set35",
+ },
+ },
+ },
+ {
+ "rules",
+ new Object[][]{
+ {
+ "set0",
+ new Object[][]{
+ {
+ "other",
+ " @integer 0~15, 100, 1000, 10000, 100000, 1" +
+ "000000, … @decimal 0.0~1.5, 10.0, 100.0, " +
+ "1000.0, 10000.0, 100000.0, 1000000.0, …",
+ },
+ },
+ },
+ {
+ "set1",
+ new Object[][]{
+ {
+ "one",
+ "i = 0 or n = 1 @integer 0, 1 @decimal 0.0~1" +
+ ".0, 0.00~0.04",
+ },
+ {
+ "other",
+ " @integer 2~17, 100, 1000, 10000, 100000, 1" +
+ "000000, … @decimal 1.1~2.6, 10.0, 100.0, " +
+ "1000.0, 10000.0, 100000.0, 1000000.0, …",
+ },
+ },
+ },
+ {
+ "set10",
+ new Object[][]{
+ {
+ "one",
+ "n = 1 or t != 0 and i = 0,1 @integer 1 @dec" +
+ "imal 0.1~1.6",
+ },
+ {
+ "other",
+ " @integer 0, 2~16, 100, 1000, 10000, 100000" +
+ ", 1000000, … @decimal 0.0, 2.0~3.4, 10.0," +
+ " 100.0, 1000.0, 10000.0, 100000.0, 1000000." +
+ "0, …",
+ },
+ },
+ },
+ {
+ "set11",
+ new Object[][]{
+ {
+ "one",
+ "t = 0 and i % 10 = 1 and i % 100 != 11 or t" +
+ " != 0 @integer 1, 21, 31, 41, 51, 61, 71, 8" +
+ "1, 101, 1001, … @decimal 0.1~1.6, 10.1, 1" +
+ "00.1, 1000.1, …",
+ },
+ {
+ "other",
+ " @integer 0, 2~16, 100, 1000, 10000, 100000" +
+ ", 1000000, … @decimal 0.0, 2.0, 3.0, 4.0," +
+ " 5.0, 6.0, 7.0, 8.0, 10.0, 100.0, 1000.0, 1" +
+ "0000.0, 100000.0, 1000000.0, …",
+ },
+ },
+ },
+ {
+ "set12",
+ new Object[][]{
+ {
+ "one",
+ "v = 0 and i % 10 = 1 or f % 10 = 1 @integer" +
+ " 1, 11, 21, 31, 41, 51, 61, 71, 101, 1001, " +
+ "… @decimal 0.1, 1.1, 2.1, 3.1, 4.1, 5.1, " +
+ "6.1, 7.1, 10.1, 100.1, 1000.1, …",
+ },
+ {
+ "other",
+ " @integer 0, 2~10, 12~17, 100, 1000, 10000," +
+ " 100000, 1000000, … @decimal 0.0, 0.2~1.0" +
+ ", 1.2~1.7, 10.0, 100.0, 1000.0, 10000.0, 10" +
+ "0000.0, 1000000.0, …",
+ },
+ },
+ },
+ {
+ "set13",
+ new Object[][]{
+ {
+ "one",
+ "v = 0 and i = 1,2,3 or v = 0 and i % 10 != " +
+ "4,6,9 or v != 0 and f % 10 != 4,6,9 @intege" +
+ "r 0~3, 5, 7, 8, 10~13, 15, 17, 18, 20, 21, " +
+ "100, 1000, 10000, 100000, 1000000, … @dec" +
+ "imal 0.0~0.3, 0.5, 0.7, 0.8, 1.0~1.3, 1.5, " +
+ "1.7, 1.8, 2.0, 2.1, 10.0, 100.0, 1000.0, 10" +
+ "000.0, 100000.0, 1000000.0, …",
+ },
+ {
+ "other",
+ " @integer 4, 6, 9, 14, 16, 19, 24, 26, 104," +
+ " 1004, … @decimal 0.4, 0.6, 0.9, 1.4, 1.6" +
+ ", 1.9, 2.4, 2.6, 10.4, 100.4, 1000.4, …",
+ },
+ },
+ },
+ {
+ "set14",
+ new Object[][]{
+ {
+ "one",
+ "n % 10 = 1 and n % 100 != 11 or v = 2 and f" +
+ " % 10 = 1 and f % 100 != 11 or v != 2 and f" +
+ " % 10 = 1 @integer 1, 21, 31, 41, 51, 61, 7" +
+ "1, 81, 101, 1001, … @decimal 0.1, 1.0, 1." +
+ "1, 2.1, 3.1, 4.1, 5.1, 6.1, 7.1, 10.1, 100." +
+ "1, 1000.1, …",
+ },
+ {
+ "other",
+ " @integer 2~9, 22~29, 102, 1002, … @decim" +
+ "al 0.2~0.9, 1.2~1.9, 10.2, 100.2, 1000.2, …",
+ },
+ {
+ "zero",
+ "n % 10 = 0 or n % 100 = 11..19 or v = 2 and" +
+ " f % 100 = 11..19 @integer 0, 10~20, 30, 40" +
+ ", 50, 60, 100, 1000, 10000, 100000, 1000000" +
+ ", … @decimal 0.0, 10.0, 11.0, 12.0, 13.0," +
+ " 14.0, 15.0, 16.0, 100.0, 1000.0, 10000.0, " +
+ "100000.0, 1000000.0, …",
+ },
+ },
+ },
+ {
+ "set15",
+ new Object[][]{
+ {
+ "one",
+ "i = 0,1 and n != 0 @integer 1 @decimal 0.1~1.6",
+ },
+ {
+ "other",
+ " @integer 2~17, 100, 1000, 10000, 100000, 1" +
+ "000000, … @decimal 2.0~3.5, 10.0, 100.0, " +
+ "1000.0, 10000.0, 100000.0, 1000000.0, …",
+ },
+ {
+ "zero",
+ "n = 0 @integer 0 @decimal 0.0, 0.00, 0.000," +
+ " 0.0000",
+ },
+ },
+ },
+ {
+ "set16",
+ new Object[][]{
+ {
+ "one",
+ "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000," +
+ " 1.0000",
+ },
+ {
+ "other",
+ " @integer 2~17, 100, 1000, 10000, 100000, 1" +
+ "000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0" +
+ ", 100.0, 1000.0, 10000.0, 100000.0, 1000000" +
+ ".0, …",
+ },
+ {
+ "zero",
+ "n = 0 @integer 0 @decimal 0.0, 0.00, 0.000," +
+ " 0.0000",
+ },
+ },
+ },
+ {
+ "set17",
+ new Object[][]{
+ {
+ "one",
+ "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000," +
+ " 1.0000",
+ },
+ {
+ "other",
+ " @integer 0, 3~17, 100, 1000, 10000, 100000" +
+ ", 1000000, … @decimal 0.0~0.9, 1.1~1.6, 1" +
+ "0.0, 100.0, 1000.0, 10000.0, 100000.0, 1000" +
+ "000.0, …",
+ },
+ {
+ "two",
+ "n = 2 @integer 2 @decimal 2.0, 2.00, 2.000," +
+ " 2.0000",
+ },
+ },
+ },
+ {
+ "set18",
+ new Object[][]{
+ {
+ "few",
+ "n = 2..10 @integer 2~10 @decimal 2.0, 3.0, " +
+ "4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 2.00, 3" +
+ ".00, 4.00, 5.00, 6.00, 7.00, 8.00",
+ },
+ {
+ "one",
+ "i = 0 or n = 1 @integer 0, 1 @decimal 0.0~1" +
+ ".0, 0.00~0.04",
+ },
+ {
+ "other",
+ " @integer 11~26, 100, 1000, 10000, 100000, " +
+ "1000000, … @decimal 1.1~1.9, 2.1~2.7, 10." +
+ "1, 100.0, 1000.0, 10000.0, 100000.0, 100000" +
+ "0.0, …",
+ },
+ },
+ },
+ {
+ "set19",
+ new Object[][]{
+ {
+ "few",
+ "v != 0 or n = 0 or n != 1 and n % 100 = 1.." +
+ "19 @integer 0, 2~16, 101, 1001, … @decima" +
+ "l 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 10" +
+ "0000.0, 1000000.0, …",
+ },
+ {
+ "one",
+ "i = 1 and v = 0 @integer 1",
+ },
+ {
+ "other",
+ " @integer 20~35, 100, 1000, 10000, 100000, " +
+ "1000000, …",
+ },
+ },
+ },
+ {
+ "set2",
+ new Object[][]{
+ {
+ "one",
+ "i = 0,1 @integer 0, 1 @decimal 0.0~1.5",
+ },
+ {
+ "other",
+ " @integer 2~17, 100, 1000, 10000, 100000, 1" +
+ "000000, … @decimal 2.0~3.5, 10.0, 100.0, " +
+ "1000.0, 10000.0, 100000.0, 1000000.0, …",
+ },
+ },
+ },
+ {
+ "set20",
+ new Object[][]{
+ {
+ "few",
+ "v = 0 and i % 10 = 2..4 and i % 100 != 12.." +
+ "14 or f % 10 = 2..4 and f % 100 != 12..14 @" +
+ "integer 2~4, 22~24, 32~34, 42~44, 52~54, 62" +
+ ", 102, 1002, … @decimal 0.2~0.4, 1.2~1.4," +
+ " 2.2~2.4, 3.2~3.4, 4.2~4.4, 5.2, 10.2, 100." +
+ "2, 1000.2, …",
+ },
+ {
+ "one",
+ "v = 0 and i % 10 = 1 and i % 100 != 11 or f" +
+ " % 10 = 1 and f % 100 != 11 @integer 1, 21," +
+ " 31, 41, 51, 61, 71, 81, 101, 1001, … @de" +
+ "cimal 0.1, 1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 7." +
+ "1, 10.1, 100.1, 1000.1, …",
+ },
+ {
+ "other",
+ " @integer 0, 5~19, 100, 1000, 10000, 100000" +
+ ", 1000000, … @decimal 0.0, 0.5~1.0, 1.5~2" +
+ ".0, 2.5~2.7, 10.0, 100.0, 1000.0, 10000.0, " +
+ "100000.0, 1000000.0, …",
+ },
+ },
+ },
+ {
+ "set21",
+ new Object[][]{
+ {
+ "few",
+ "n = 3..10,13..19 @integer 3~10, 13~19 @deci" +
+ "mal 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0" +
+ ", 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0," +
+ " 3.00",
+ },
+ {
+ "one",
+ "n = 1,11 @integer 1, 11 @decimal 1.0, 11.0," +
+ " 1.00, 11.00, 1.000, 11.000, 1.0000",
+ },
+ {
+ "other",
+ " @integer 0, 20~34, 100, 1000, 10000, 10000" +
+ "0, 1000000, … @decimal 0.0~0.9, 1.1~1.6, " +
+ "10.1, 100.0, 1000.0, 10000.0, 100000.0, 100" +
+ "0000.0, …",
+ },
+ {
+ "two",
+ "n = 2,12 @integer 2, 12 @decimal 2.0, 12.0," +
+ " 2.00, 12.00, 2.000, 12.000, 2.0000",
+ },
+ },
+ },
+ {
+ "set22",
+ new Object[][]{
+ {
+ "few",
+ "v = 0 and i % 100 = 3..4 or v != 0 @integer" +
+ " 3, 4, 103, 104, 203, 204, 303, 304, 403, 4" +
+ "04, 503, 504, 603, 604, 703, 704, 1003, …" +
+ " @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 100" +
+ "00.0, 100000.0, 1000000.0, …",
+ },
+ {
+ "one",
+ "v = 0 and i % 100 = 1 @integer 1, 101, 201," +
+ " 301, 401, 501, 601, 701, 1001, …",
+ },
+ {
+ "other",
+ " @integer 0, 5~19, 100, 1000, 10000, 100000" +
+ ", 1000000, …",
+ },
+ {
+ "two",
+ "v = 0 and i % 100 = 2 @integer 2, 102, 202," +
+ " 302, 402, 502, 602, 702, 1002, …",
+ },
+ },
+ },
+ {
+ "set23",
+ new Object[][]{
+ {
+ "many",
+ "v = 0 and n != 0..10 and n % 10 = 0 @intege" +
+ "r 20, 30, 40, 50, 60, 70, 80, 90, 100, 1000" +
+ ", 10000, 100000, 1000000, …",
+ },
+ {
+ "one",
+ "i = 1 and v = 0 @integer 1",
+ },
+ {
+ "other",
+ " @integer 0, 3~17, 101, 1001, … @decimal " +
+ "0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 1000" +
+ "00.0, 1000000.0, …",
+ },
+ {
+ "two",
+ "i = 2 and v = 0 @integer 2",
+ },
+ },
+ },
+ {
+ "set24",
+ new Object[][]{
+ {
+ "few",
+ "i = 2..4 and v = 0 @integer 2~4",
+ },
+ {
+ "many",
+ "v != 0 @decimal 0.0~1.5, 10.0, 100.0, 100" +
+ "0.0, 10000.0, 100000.0, 1000000.0, …",
+ },
+ {
+ "one",
+ "i = 1 and v = 0 @integer 1",
+ },
+ {
+ "other",
+ " @integer 0, 5~19, 100, 1000, 10000, 100000" +
+ ", 1000000, …",
+ },
+ },
+ },
+ {
+ "set25",
+ new Object[][]{
+ {
+ "few",
+ "v = 0 and i % 10 = 2..4 and i % 100 != 12.." +
+ "14 @integer 2~4, 22~24, 32~34, 42~44, 52~54" +
+ ", 62, 102, 1002, …",
+ },
+ {
+ "many",
+ "v = 0 and i != 1 and i % 10 = 0..1 or v = 0" +
+ " and i % 10 = 5..9 or v = 0 and i % 100 = 1" +
+ "2..14 @integer 0, 5~19, 100, 1000, 10000, 1" +
+ "00000, 1000000, …",
+ },
+ {
+ "one",
+ "i = 1 and v = 0 @integer 1",
+ },
+ {
+ "other",
+ " @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 1" +
+ "0000.0, 100000.0, 1000000.0, …",
+ },
+ },
+ },
+ {
+ "set26",
+ new Object[][]{
+ {
+ "few",
+ "n % 10 = 2..4 and n % 100 != 12..14 @intege" +
+ "r 2~4, 22~24, 32~34, 42~44, 52~54, 62, 102," +
+ " 1002, … @decimal 2.0, 3.0, 4.0, 22.0, 23" +
+ ".0, 24.0, 32.0, 33.0, 102.0, 1002.0, …",
+ },
+ {
+ "many",
+ "n % 10 = 0 or n % 10 = 5..9 or n % 100 = 11" +
+ "..14 @integer 0, 5~19, 100, 1000, 10000, 10" +
+ "0000, 1000000, … @decimal 0.0, 5.0, 6.0, " +
+ "7.0, 8.0, 9.0, 10.0, 11.0, 100.0, 1000.0, 1" +
+ "0000.0, 100000.0, 1000000.0, …",
+ },
+ {
+ "one",
+ "n % 10 = 1 and n % 100 != 11 @integer 1, 21" +
+ ", 31, 41, 51, 61, 71, 81, 101, 1001, … @d" +
+ "ecimal 1.0, 21.0, 31.0, 41.0, 51.0, 61.0, 7" +
+ "1.0, 81.0, 101.0, 1001.0, …",
+ },
+ {
+ "other",
+ " @decimal 0.1~0.9, 1.1~1.7, 10.1, 100.1, " +
+ "1000.1, …",
+ },
+ },
+ },
+ {
+ "set27",
+ new Object[][]{
+ {
+ "few",
+ "n % 10 = 2..9 and n % 100 != 11..19 @intege" +
+ "r 2~9, 22~29, 102, 1002, … @decimal 2.0, " +
+ "3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 22.0, 10" +
+ "2.0, 1002.0, …",
+ },
+ {
+ "many",
+ "f != 0 @decimal 0.1~0.9, 1.1~1.7, 10.1, 1" +
+ "00.1, 1000.1, …",
+ },
+ {
+ "one",
+ "n % 10 = 1 and n % 100 != 11..19 @integer 1" +
+ ", 21, 31, 41, 51, 61, 71, 81, 101, 1001, … @decimal 1.0, 21.0, 31.0, 41.0, 51.0, 61." +
+ "0, 71.0, 81.0, 101.0, 1001.0, …",
+ },
+ {
+ "other",
+ " @integer 0, 10~20, 30, 40, 50, 60, 100, 10" +
+ "00, 10000, 100000, 1000000, … @decimal 0." +
+ "0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0" +
+ ", 100.0, 1000.0, 10000.0, 100000.0, 1000000" +
+ ".0, …",
+ },
+ },
+ },
+ {
+ "set28",
+ new Object[][]{
+ {
+ "few",
+ "n = 0 or n % 100 = 2..10 @integer 0, 2~10, " +
+ "102~107, 1002, … @decimal 0.0, 2.0, 3.0, " +
+ "4.0, 5.0, 6.0, 7.0, 8.0, 10.0, 102.0, 1002." +
+ "0, …",
+ },
+ {
+ "many",
+ "n % 100 = 11..19 @integer 11~19, 111~117, 1" +
+ "011, … @decimal 11.0, 12.0, 13.0, 14.0, 1" +
+ "5.0, 16.0, 17.0, 18.0, 111.0, 1011.0, …",
+ },
+ {
+ "one",
+ "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000," +
+ " 1.0000",
+ },
+ {
+ "other",
+ " @integer 20~35, 100, 1000, 10000, 100000, " +
+ "1000000, … @decimal 0.1~0.9, 1.1~1.7, 10." +
+ "1, 100.0, 1000.0, 10000.0, 100000.0, 100000" +
+ "0.0, …",
+ },
+ },
+ },
+ {
+ "set29",
+ new Object[][]{
+ {
+ "few",
+ "v = 0 and i % 10 = 2..4 and i % 100 != 12.." +
+ "14 @integer 2~4, 22~24, 32~34, 42~44, 52~54" +
+ ", 62, 102, 1002, …",
+ },
+ {
+ "many",
+ "v = 0 and i % 10 = 0 or v = 0 and i % 10 = " +
+ "5..9 or v = 0 and i % 100 = 11..14 @integer" +
+ " 0, 5~19, 100, 1000, 10000, 100000, 1000000" +
+ ", …",
+ },
+ {
+ "one",
+ "v = 0 and i % 10 = 1 and i % 100 != 11 @int" +
+ "eger 1, 21, 31, 41, 51, 61, 71, 81, 101, 10" +
+ "01, …",
+ },
+ {
+ "other",
+ " @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 1" +
+ "0000.0, 100000.0, 1000000.0, …",
+ },
+ },
+ },
+ {
+ "set3",
+ new Object[][]{
+ {
+ "one",
+ "i = 1 and v = 0 @integer 1",
+ },
+ {
+ "other",
+ " @integer 0, 2~16, 100, 1000, 10000, 100000" +
+ ", 1000000, … @decimal 0.0~1.5, 10.0, 100." +
+ "0, 1000.0, 10000.0, 100000.0, 1000000.0, …",
+ },
+ },
+ },
+ {
+ "set30",
+ new Object[][]{
+ {
+ "few",
+ "n % 10 = 3..4,9 and n % 100 != 10..19,70..7" +
+ "9,90..99 @integer 3, 4, 9, 23, 24, 29, 33, " +
+ "34, 39, 43, 44, 49, 103, 1003, … @decimal" +
+ " 3.0, 4.0, 9.0, 23.0, 24.0, 29.0, 33.0, 34." +
+ "0, 103.0, 1003.0, …",
+ },
+ {
+ "many",
+ "n != 0 and n % 1000000 = 0 @integer 1000000" +
+ ", … @decimal 1000000.0, 1000000.00, 10000" +
+ "00.000, …",
+ },
+ {
+ "one",
+ "n % 10 = 1 and n % 100 != 11,71,91 @integer" +
+ " 1, 21, 31, 41, 51, 61, 81, 101, 1001, … " +
+ "@decimal 1.0, 21.0, 31.0, 41.0, 51.0, 61.0," +
+ " 81.0, 101.0, 1001.0, …",
+ },
+ {
+ "other",
+ " @integer 0, 5~8, 10~20, 100, 1000, 10000, " +
+ "100000, … @decimal 0.0~0.9, 1.1~1.6, 10.0" +
+ ", 100.0, 1000.0, 10000.0, 100000.0, …",
+ },
+ {
+ "two",
+ "n % 10 = 2 and n % 100 != 12,72,92 @integer" +
+ " 2, 22, 32, 42, 52, 62, 82, 102, 1002, … " +
+ "@decimal 2.0, 22.0, 32.0, 42.0, 52.0, 62.0," +
+ " 82.0, 102.0, 1002.0, …",
+ },
+ },
+ },
+ {
+ "set31",
+ new Object[][]{
+ {
+ "few",
+ "n = 3..6 @integer 3~6 @decimal 3.0, 4.0, 5." +
+ "0, 6.0, 3.00, 4.00, 5.00, 6.00, 3.000, 4.00" +
+ "0, 5.000, 6.000, 3.0000, 4.0000, 5.0000, 6." +
+ "0000",
+ },
+ {
+ "many",
+ "n = 7..10 @integer 7~10 @decimal 7.0, 8.0, " +
+ "9.0, 10.0, 7.00, 8.00, 9.00, 10.00, 7.000, " +
+ "8.000, 9.000, 10.000, 7.0000, 8.0000, 9.000" +
+ "0, 10.0000",
+ },
+ {
+ "one",
+ "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000," +
+ " 1.0000",
+ },
+ {
+ "other",
+ " @integer 0, 11~25, 100, 1000, 10000, 10000" +
+ "0, 1000000, … @decimal 0.0~0.9, 1.1~1.6, " +
+ "10.1, 100.0, 1000.0, 10000.0, 100000.0, 100" +
+ "0000.0, …",
+ },
+ {
+ "two",
+ "n = 2 @integer 2 @decimal 2.0, 2.00, 2.000," +
+ " 2.0000",
+ },
+ },
+ },
+ {
+ "set32",
+ new Object[][]{
+ {
+ "few",
+ "v = 0 and i % 100 = 0,20,40,60,80 @integer " +
+ "0, 20, 40, 60, 80, 100, 120, 140, 1000, 100" +
+ "00, 100000, 1000000, …",
+ },
+ {
+ "many",
+ "v != 0 @decimal 0.0~1.5, 10.0, 100.0, 100" +
+ "0.0, 10000.0, 100000.0, 1000000.0, …",
+ },
+ {
+ "one",
+ "v = 0 and i % 10 = 1 @integer 1, 11, 21, 31" +
+ ", 41, 51, 61, 71, 101, 1001, …",
+ },
+ {
+ "other",
+ " @integer 3~10, 13~19, 23, 103, 1003, …",
+ },
+ {
+ "two",
+ "v = 0 and i % 10 = 2 @integer 2, 12, 22, 32" +
+ ", 42, 52, 62, 72, 102, 1002, …",
+ },
+ },
+ },
+ {
+ "set33",
+ new Object[][]{
+ {
+ "few",
+ "n % 100 = 3..10 @integer 3~10, 103~110, 100" +
+ "3, … @decimal 3.0, 4.0, 5.0, 6.0, 7.0, 8." +
+ "0, 9.0, 10.0, 103.0, 1003.0, …",
+ },
+ {
+ "many",
+ "n % 100 = 11..99 @integer 11~26, 111, 1011," +
+ " … @decimal 11.0, 12.0, 13.0, 14.0, 15.0," +
+ " 16.0, 17.0, 18.0, 111.0, 1011.0, …",
+ },
+ {
+ "one",
+ "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000," +
+ " 1.0000",
+ },
+ {
+ "other",
+ " @integer 100~102, 200~202, 300~302, 400~40" +
+ "2, 500~502, 600, 1000, 10000, 100000, 10000" +
+ "00, … @decimal 0.1~0.9, 1.1~1.7, 10.1, 10" +
+ "0.0, 1000.0, 10000.0, 100000.0, 1000000.0, " +
+ "…",
+ },
+ {
+ "two",
+ "n = 2 @integer 2 @decimal 2.0, 2.00, 2.000," +
+ " 2.0000",
+ },
+ {
+ "zero",
+ "n = 0 @integer 0 @decimal 0.0, 0.00, 0.000," +
+ " 0.0000",
+ },
+ },
+ },
+ {
+ "set34",
+ new Object[][]{
+ {
+ "few",
+ "n = 3 @integer 3 @decimal 3.0, 3.00, 3.000," +
+ " 3.0000",
+ },
+ {
+ "many",
+ "n = 6 @integer 6 @decimal 6.0, 6.00, 6.000," +
+ " 6.0000",
+ },
+ {
+ "one",
+ "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000," +
+ " 1.0000",
+ },
+ {
+ "other",
+ " @integer 4, 5, 7~20, 100, 1000, 10000, 100" +
+ "000, 1000000, … @decimal 0.1~0.9, 1.1~1.7" +
+ ", 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1" +
+ "000000.0, …",
+ },
+ {
+ "two",
+ "n = 2 @integer 2 @decimal 2.0, 2.00, 2.000," +
+ " 2.0000",
+ },
+ {
+ "zero",
+ "n = 0 @integer 0 @decimal 0.0, 0.00, 0.000," +
+ " 0.0000",
+ },
+ },
+ },
+ {
+ "set35",
+ new Object[][]{
+ {
+ "other",
+ " @integer 0~15, 100, 1000, 10000, 100000, 1" +
+ "000000, …",
+ },
+ },
+ },
+ {
+ "set36",
+ new Object[][]{
+ {
+ "one",
+ "n % 10 = 1,2 and n % 100 != 11,12 @integer " +
+ "1, 2, 21, 22, 31, 32, 41, 42, 51, 52, 61, 6" +
+ "2, 71, 72, 81, 82, 101, 1001, …",
+ },
+ {
+ "other",
+ " @integer 0, 3~17, 100, 1000, 10000, 100000" +
+ ", 1000000, …",
+ },
+ },
+ },
+ {
+ "set37",
+ new Object[][]{
+ {
+ "one",
+ "n = 1 @integer 1",
+ },
+ {
+ "other",
+ " @integer 0, 2~16, 100, 1000, 10000, 100000" +
+ ", 1000000, …",
+ },
+ },
+ },
+ {
+ "set38",
+ new Object[][]{
+ {
+ "one",
+ "n = 1,5 @integer 1, 5",
+ },
+ {
+ "other",
+ " @integer 0, 2~4, 6~17, 100, 1000, 10000, 1" +
+ "00000, 1000000, …",
+ },
+ },
+ },
+ {
+ "set39",
+ new Object[][]{
+ {
+ "one",
+ "n = 1..4 @integer 1~4",
+ },
+ {
+ "other",
+ " @integer 0, 5~19, 100, 1000, 10000, 100000" +
+ ", 1000000, …",
+ },
+ },
+ },
+ {
+ "set4",
+ new Object[][]{
+ {
+ "one",
+ "i = 1 and v = 0 or i = 0 and t = 1 @integer" +
+ " 1 @decimal 0.1, 0.01, 0.10, 0.001, 0.010, " +
+ "0.100, 0.0001, 0.0010, 0.0100, 0.1000",
+ },
+ {
+ "other",
+ " @integer 0, 2~16, 100, 1000, 10000, 100000" +
+ ", 1000000, … @decimal 0.0, 0.2~1.6, 10.0," +
+ " 100.0, 1000.0, 10000.0, 100000.0, 1000000." +
+ "0, …",
+ },
+ },
+ },
+ {
+ "set40",
+ new Object[][]{
+ {
+ "many",
+ "n % 10 = 6 or n % 10 = 9 or n % 10 = 0 and " +
+ "n != 0 @integer 6, 9, 10, 16, 19, 20, 26, 2" +
+ "9, 30, 36, 39, 40, 100, 1000, 10000, 100000" +
+ ", 1000000, …",
+ },
+ {
+ "other",
+ " @integer 0~5, 7, 8, 11~15, 17, 18, 21, 101" +
+ ", 1001, …",
+ },
+ },
+ },
+ {
+ "set41",
+ new Object[][]{
+ {
+ "many",
+ "n = 11,8,80,800 @integer 8, 11, 80, 800",
+ },
+ {
+ "other",
+ " @integer 0~7, 9, 10, 12~17, 100, 1000, 100" +
+ "00, 100000, 1000000, …",
+ },
+ },
+ },
+ {
+ "set42",
+ new Object[][]{
+ {
+ "many",
+ "i = 0 or i % 100 = 2..20,40,60,80 @integer " +
+ "0, 2~16, 102, 1002, …",
+ },
+ {
+ "one",
+ "i = 1 @integer 1",
+ },
+ {
+ "other",
+ " @integer 21~36, 100, 1000, 10000, 100000, " +
+ "1000000, …",
+ },
+ },
+ },
+ {
+ "set43",
+ new Object[][]{
+ {
+ "many",
+ "n % 10 = 4 and n % 100 != 14 @integer 4, 24" +
+ ", 34, 44, 54, 64, 74, 84, 104, 1004, …",
+ },
+ {
+ "one",
+ "n = 1 @integer 1",
+ },
+ {
+ "other",
+ " @integer 0, 2, 3, 5~17, 100, 1000, 10000, " +
+ "100000, 1000000, …",
+ },
+ },
+ },
+ {
+ "set44",
+ new Object[][]{
+ {
+ "few",
+ "n % 10 = 3 and n % 100 != 13 @integer 3, 23" +
+ ", 33, 43, 53, 63, 73, 83, 103, 1003, …",
+ },
+ {
+ "one",
+ "n % 10 = 1 and n % 100 != 11 @integer 1, 21" +
+ ", 31, 41, 51, 61, 71, 81, 101, 1001, …",
+ },
+ {
+ "other",
+ " @integer 0, 4~18, 100, 1000, 10000, 100000" +
+ ", 1000000, …",
+ },
+ {
+ "two",
+ "n % 10 = 2 and n % 100 != 12 @integer 2, 22" +
+ ", 32, 42, 52, 62, 72, 82, 102, 1002, …",
+ },
+ },
+ },
+ {
+ "set45",
+ new Object[][]{
+ {
+ "few",
+ "n = 4 @integer 4",
+ },
+ {
+ "one",
+ "n = 1 @integer 1",
+ },
+ {
+ "other",
+ " @integer 0, 5~19, 100, 1000, 10000, 100000" +
+ ", 1000000, …",
+ },
+ {
+ "two",
+ "n = 2,3 @integer 2, 3",
+ },
+ },
+ },
+ {
+ "set46",
+ new Object[][]{
+ {
+ "few",
+ "n = 4 @integer 4",
+ },
+ {
+ "one",
+ "n = 1,3 @integer 1, 3",
+ },
+ {
+ "other",
+ " @integer 0, 5~19, 100, 1000, 10000, 100000" +
+ ", 1000000, …",
+ },
+ {
+ "two",
+ "n = 2 @integer 2",
+ },
+ },
+ },
+ {
+ "set47",
+ new Object[][]{
+ {
+ "many",
+ "i % 10 = 7,8 and i % 100 != 17,18 @integer " +
+ "7, 8, 27, 28, 37, 38, 47, 48, 57, 58, 67, 6" +
+ "8, 77, 78, 87, 88, 107, 1007, …",
+ },
+ {
+ "one",
+ "i % 10 = 1 and i % 100 != 11 @integer 1, 21" +
+ ", 31, 41, 51, 61, 71, 81, 101, 1001, …",
+ },
+ {
+ "other",
+ " @integer 0, 3~6, 9~19, 100, 1000, 10000, 1" +
+ "00000, 1000000, …",
+ },
+ {
+ "two",
+ "i % 10 = 2 and i % 100 != 12 @integer 2, 22" +
+ ", 32, 42, 52, 62, 72, 82, 102, 1002, …",
+ },
+ },
+ },
+ {
+ "set48",
+ new Object[][]{
+ {
+ "few",
+ "i % 10 = 3,4 or i % 1000 = 100,200,300,400," +
+ "500,600,700,800,900 @integer 3, 4, 13, 14, " +
+ "23, 24, 33, 34, 43, 44, 53, 54, 63, 64, 73," +
+ " 74, 100, 1003, …",
+ },
+ {
+ "many",
+ "i = 0 or i % 10 = 6 or i % 100 = 40,60,90 @" +
+ "integer 0, 6, 16, 26, 36, 40, 46, 56, 106, " +
+ "1006, …",
+ },
+ {
+ "one",
+ "i % 10 = 1,2,5,7,8 or i % 100 = 20,50,70,80" +
+ " @integer 1, 2, 5, 7, 8, 11, 12, 15, 17, 18" +
+ ", 20~22, 25, 101, 1001, …",
+ },
+ {
+ "other",
+ " @integer 9, 10, 19, 29, 30, 39, 49, 59, 69" +
+ ", 79, 109, 1000, 10000, 100000, 1000000, …",
+ },
+ },
+ },
+ {
+ "set49",
+ new Object[][]{
+ {
+ "few",
+ "n = 4 @integer 4",
+ },
+ {
+ "many",
+ "n = 6 @integer 6",
+ },
+ {
+ "one",
+ "n = 1 @integer 1",
+ },
+ {
+ "other",
+ " @integer 0, 5, 7~20, 100, 1000, 10000, 100" +
+ "000, 1000000, …",
+ },
+ {
+ "two",
+ "n = 2,3 @integer 2, 3",
+ },
+ },
+ },
+ {
+ "set5",
+ new Object[][]{
+ {
+ "one",
+ "n = 0,1 or i = 0 and f = 1 @integer 0, 1 @d" +
+ "ecimal 0.0, 0.1, 1.0, 0.00, 0.01, 1.00, 0.0" +
+ "00, 0.001, 1.000, 0.0000, 0.0001, 1.0000",
+ },
+ {
+ "other",
+ " @integer 2~17, 100, 1000, 10000, 100000, 1" +
+ "000000, … @decimal 0.2~0.9, 1.1~1.8, 10.0" +
+ ", 100.0, 1000.0, 10000.0, 100000.0, 1000000" +
+ ".0, …",
+ },
+ },
+ },
+ {
+ "set50",
+ new Object[][]{
+ {
+ "few",
+ "n = 4 @integer 4",
+ },
+ {
+ "many",
+ "n = 6 @integer 6",
+ },
+ {
+ "one",
+ "n = 1,5,7,8,9,10 @integer 1, 5, 7~10",
+ },
+ {
+ "other",
+ " @integer 0, 11~25, 100, 1000, 10000, 10000" +
+ "0, 1000000, …",
+ },
+ {
+ "two",
+ "n = 2,3 @integer 2, 3",
+ },
+ },
+ },
+ {
+ "set51",
+ new Object[][]{
+ {
+ "few",
+ "n = 3,4 @integer 3, 4",
+ },
+ {
+ "many",
+ "n = 5,6 @integer 5, 6",
+ },
+ {
+ "one",
+ "n = 1 @integer 1",
+ },
+ {
+ "other",
+ " @integer 10~25, 100, 1000, 10000, 100000, " +
+ "1000000, …",
+ },
+ {
+ "two",
+ "n = 2 @integer 2",
+ },
+ {
+ "zero",
+ "n = 0,7,8,9 @integer 0, 7~9",
+ },
+ },
+ },
+ {
+ "set6",
+ new Object[][]{
+ {
+ "one",
+ "n = 0..1 @integer 0, 1 @decimal 0.0, 1.0, 0" +
+ ".00, 1.00, 0.000, 1.000, 0.0000, 1.0000",
+ },
+ {
+ "other",
+ " @integer 2~17, 100, 1000, 10000, 100000, 1" +
+ "000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0" +
+ ", 100.0, 1000.0, 10000.0, 100000.0, 1000000" +
+ ".0, …",
+ },
+ },
+ },
+ {
+ "set7",
+ new Object[][]{
+ {
+ "one",
+ "n = 0..1 or n = 11..99 @integer 0, 1, 11~24" +
+ " @decimal 0.0, 1.0, 11.0, 12.0, 13.0, 14.0," +
+ " 15.0, 16.0, 17.0, 18.0, 19.0, 20.0, 21.0, " +
+ "22.0, 23.0, 24.0",
+ },
+ {
+ "other",
+ " @integer 2~10, 100~106, 1000, 10000, 10000" +
+ "0, 1000000, … @decimal 0.1~0.9, 1.1~1.7, " +
+ "10.0, 100.0, 1000.0, 10000.0, 100000.0, 100" +
+ "0000.0, …",
+ },
+ },
+ },
+ {
+ "set8",
+ new Object[][]{
+ {
+ "one",
+ "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000," +
+ " 1.0000",
+ },
+ {
+ "other",
+ " @integer 0, 2~16, 100, 1000, 10000, 100000" +
+ ", 1000000, … @decimal 0.0~0.9, 1.1~1.6, 1" +
+ "0.0, 100.0, 1000.0, 10000.0, 100000.0, 1000" +
+ "000.0, …",
+ },
+ },
+ },
+ {
+ "set9",
+ new Object[][]{
+ {
+ "one",
+ "n = 1 and v = 0 @integer 1",
+ },
+ {
+ "other",
+ " @integer 0, 2~16, 100, 1000, 10000, 100000" +
+ ", 1000000, … @decimal 0.0~1.5, 10.0, 100." +
+ "0, 1000.0, 10000.0, 100000.0, 1000000.0, …",
+ },
+ },
+ },
+ },
+ },
+ };
+}
diff --git a/src/main/com/android/i18n/MessageFormat.java b/src/com/ibm/icu/simple/MessageFormat.java
index 98510fc..0a883dd 100644
--- a/src/main/com/android/i18n/MessageFormat.java
+++ b/src/com/ibm/icu/simple/MessageFormat.java
@@ -1,6 +1,6 @@
/*
**********************************************************************
-* Copyright (c) 2004-2013, International Business Machines
+* Copyright (c) 2004-2014, International Business Machines
* Corporation and others. All Rights Reserved.
**********************************************************************
* Author: Alan Liu
@@ -8,38 +8,40 @@
* Since: ICU 3.0
**********************************************************************
*/
-package com.ibm.icu.text;
+package com.ibm.icu.simple;
import java.io.IOException;
import java.io.InvalidObjectException;
-import java.io.ObjectInputStream;
import java.text.AttributedCharacterIterator;
import java.text.AttributedCharacterIterator.Attribute;
import java.text.AttributedString;
import java.text.CharacterIterator;
import java.text.ChoiceFormat;
+import java.text.DateFormat;
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
import java.text.FieldPosition;
import java.text.Format;
+import java.text.NumberFormat;
import java.text.ParseException;
import java.text.ParsePosition;
+import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import com.ibm.icu.impl.PatternProps;
-import com.ibm.icu.impl.Utility;
+import com.ibm.icu.simple.PluralRules.PluralType;
+import com.ibm.icu.text.MessagePattern;
import com.ibm.icu.text.MessagePattern.ArgType;
import com.ibm.icu.text.MessagePattern.Part;
-import com.ibm.icu.text.PluralRules.FixedDecimal;
-import com.ibm.icu.text.PluralRules.PluralType;
-import com.ibm.icu.util.ULocale;
-import com.ibm.icu.util.ULocale.Category;
+import com.ibm.icu.text.SelectFormat;
+import com.ibm.icu.util.ICUUncheckedIOException;
/**
* {@icuenhanced java.text.MessageFormat}.{@icu _usage_}
@@ -330,12 +332,28 @@ import com.ibm.icu.util.ULocale.Category;
* @author Markus Scherer
* @stable ICU 3.0
*/
-public class MessageFormat extends UFormat {
+public class MessageFormat extends Format {
// Incremented by 1 for ICU 4.8's new format.
static final long serialVersionUID = 7136212545847378652L;
/**
+ * Formats a message pattern string with a variable number of name/value pair arguments.
+ * Creates an ICU MessageFormat for the locale and pattern,
+ * and formats with the arguments.
+ *
+ * @param locale Locale for number formatting and plural selection etc.
+ * @param msg an ICU-MessageFormat-syntax string
+ * @param nameValuePairs (argument name, argument value) pairs
+ */
+ public static final String formatNamedArgs(Locale locale, String msg, Object... nameValuePairs) {
+ StringBuilder result = new StringBuilder(msg.length());
+ new MessageFormat(msg, locale).format(0, null, null, null, nameValuePairs,
+ new AppendableWrapper(result), null);
+ return result.toString();
+ }
+
+ /**
* Constructs a MessageFormat for the default <code>FORMAT</code> locale and the
* specified pattern.
* Sets the locale and calls applyPattern(pattern).
@@ -346,7 +364,7 @@ public class MessageFormat extends UFormat {
* @stable ICU 3.0
*/
public MessageFormat(String pattern) {
- this.ulocale = ULocale.getDefault(Category.FORMAT);
+ locale_ = Locale.getDefault(); // Category.FORMAT
applyPattern(pattern);
}
@@ -361,82 +379,21 @@ public class MessageFormat extends UFormat {
* @stable ICU 3.0
*/
public MessageFormat(String pattern, Locale locale) {
- this(pattern, ULocale.forLocale(locale));
- }
-
- /**
- * Constructs a MessageFormat for the specified locale and
- * pattern.
- * Sets the locale and calls applyPattern(pattern).
- *
- * @param pattern the pattern for this message format
- * @param locale the locale for this message format
- * @exception IllegalArgumentException if the pattern is invalid
- * @stable ICU 3.2
- */
- public MessageFormat(String pattern, ULocale locale) {
- this.ulocale = locale;
+ locale_ = locale;
applyPattern(pattern);
}
/**
- * Sets the locale to be used for creating argument Format objects.
- * This affects subsequent calls to the {@link #applyPattern applyPattern}
- * method as well as to the <code>format</code> and
- * {@link #formatToCharacterIterator formatToCharacterIterator} methods.
- *
- * @param locale the locale to be used when creating or comparing subformats
- * @stable ICU 3.0
- */
- public void setLocale(Locale locale) {
- setLocale(ULocale.forLocale(locale));
- }
-
- /**
- * Sets the locale to be used for creating argument Format objects.
- * This affects subsequent calls to the {@link #applyPattern applyPattern}
- * method as well as to the <code>format</code> and
- * {@link #formatToCharacterIterator formatToCharacterIterator} methods.
- *
- * @param locale the locale to be used when creating or comparing subformats
- * @stable ICU 3.2
- */
- public void setLocale(ULocale locale) {
- /* Save the pattern, and then reapply so that */
- /* we pick up any changes in locale specific */
- /* elements */
- String existingPattern = toPattern(); /*ibm.3550*/
- this.ulocale = locale;
- // Invalidate all stock formatters. They are no longer valid since
- // the locale has changed.
- stockDateFormatter = null;
- stockNumberFormatter = null;
- pluralProvider = null;
- ordinalProvider = null;
- applyPattern(existingPattern); /*ibm.3550*/
- }
-
- /**
* Returns the locale that's used when creating or comparing subformats.
*
* @return the locale used when creating or comparing subformats
* @stable ICU 3.0
*/
public Locale getLocale() {
- return ulocale.toLocale();
+ return locale_;
}
/**
- * {@icu} Returns the locale that's used when creating argument Format objects.
- *
- * @return the locale used when creating or comparing subformats
- * @stable ICU 3.2
- */
- public ULocale getULocale() {
- return ulocale;
- }
-
- /**
* Sets the pattern used by this message format.
* Parses the pattern and caches Format objects for simple argument types.
* Patterns and their interpretation are specified in the
@@ -1414,46 +1371,6 @@ public class MessageFormat extends UFormat {
/**
* {@inheritDoc}
* @stable ICU 3.0
- */
- @Override
- public Object clone() {
- MessageFormat other = (MessageFormat) super.clone();
-
- if (customFormatArgStarts != null) {
- other.customFormatArgStarts = new HashSet<Integer>();
- for (Integer key : customFormatArgStarts) {
- other.customFormatArgStarts.add(key);
- }
- } else {
- other.customFormatArgStarts = null;
- }
-
- if (cachedFormatters != null) {
- other.cachedFormatters = new HashMap<Integer, Format>();
- Iterator<Map.Entry<Integer, Format>> it = cachedFormatters.entrySet().iterator();
- while (it.hasNext()){
- Map.Entry<Integer, Format> entry = it.next();
- other.cachedFormatters.put(entry.getKey(), entry.getValue());
- }
- } else {
- other.cachedFormatters = null;
- }
-
- other.msgPattern = msgPattern == null ? null : (MessagePattern)msgPattern.clone();
- other.stockDateFormatter =
- stockDateFormatter == null ? null : (DateFormat) stockDateFormatter.clone();
- other.stockNumberFormatter =
- stockNumberFormatter == null ? null : (NumberFormat) stockNumberFormatter.clone();
-
- other.pluralProvider = null;
- other.ordinalProvider = null;
- return other;
- }
-
- /**
- * {@inheritDoc}
- * @stable ICU 3.0
- */
@Override
public boolean equals(Object obj) {
if (this == obj) // quick check
@@ -1468,6 +1385,7 @@ public class MessageFormat extends UFormat {
// Note: It might suffice to only compare custom formatters
// rather than all formatters.
}
+ */
/**
* {@inheritDoc}
@@ -1541,7 +1459,7 @@ public class MessageFormat extends UFormat {
/**
* The locale to use for formatting numbers and dates.
*/
- private transient ULocale ulocale;
+ private transient Locale locale_;
/**
* The MessagePattern which contains the parsed structure of the pattern string.
@@ -1571,13 +1489,13 @@ public class MessageFormat extends UFormat {
private DateFormat getStockDateFormatter() {
if (stockDateFormatter == null) {
stockDateFormatter = DateFormat.getDateTimeInstance(
- DateFormat.SHORT, DateFormat.SHORT, ulocale);//fix
+ DateFormat.SHORT, DateFormat.SHORT, locale_);//fix
}
return stockDateFormatter;
}
private NumberFormat getStockNumberFormatter() {
if (stockNumberFormatter == null) {
- stockNumberFormatter = NumberFormat.getInstance(ulocale);
+ stockNumberFormatter = NumberFormat.getInstance(locale_);
}
return stockNumberFormatter;
}
@@ -1601,7 +1519,7 @@ public class MessageFormat extends UFormat {
* @param fp Field position status.
*/
private void format(int msgStart, PluralSelectorContext pluralNumber,
- Object[] args, Map<String, Object> argsMap,
+ Object[] args, Map<String, Object> argsMap, Object[] nameValuePairs,
AppendableWrapper dest, FieldPosition fp) {
String msgString=msgPattern.getPatternString();
int prevIndex=msgPattern.getPart(msgStart).getLimit();
@@ -1646,6 +1564,20 @@ public class MessageFormat extends UFormat {
arg=null;
noArg=true;
}
+ } else if(nameValuePairs!=null) {
+ argId = argName;
+ for(int nvIndex=0;; nvIndex+=2) {
+ if(nvIndex<nameValuePairs.length) {
+ if(argName.equals(nameValuePairs[nvIndex].toString())) {
+ arg=nameValuePairs[nvIndex+1];
+ break;
+ }
+ } else {
+ arg=null;
+ noArg=true;
+ break;
+ }
+ }
} else {
argId = argName;
if(argsMap!=null && argsMap.containsKey(argName)) {
@@ -1673,28 +1605,7 @@ public class MessageFormat extends UFormat {
}
} else if(cachedFormatters!=null && (formatter=cachedFormatters.get(i - 2))!=null) {
// Handles all ArgType.SIMPLE, and formatters from setFormat() and its siblings.
- if ( formatter instanceof ChoiceFormat ||
- formatter instanceof PluralFormat ||
- formatter instanceof SelectFormat) {
- // We only handle nested formats here if they were provided via setFormat() or its siblings.
- // Otherwise they are not cached and instead handled below according to argType.
- String subMsgString = formatter.format(arg);
- if (subMsgString.indexOf('{') >= 0 ||
- (subMsgString.indexOf('\'') >= 0 && !msgPattern.jdkAposMode())) {
- MessageFormat subMsgFormat = new MessageFormat(subMsgString, ulocale);
- subMsgFormat.format(0, null, args, argsMap, dest, null);
- } else if (dest.attributes == null) {
- dest.append(subMsgString);
- } else {
- // This formats the argument twice, once above to get the subMsgString
- // and then once more here.
- // It only happens in formatToCharacterIterator()
- // on a complex Format set via setFormat(),
- // and only when the selected subMsgString does not need further formatting.
- // This imitates ICU 4.6 behavior.
- dest.formatAndAppend(formatter, arg);
- }
- } else {
+ {
dest.formatAndAppend(formatter, arg);
}
} else if(
@@ -1717,7 +1628,7 @@ public class MessageFormat extends UFormat {
}
double number = ((Number)arg).doubleValue();
int subMsgStart=findChoiceSubMessage(msgPattern, i, number);
- formatComplexSubMessage(subMsgStart, null, args, argsMap, dest);
+ formatComplexSubMessage(subMsgStart, null, args, argsMap, nameValuePairs, dest);
} else if(argType.hasPluralStyle()) {
if (!(arg instanceof Number)) {
throw new IllegalArgumentException("'" + arg + "' is not a Number");
@@ -1740,10 +1651,10 @@ public class MessageFormat extends UFormat {
new PluralSelectorContext(i, argName, number, offset);
int subMsgStart=PluralFormat.findSubMessage(
msgPattern, i, selector, context, number.doubleValue());
- formatComplexSubMessage(subMsgStart, context, args, argsMap, dest);
+ formatComplexSubMessage(subMsgStart, context, args, argsMap, nameValuePairs, dest);
} else if(argType==ArgType.SELECT) {
int subMsgStart=SelectFormat.findSubMessage(msgPattern, i, arg.toString());
- formatComplexSubMessage(subMsgStart, null, args, argsMap, dest);
+ formatComplexSubMessage(subMsgStart, null, args, argsMap, nameValuePairs, dest);
} else {
// This should never happen.
throw new IllegalStateException("unexpected argType "+argType);
@@ -1756,13 +1667,15 @@ public class MessageFormat extends UFormat {
private void formatComplexSubMessage(
int msgStart, PluralSelectorContext pluralNumber,
- Object[] args, Map<String, Object> argsMap,
+ Object[] args, Map<String, Object> argsMap, Object[] nameValuePairs,
AppendableWrapper dest) {
if (!msgPattern.jdkAposMode()) {
- format(msgStart, pluralNumber, args, argsMap, dest, null);
+ format(msgStart, pluralNumber, args, argsMap, nameValuePairs, dest, null);
return;
}
// JDK compatibility mode: (see JDK MessageFormat.format() API docs)
+ throw new UnsupportedOperationException("JDK apostrophe mode not supported");
+ /*
// - remove SKIP_SYNTAX; that is, remove half of the apostrophes
// - if the result string contains an open curly brace '{' then
// instantiate a temporary MessageFormat object and format again;
@@ -1815,6 +1728,7 @@ public class MessageFormat extends UFormat {
} else {
dest.append(subMsgString);
}
+ */
}
/**
@@ -2080,7 +1994,7 @@ public class MessageFormat extends UFormat {
}
public String select(Object ctx, double number) {
if(rules == null) {
- rules = PluralRules.forLocale(msgFormat.ulocale, type);
+ rules = PluralRules.forLocale(msgFormat.locale_, type);
}
// Select a sub-message according to how the number is formatted,
// which is specified in the selected sub-message.
@@ -2100,10 +2014,11 @@ public class MessageFormat extends UFormat {
}
assert context.number.doubleValue() == number; // argument number minus the offset
context.numberString = context.formatter.format(context.number);
+ /* TODO: Try to get FixedDecimal from formatted string.
if(context.formatter instanceof DecimalFormat) {
FixedDecimal dec = ((DecimalFormat)context.formatter).getFixedDecimal(number);
return rules.select(dec);
- } else {
+ } else */ {
return rules.select(number);
}
}
@@ -2135,7 +2050,7 @@ public class MessageFormat extends UFormat {
"This method is not available in MessageFormat objects " +
"that use alphanumeric argument names.");
}
- format(0, null, arguments, argsMap, dest, fp);
+ format(0, null, arguments, argsMap, null, dest, fp);
}
private void resetPattern() {
@@ -2186,67 +2101,68 @@ public class MessageFormat extends UFormat {
case TYPE_NUMBER:
switch (findKeyword(style, modifierList)) {
case MODIFIER_EMPTY:
- newFormat = NumberFormat.getInstance(ulocale);
+ newFormat = NumberFormat.getInstance(locale_);
break;
case MODIFIER_CURRENCY:
- newFormat = NumberFormat.getCurrencyInstance(ulocale);
+ newFormat = NumberFormat.getCurrencyInstance(locale_);
break;
case MODIFIER_PERCENT:
- newFormat = NumberFormat.getPercentInstance(ulocale);
+ newFormat = NumberFormat.getPercentInstance(locale_);
break;
case MODIFIER_INTEGER:
- newFormat = NumberFormat.getIntegerInstance(ulocale);
+ newFormat = NumberFormat.getIntegerInstance(locale_);
break;
default: // pattern
newFormat = new DecimalFormat(style,
- new DecimalFormatSymbols(ulocale));
+ new DecimalFormatSymbols(locale_));
break;
}
break;
case TYPE_DATE:
switch (findKeyword(style, dateModifierList)) {
case DATE_MODIFIER_EMPTY:
- newFormat = DateFormat.getDateInstance(DateFormat.DEFAULT, ulocale);
+ newFormat = DateFormat.getDateInstance(DateFormat.DEFAULT, locale_);
break;
case DATE_MODIFIER_SHORT:
- newFormat = DateFormat.getDateInstance(DateFormat.SHORT, ulocale);
+ newFormat = DateFormat.getDateInstance(DateFormat.SHORT, locale_);
break;
case DATE_MODIFIER_MEDIUM:
- newFormat = DateFormat.getDateInstance(DateFormat.DEFAULT, ulocale);
+ newFormat = DateFormat.getDateInstance(DateFormat.DEFAULT, locale_);
break;
case DATE_MODIFIER_LONG:
- newFormat = DateFormat.getDateInstance(DateFormat.LONG, ulocale);
+ newFormat = DateFormat.getDateInstance(DateFormat.LONG, locale_);
break;
case DATE_MODIFIER_FULL:
- newFormat = DateFormat.getDateInstance(DateFormat.FULL, ulocale);
+ newFormat = DateFormat.getDateInstance(DateFormat.FULL, locale_);
break;
default:
- newFormat = new SimpleDateFormat(style, ulocale);
+ newFormat = new SimpleDateFormat(style, locale_);
break;
}
break;
case TYPE_TIME:
switch (findKeyword(style, dateModifierList)) {
case DATE_MODIFIER_EMPTY:
- newFormat = DateFormat.getTimeInstance(DateFormat.DEFAULT, ulocale);
+ newFormat = DateFormat.getTimeInstance(DateFormat.DEFAULT, locale_);
break;
case DATE_MODIFIER_SHORT:
- newFormat = DateFormat.getTimeInstance(DateFormat.SHORT, ulocale);
+ newFormat = DateFormat.getTimeInstance(DateFormat.SHORT, locale_);
break;
case DATE_MODIFIER_MEDIUM:
- newFormat = DateFormat.getTimeInstance(DateFormat.DEFAULT, ulocale);
+ newFormat = DateFormat.getTimeInstance(DateFormat.DEFAULT, locale_);
break;
case DATE_MODIFIER_LONG:
- newFormat = DateFormat.getTimeInstance(DateFormat.LONG, ulocale);
+ newFormat = DateFormat.getTimeInstance(DateFormat.LONG, locale_);
break;
case DATE_MODIFIER_FULL:
- newFormat = DateFormat.getTimeInstance(DateFormat.FULL, ulocale);
+ newFormat = DateFormat.getTimeInstance(DateFormat.FULL, locale_);
break;
default:
- newFormat = new SimpleDateFormat(style, ulocale);
+ newFormat = new SimpleDateFormat(style, locale_);
break;
}
break;
+ /* There is no java.text.RuleBasedNumberFormat --
case TYPE_SPELLOUT:
{
RuleBasedNumberFormat rbnf = new RuleBasedNumberFormat(ulocale,
@@ -2295,6 +2211,7 @@ public class MessageFormat extends UFormat {
newFormat = rbnf;
}
break;
+ */
default:
throw new IllegalArgumentException("Unknown format type \"" + type + "\"");
}
@@ -2312,80 +2229,6 @@ public class MessageFormat extends UFormat {
return -1;
}
- /**
- * Custom serialization, new in ICU 4.8.
- * We do not want to use default serialization because we only have a small
- * amount of persistent state which is better expressed explicitly
- * rather than via writing field objects.
- * @param out The output stream.
- * @serialData Writes the locale as a BCP 47 language tag string,
- * the MessagePattern.ApostropheMode as an object,
- * and the pattern string (null if none was applied).
- * Followed by an int with the number of (int formatIndex, Object formatter) pairs,
- * and that many such pairs, corresponding to previous setFormat() calls for custom formats.
- * Followed by an int with the number of (int, Object) pairs,
- * and that many such pairs, for future (post-ICU 4.8) extension of the serialization format.
- */
- private void writeObject(java.io.ObjectOutputStream out) throws IOException {
- out.defaultWriteObject();
- // ICU 4.8 custom serialization.
- // locale as a BCP 47 language tag
- out.writeObject(ulocale.toLanguageTag());
- // ApostropheMode
- if (msgPattern == null) {
- msgPattern = new MessagePattern();
- }
- out.writeObject(msgPattern.getApostropheMode());
- // message pattern string
- out.writeObject(msgPattern.getPatternString());
- // custom formatters
- if (customFormatArgStarts == null || customFormatArgStarts.isEmpty()) {
- out.writeInt(0);
- } else {
- out.writeInt(customFormatArgStarts.size());
- int formatIndex = 0;
- for (int partIndex = 0; (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) {
- if (customFormatArgStarts.contains(partIndex)) {
- out.writeInt(formatIndex);
- out.writeObject(cachedFormatters.get(partIndex));
- }
- ++formatIndex;
- }
- }
- // number of future (int, Object) pairs
- out.writeInt(0);
- }
-
- /**
- * Custom deserialization, new in ICU 4.8. See comments on writeObject().
- * @throws InvalidObjectException if the objects read from the stream is invalid.
- */
- private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
- in.defaultReadObject();
- // ICU 4.8 custom deserialization.
- String languageTag = (String)in.readObject();
- ulocale = ULocale.forLanguageTag(languageTag);
- MessagePattern.ApostropheMode aposMode = (MessagePattern.ApostropheMode)in.readObject();
- if (msgPattern == null || aposMode != msgPattern.getApostropheMode()) {
- msgPattern = new MessagePattern(aposMode);
- }
- String msg = (String)in.readObject();
- if (msg != null) {
- applyPattern(msg);
- }
- // custom formatters
- for (int numFormatters = in.readInt(); numFormatters > 0; --numFormatters) {
- int formatIndex = in.readInt();
- Format formatter = (Format)in.readObject();
- setFormat(formatIndex, formatter);
- }
- // skip future (int, Object) pairs
- for (int numPairs = in.readInt(); numPairs > 0; --numPairs) {
- in.readInt();
- in.readObject();
- }
- }
-
private void cacheExplicitFormats() {
if (cachedFormatters != null) {
cachedFormatters.clear();
@@ -2570,7 +2413,7 @@ public class MessageFormat extends UFormat {
app.append(s);
length += s.length();
} catch(IOException e) {
- throw new RuntimeException(e);
+ throw new ICUUncheckedIOException(e);
}
}
@@ -2579,7 +2422,7 @@ public class MessageFormat extends UFormat {
app.append(s, start, limit);
length += limit - start;
} catch(IOException e) {
- throw new RuntimeException(e);
+ throw new ICUUncheckedIOException(e);
}
}
@@ -2600,7 +2443,7 @@ public class MessageFormat extends UFormat {
}
return length;
} catch(IOException e) {
- throw new RuntimeException(e);
+ throw new ICUUncheckedIOException(e);
}
}
diff --git a/src/com/ibm/icu/simple/PluralFormat.java b/src/com/ibm/icu/simple/PluralFormat.java
new file mode 100644
index 0000000..67debd7
--- /dev/null
+++ b/src/com/ibm/icu/simple/PluralFormat.java
@@ -0,0 +1,474 @@
+/*
+ *******************************************************************************
+ * Copyright (C) 2007-2014, International Business Machines Corporation and
+ * others. All Rights Reserved.
+ *******************************************************************************
+ */
+
+package com.ibm.icu.simple;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.text.NumberFormat;
+import java.text.ParsePosition;
+import java.util.Locale;
+import java.util.Map;
+
+import com.ibm.icu.simple.PluralRules.FixedDecimal;
+import com.ibm.icu.simple.PluralRules.PluralType;
+import com.ibm.icu.text.MessagePattern;
+
+/**
+ * <p>
+ * <code>PluralFormat</code> supports the creation of internationalized
+ * messages with plural inflection. It is based on <i>plural
+ * selection</i>, i.e. the caller specifies messages for each
+ * plural case that can appear in the user's language and the
+ * <code>PluralFormat</code> selects the appropriate message based on
+ * the number.
+ * </p>
+ * <h4>The Problem of Plural Forms in Internationalized Messages</h4>
+ * <p>
+ * Different languages have different ways to inflect
+ * plurals. Creating internationalized messages that include plural
+ * forms is only feasible when the framework is able to handle plural
+ * forms of <i>all</i> languages correctly. <code>ChoiceFormat</code>
+ * doesn't handle this well, because it attaches a number interval to
+ * each message and selects the message whose interval contains a
+ * given number. This can only handle a finite number of
+ * intervals. But in some languages, like Polish, one plural case
+ * applies to infinitely many intervals (e.g., the paucal case applies to
+ * numbers ending with 2, 3, or 4 except those ending with 12, 13, or
+ * 14). Thus <code>ChoiceFormat</code> is not adequate.
+ * </p><p>
+ * <code>PluralFormat</code> deals with this by breaking the problem
+ * into two parts:
+ * <ul>
+ * <li>It uses <code>PluralRules</code> that can define more complex
+ * conditions for a plural case than just a single interval. These plural
+ * rules define both what plural cases exist in a language, and to
+ * which numbers these cases apply.
+ * <li>It provides predefined plural rules for many languages. Thus, the programmer
+ * need not worry about the plural cases of a language and
+ * does not have to define the plural cases; they can simply
+ * use the predefined keywords. The whole plural formatting of messages can
+ * be done using localized patterns from resource bundles. For predefined plural
+ * rules, see the CLDR <i>Language Plural Rules</i> page at
+ * http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
+ * </ul>
+ * </p>
+ * <h4>Usage of <code>PluralFormat</code></h4>
+ * <p>Note: Typically, plural formatting is done via <code>MessageFormat</code>
+ * with a <code>plural</code> argument type,
+ * rather than using a stand-alone <code>PluralFormat</code>.
+ * </p><p>
+ * This discussion assumes that you use <code>PluralFormat</code> with
+ * a predefined set of plural rules. You can create one using one of
+ * the constructors that takes a <code>ULocale</code> object. To
+ * specify the message pattern, you can either pass it to the
+ * constructor or set it explicitly using the
+ * <code>applyPattern()</code> method. The <code>format()</code>
+ * method takes a number object and selects the message of the
+ * matching plural case. This message will be returned.
+ * </p>
+ * <h5>Patterns and Their Interpretation</h5>
+ * <p>
+ * The pattern text defines the message output for each plural case of the
+ * specified locale. Syntax:
+ * <blockquote><pre>
+ * pluralStyle = [offsetValue] (selector '{' message '}')+
+ * offsetValue = "offset:" number
+ * selector = explicitValue | keyword
+ * explicitValue = '=' number // adjacent, no white space in between
+ * keyword = [^[[:Pattern_Syntax:][:Pattern_White_Space:]]]+
+ * message: see {@link MessageFormat}
+ * </pre></blockquote>
+ * Pattern_White_Space between syntax elements is ignored, except
+ * between the {curly braces} and their sub-message,
+ * and between the '=' and the number of an explicitValue.
+ *
+ * </p><p>
+ * There are 6 predefined case keywords in CLDR/ICU - 'zero', 'one', 'two', 'few', 'many' and
+ * 'other'. You always have to define a message text for the default plural case
+ * "<code>other</code>" which is contained in every rule set.
+ * If you do not specify a message text for a particular plural case, the
+ * message text of the plural case "<code>other</code>" gets assigned to this
+ * plural case.
+ * </p><p>
+ * When formatting, the input number is first matched against the explicitValue clauses.
+ * If there is no exact-number match, then a keyword is selected by calling
+ * the <code>PluralRules</code> with the input number <em>minus the offset</em>.
+ * (The offset defaults to 0 if it is omitted from the pattern string.)
+ * If there is no clause with that keyword, then the "other" clauses is returned.
+ * </p><p>
+ * An unquoted pound sign (<code>#</code>) in the selected sub-message
+ * itself (i.e., outside of arguments nested in the sub-message)
+ * is replaced by the input number minus the offset.
+ * The number-minus-offset value is formatted using a
+ * <code>NumberFormat</code> for the <code>PluralFormat</code>'s locale. If you
+ * need special number formatting, you have to use a <code>MessageFormat</code>
+ * and explicitly specify a <code>NumberFormat</code> argument.
+ * <strong>Note:</strong> That argument is formatting without subtracting the offset!
+ * If you need a custom format and have a non-zero offset, then you need to pass the
+ * number-minus-offset value as a separate parameter.
+ * </p>
+ * For a usage example, see the {@link MessageFormat} class documentation.
+ *
+ * <h4>Defining Custom Plural Rules</h4>
+ * <p>If you need to use <code>PluralFormat</code> with custom rules, you can
+ * create a <code>PluralRules</code> object and pass it to
+ * <code>PluralFormat</code>'s constructor. If you also specify a locale in this
+ * constructor, this locale will be used to format the number in the message
+ * texts.
+ * </p><p>
+ * For more information about <code>PluralRules</code>, see
+ * {@link PluralRules}.
+ * </p>
+ *
+ * @author tschumann (Tim Schumann)
+ * @stable ICU 3.8
+ */
+public class PluralFormat /* extends UFormat */ {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * The locale used for standard number formatting and getting the predefined
+ * plural rules (if they were not defined explicitely).
+ * @serial
+ */
+ private Locale locale_ = null;
+
+ /**
+ * The plural rules used for plural selection.
+ * @serial
+ */
+ private PluralRules pluralRules = null;
+
+ /**
+ * The applied pattern string.
+ * @serial
+ */
+ private String pattern = null;
+
+ /**
+ * The MessagePattern which contains the parsed structure of the pattern string.
+ */
+ transient private MessagePattern msgPattern;
+
+ /**
+ * Obsolete with use of MessagePattern since ICU 4.8. Used to be:
+ * The format messages for each plural case. It is a mapping:
+ * <code>String</code>(plural case keyword) --&gt; <code>String</code>
+ * (message for this plural case).
+ * @serial
+ */
+ private Map<String, String> parsedValues = null;
+
+ /**
+ * This <code>NumberFormat</code> is used for the standard formatting of
+ * the number inserted into the message.
+ * @serial
+ */
+ private NumberFormat numberFormat = null;
+
+ /**
+ * The offset to subtract before invoking plural rules.
+ */
+ transient private double offset = 0;
+
+ /**
+ * Creates a new cardinal-number <code>PluralFormat</code> for the default <code>FORMAT</code> locale.
+ * This locale will be used to get the set of plural rules and for standard
+ * number formatting.
+ * @see Category#FORMAT
+ * @stable ICU 3.8
+ */
+ public PluralFormat() {
+ init(null, PluralType.CARDINAL, Locale.getDefault()); // Category.FORMAT
+ }
+
+ /**
+ * Creates a new cardinal-number <code>PluralFormat</code> for a given locale.
+ * @param locale the <code>PluralFormat</code> will be configured with
+ * rules for this locale. This locale will also be used for standard
+ * number formatting.
+ * @stable ICU 3.8
+ */
+ public PluralFormat(Locale locale) {
+ init(null, PluralType.CARDINAL, locale);
+ }
+
+ /**
+ * Creates a new <code>PluralFormat</code> for the plural type.
+ * The standard number formatting will be done using the given locale.
+ * @param locale the default number formatting will be done using this
+ * locale.
+ * @param type The plural type (e.g., cardinal or ordinal).
+ * @stable ICU 50
+ */
+ public PluralFormat(Locale locale, PluralType type) {
+ init(null, type, locale);
+ }
+
+ /*
+ * Initializes the <code>PluralRules</code> object.
+ * Postcondition:<br/>
+ * <code>ulocale</code> : is <code>locale</code><br/>
+ * <code>pluralRules</code>: if <code>rules</code> != <code>null</code>
+ * it's set to rules, otherwise it is the
+ * predefined plural rule set for the locale
+ * <code>ulocale</code>.<br/>
+ * <code>parsedValues</code>: is <code>null</code><br/>
+ * <code>pattern</code>: is <code>null</code><br/>
+ * <code>numberFormat</code>: a <code>NumberFormat</code> for the locale
+ * <code>ulocale</code>.
+ */
+ private void init(PluralRules rules, PluralType type, Locale locale) {
+ locale_ = locale;
+ pluralRules = (rules == null) ? PluralRules.forLocale(locale, type)
+ : rules;
+ resetPattern();
+ numberFormat = NumberFormat.getInstance(locale);
+ }
+
+ private void resetPattern() {
+ pattern = null;
+ if(msgPattern != null) {
+ msgPattern.clear();
+ }
+ offset = 0;
+ }
+
+ /**
+ * Sets the pattern used by this plural format.
+ * The method parses the pattern and creates a map of format strings
+ * for the plural rules.
+ * Patterns and their interpretation are specified in the class description.
+ *
+ * @param pattern the pattern for this plural format.
+ * @throws IllegalArgumentException if the pattern is invalid.
+ * @stable ICU 3.8
+ */
+ public void applyPattern(String pattern) {
+ this.pattern = pattern;
+ if (msgPattern == null) {
+ msgPattern = new MessagePattern();
+ }
+ try {
+ msgPattern.parsePluralStyle(pattern);
+ offset = msgPattern.getPluralOffset(0);
+ } catch(RuntimeException e) {
+ resetPattern();
+ throw e;
+ }
+ }
+
+ /**
+ * Returns the pattern for this PluralFormat.
+ *
+ * @return the pattern string
+ * @stable ICU 4.2
+ */
+ public String toPattern() {
+ return pattern;
+ }
+
+ /**
+ * Finds the PluralFormat sub-message for the given number, or the "other" sub-message.
+ * @param pattern A MessagePattern.
+ * @param partIndex the index of the first PluralFormat argument style part.
+ * @param selector the PluralSelector for mapping the number (minus offset) to a keyword.
+ * @param context worker object for the selector.
+ * @param number a number to be matched to one of the PluralFormat argument's explicit values,
+ * or mapped via the PluralSelector.
+ * @return the sub-message start part index.
+ */
+ /*package*/ static int findSubMessage(
+ MessagePattern pattern, int partIndex,
+ PluralSelector selector, Object context, double number) {
+ int count=pattern.countParts();
+ double offset;
+ MessagePattern.Part part=pattern.getPart(partIndex);
+ if(part.getType().hasNumericValue()) {
+ offset=pattern.getNumericValue(part);
+ ++partIndex;
+ } else {
+ offset=0;
+ }
+ // The keyword is null until we need to match against a non-explicit, not-"other" value.
+ // Then we get the keyword from the selector.
+ // (In other words, we never call the selector if we match against an explicit value,
+ // or if the only non-explicit keyword is "other".)
+ String keyword=null;
+ // When we find a match, we set msgStart>0 and also set this boolean to true
+ // to avoid matching the keyword again (duplicates are allowed)
+ // while we continue to look for an explicit-value match.
+ boolean haveKeywordMatch=false;
+ // msgStart is 0 until we find any appropriate sub-message.
+ // We remember the first "other" sub-message if we have not seen any
+ // appropriate sub-message before.
+ // We remember the first matching-keyword sub-message if we have not seen
+ // one of those before.
+ // (The parser allows [does not check for] duplicate keywords.
+ // We just have to make sure to take the first one.)
+ // We avoid matching the keyword twice by also setting haveKeywordMatch=true
+ // at the first keyword match.
+ // We keep going until we find an explicit-value match or reach the end of the plural style.
+ int msgStart=0;
+ // Iterate over (ARG_SELECTOR [ARG_INT|ARG_DOUBLE] message) tuples
+ // until ARG_LIMIT or end of plural-only pattern.
+ do {
+ part=pattern.getPart(partIndex++);
+ MessagePattern.Part.Type type=part.getType();
+ if(type==MessagePattern.Part.Type.ARG_LIMIT) {
+ break;
+ }
+ assert type==MessagePattern.Part.Type.ARG_SELECTOR;
+ // part is an ARG_SELECTOR followed by an optional explicit value, and then a message
+ if(pattern.getPartType(partIndex).hasNumericValue()) {
+ // explicit value like "=2"
+ part=pattern.getPart(partIndex++);
+ if(number==pattern.getNumericValue(part)) {
+ // matches explicit value
+ return partIndex;
+ }
+ } else if(!haveKeywordMatch) {
+ // plural keyword like "few" or "other"
+ // Compare "other" first and call the selector if this is not "other".
+ if(pattern.partSubstringMatches(part, "other")) {
+ if(msgStart==0) {
+ msgStart=partIndex;
+ if(keyword!=null && keyword.equals("other")) {
+ // This is the first "other" sub-message,
+ // and the selected keyword is also "other".
+ // Do not match "other" again.
+ haveKeywordMatch=true;
+ }
+ }
+ } else {
+ if(keyword==null) {
+ keyword=selector.select(context, number-offset);
+ if(msgStart!=0 && keyword.equals("other")) {
+ // We have already seen an "other" sub-message.
+ // Do not match "other" again.
+ haveKeywordMatch=true;
+ // Skip keyword matching but do getLimitPartIndex().
+ }
+ }
+ if(!haveKeywordMatch && pattern.partSubstringMatches(part, keyword)) {
+ // keyword matches
+ msgStart=partIndex;
+ // Do not match this keyword again.
+ haveKeywordMatch=true;
+ }
+ }
+ }
+ partIndex=pattern.getLimitPartIndex(partIndex);
+ } while(++partIndex<count);
+ return msgStart;
+ }
+
+ /**
+ * Interface for selecting PluralFormat keywords for numbers.
+ * The PluralRules class was intended to implement this interface,
+ * but there is no public API that uses a PluralSelector,
+ * only MessageFormat and PluralFormat have PluralSelector implementations.
+ * Therefore, PluralRules is not marked to implement this non-public interface,
+ * to avoid confusing users.
+ * @internal
+ */
+ /*package*/ interface PluralSelector {
+ /**
+ * Given a number, returns the appropriate PluralFormat keyword.
+ *
+ * @param context worker object for the selector.
+ * @param number The number to be plural-formatted.
+ * @return The selected PluralFormat keyword.
+ */
+ public String select(Object context, double number);
+ }
+
+ // See PluralSelector:
+ // We could avoid this adapter class if we made PluralSelector public
+ // (or at least publicly visible) and had PluralRules implement PluralSelector.
+ private final class PluralSelectorAdapter implements PluralSelector {
+ public String select(Object context, double number) {
+ FixedDecimal dec = (FixedDecimal) context;
+ assert dec.source == number;
+ return pluralRules.select(dec);
+ }
+ }
+ transient private PluralSelectorAdapter pluralRulesWrapper = new PluralSelectorAdapter();
+
+ /**
+ * This method is not yet supported by <code>PluralFormat</code>.
+ * @param text the string to be parsed.
+ * @param parsePosition defines the position where parsing is to begin,
+ * and upon return, the position where parsing left off. If the position
+ * has not changed upon return, then parsing failed.
+ * @return nothing because this method is not yet implemented.
+ * @throws UnsupportedOperationException will always be thrown by this method.
+ * @stable ICU 3.8
+ */
+ public Number parse(String text, ParsePosition parsePosition) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * This method is not yet supported by <code>PluralFormat</code>.
+ * @param source the string to be parsed.
+ * @param pos defines the position where parsing is to begin,
+ * and upon return, the position where parsing left off. If the position
+ * has not changed upon return, then parsing failed.
+ * @return nothing because this method is not yet implemented.
+ * @throws UnsupportedOperationException will always be thrown by this method.
+ * @stable ICU 3.8
+ */
+ public Object parseObject(String source, ParsePosition pos) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Returns true if this equals the provided PluralFormat.
+ * @param rhs the PluralFormat to compare against
+ * @return true if this equals rhs
+ * @stable ICU 3.8
+ */
+ public boolean equals(PluralFormat rhs) {
+ return equals((Object)rhs);
+ }
+
+ /**
+ * {@inheritDoc}
+ * @stable ICU 3.8
+ */
+ @Override
+ public int hashCode() {
+ return pluralRules.hashCode() ^ parsedValues.hashCode();
+ }
+
+ /**
+ * {@inheritDoc}
+ * @stable ICU 3.8
+ */
+ @Override
+ public String toString() {
+ StringBuilder buf = new StringBuilder();
+ buf.append("locale=" + locale_);
+ buf.append(", rules='" + pluralRules + "'");
+ buf.append(", pattern='" + pattern + "'");
+ buf.append(", format='" + numberFormat + "'");
+ return buf.toString();
+ }
+
+ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+ in.defaultReadObject();
+ pluralRulesWrapper = new PluralSelectorAdapter();
+ // Ignore the parsedValues from an earlier class version (before ICU 4.8)
+ // and rebuild the msgPattern.
+ parsedValues = null;
+ if (pattern != null) {
+ applyPattern(pattern);
+ }
+ }
+}
diff --git a/src/main/com/android/i18n/PluralRules.java b/src/com/ibm/icu/simple/PluralRules.java
index 10e0be0..ab0039d 100644
--- a/src/main/com/android/i18n/PluralRules.java
+++ b/src/com/ibm/icu/simple/PluralRules.java
@@ -1,17 +1,16 @@
/*
*******************************************************************************
- * Copyright (C) 2007-2013, International Business Machines Corporation and
+ * Copyright (C) 2007-2014, International Business Machines Corporation and
* others. All Rights Reserved.
*******************************************************************************
*/
-package com.ibm.icu.text;
+package com.ibm.icu.simple;
import java.io.IOException;
import java.io.NotSerializableException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
-import java.io.ObjectStreamException;
import java.io.Serializable;
import java.text.ParseException;
import java.util.ArrayList;
@@ -26,9 +25,7 @@ import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Pattern;
-import com.ibm.icu.impl.PluralRulesLoader;
import com.ibm.icu.util.Output;
-import com.ibm.icu.util.ULocale;
/**
* <p>
@@ -167,18 +164,20 @@ import com.ibm.icu.util.ULocale;
*/
public class PluralRules implements Serializable {
- static final UnicodeSet ALLOWED_ID = new UnicodeSet("[a-z]").freeze();
+ // static final UnicodeSet ALLOWED_ID = new UnicodeSet("[a-z]").freeze();
// TODO Remove RulesList by moving its API and fields into PluralRules.
/**
* @internal
* @deprecated This API is ICU internal only.
*/
+ @Deprecated
public static final String CATEGORY_SEPARATOR = "; ";
/**
* @internal
* @deprecated This API is ICU internal only.
*/
+ @Deprecated
public static final String KEYWORD_RULE_SEPARATOR = ": ";
private static final long serialVersionUID = 1;
@@ -189,9 +188,10 @@ public class PluralRules implements Serializable {
/**
* Provides a factory for returning plural rules
*
- * @deprecated This API is ICU internal only.
* @internal
+ * @deprecated This API is ICU internal only.
*/
+ @Deprecated
public static abstract class Factory {
/**
* Provides access to the predefined <code>PluralRules</code> for a given locale and the plural type.
@@ -207,29 +207,32 @@ public class PluralRules implements Serializable {
* @return The predefined <code>PluralRules</code> object for this locale. If there's no predefined rules for
* this locale, the rules for the closest parent in the locale hierarchy that has one will be returned.
* The final fallback always returns the default rules.
- * @deprecated This API is ICU internal only.
* @internal
+ * @deprecated This API is ICU internal only.
*/
- public abstract PluralRules forLocale(ULocale locale, PluralType type);
+ @Deprecated
+ public abstract PluralRules forLocale(Locale locale, PluralType type);
/**
* Utility for getting CARDINAL rules.
* @param locale the locale
* @return plural rules.
- * @deprecated This API is ICU internal only.
* @internal
+ * @deprecated This API is ICU internal only.
*/
- public final PluralRules forLocale(ULocale locale) {
+ @Deprecated
+ public final PluralRules forLocale(Locale locale) {
return forLocale(locale, PluralType.CARDINAL);
}
/**
* Returns the locales for which there is plurals data.
*
- * @deprecated This API is ICU internal only.
* @internal
- */
+ * @deprecated This API is ICU internal only.
+ @Deprecated
public abstract ULocale[] getAvailableULocales();
+ */
/**
* Returns the 'functionally equivalent' locale with respect to plural rules. Calling PluralRules.forLocale with
@@ -244,26 +247,29 @@ public class PluralRules implements Serializable {
* if not null and of length > 0, this will hold 'true' at index 0 if locale is directly defined
* (without fallback) as having plural rules
* @return the functionally-equivalent locale
- * @deprecated This API is ICU internal only.
* @internal
- */
+ * @deprecated This API is ICU internal only.
+ @Deprecated
public abstract ULocale getFunctionalEquivalent(ULocale locale, boolean[] isAvailable);
+ */
/**
* Returns the default factory.
- * @deprecated This API is ICU internal only.
* @internal
+ * @deprecated This API is ICU internal only.
*/
+ @Deprecated
public static PluralRulesLoader getDefaultFactory() {
return PluralRulesLoader.loader;
}
/**
* Returns whether or not there are overrides.
- * @deprecated This API is ICU internal only.
* @internal
- */
+ * @deprecated This API is ICU internal only.
+ @Deprecated
public abstract boolean hasOverride(ULocale locale);
+ */
}
// Standard keywords.
@@ -396,7 +402,7 @@ public class PluralRules implements Serializable {
t,
v,
w,
- /**@deprecated*/
+ /* deprecated */
j;
}
@@ -404,47 +410,56 @@ public class PluralRules implements Serializable {
* @internal
* @deprecated This API is ICU internal only.
*/
+ @Deprecated
public static class FixedDecimal extends Number implements Comparable<FixedDecimal> {
private static final long serialVersionUID = -4756200506571685661L;
/**
* @internal
* @deprecated This API is ICU internal only.
*/
+ @Deprecated
public final double source;
/**
* @internal
* @deprecated This API is ICU internal only.
*/
+ @Deprecated
public final int visibleDecimalDigitCount;
/**
* @internal
* @deprecated This API is ICU internal only.
*/
+ @Deprecated
public final int visibleDecimalDigitCountWithoutTrailingZeros;
/**
* @internal
* @deprecated This API is ICU internal only.
*/
+ @Deprecated
public final long decimalDigits;
/**
* @internal
* @deprecated This API is ICU internal only.
*/
+ @Deprecated
public final long decimalDigitsWithoutTrailingZeros;
/**
* @internal
* @deprecated This API is ICU internal only.
*/
+ @Deprecated
public final long integerValue;
/**
* @internal
* @deprecated This API is ICU internal only.
*/
+ @Deprecated
public final boolean hasIntegerValue;
/**
* @internal
* @deprecated This API is ICU internal only.
*/
+ @Deprecated
public final boolean isNegative;
private final int baseFactor;
@@ -452,6 +467,7 @@ public class PluralRules implements Serializable {
* @internal
* @deprecated This API is ICU internal only.
*/
+ @Deprecated
public double getSource() {
return source;
}
@@ -460,6 +476,7 @@ public class PluralRules implements Serializable {
* @internal
* @deprecated This API is ICU internal only.
*/
+ @Deprecated
public int getVisibleDecimalDigitCount() {
return visibleDecimalDigitCount;
}
@@ -468,6 +485,7 @@ public class PluralRules implements Serializable {
* @internal
* @deprecated This API is ICU internal only.
*/
+ @Deprecated
public int getVisibleDecimalDigitCountWithoutTrailingZeros() {
return visibleDecimalDigitCountWithoutTrailingZeros;
}
@@ -476,6 +494,7 @@ public class PluralRules implements Serializable {
* @internal
* @deprecated This API is ICU internal only.
*/
+ @Deprecated
public long getDecimalDigits() {
return decimalDigits;
}
@@ -484,6 +503,7 @@ public class PluralRules implements Serializable {
* @internal
* @deprecated This API is ICU internal only.
*/
+ @Deprecated
public long getDecimalDigitsWithoutTrailingZeros() {
return decimalDigitsWithoutTrailingZeros;
}
@@ -492,6 +512,7 @@ public class PluralRules implements Serializable {
* @internal
* @deprecated This API is ICU internal only.
*/
+ @Deprecated
public long getIntegerValue() {
return integerValue;
}
@@ -500,6 +521,7 @@ public class PluralRules implements Serializable {
* @internal
* @deprecated This API is ICU internal only.
*/
+ @Deprecated
public boolean isHasIntegerValue() {
return hasIntegerValue;
}
@@ -508,6 +530,7 @@ public class PluralRules implements Serializable {
* @internal
* @deprecated This API is ICU internal only.
*/
+ @Deprecated
public boolean isNegative() {
return isNegative;
}
@@ -516,10 +539,13 @@ public class PluralRules implements Serializable {
* @internal
* @deprecated This API is ICU internal only.
*/
+ @Deprecated
public int getBaseFactor() {
return baseFactor;
}
+ static final long MAX = (long)1E18;
+
/**
* @internal
* @deprecated This API is ICU internal only.
@@ -528,12 +554,15 @@ public class PluralRules implements Serializable {
* @param f Corresponds to f in the plural rules grammar.
* The digits to the right of the decimal place as an integer. e.g 1.10 = 10
*/
+ @Deprecated
public FixedDecimal(double n, int v, long f) {
isNegative = n < 0;
source = isNegative ? -n : n;
visibleDecimalDigitCount = v;
decimalDigits = f;
- integerValue = (long)n;
+ integerValue = n > MAX
+ ? MAX
+ : (long)n;
hasIntegerValue = source == integerValue;
// check values. TODO make into unit test.
//
@@ -568,6 +597,7 @@ public class PluralRules implements Serializable {
* @internal
* @deprecated This API is ICU internal only.
*/
+ @Deprecated
public FixedDecimal(double n, int v) {
this(n,v,getFractionalDigits(n, v));
}
@@ -576,6 +606,9 @@ public class PluralRules implements Serializable {
if (v == 0) {
return 0;
} else {
+ if (n < 0) {
+ n = -n;
+ }
int baseFactor = (int) Math.pow(10, v);
long scaled = Math.round(n * baseFactor);
return (int) (scaled % baseFactor);
@@ -586,6 +619,7 @@ public class PluralRules implements Serializable {
* @internal
* @deprecated This API is ICU internal only.
*/
+ @Deprecated
public FixedDecimal(double n) {
this(n, decimals(n));
}
@@ -594,24 +628,65 @@ public class PluralRules implements Serializable {
* @internal
* @deprecated This API is ICU internal only.
*/
+ @Deprecated
public FixedDecimal(long n) {
this(n,0);
}
+ private static final long MAX_INTEGER_PART = 1000000000;
/**
+ * Return a guess as to the number of decimals that would be displayed. This is only a guess; callers should
+ * always supply the decimals explicitly if possible. Currently, it is up to 6 decimals (without trailing zeros).
+ * Returns 0 for infinities and nans.
* @internal
* @deprecated This API is ICU internal only.
+ *
*/
+ @Deprecated
public static int decimals(double n) {
// Ugly...
- String temp = String.valueOf(n);
- return temp.endsWith(".0") ? 0 : temp.length() - temp.indexOf('.') - 1;
+ if (Double.isInfinite(n) || Double.isNaN(n)) {
+ return 0;
+ }
+ if (n < 0) {
+ n = -n;
+ }
+ if (n < MAX_INTEGER_PART) {
+ long temp = (long)(n * 1000000) % 1000000; // get 6 decimals
+ for (int mask = 10, digits = 6; digits > 0; mask *= 10, --digits) {
+ if ((temp % mask) != 0) {
+ return digits;
+ }
+ }
+ return 0;
+ } else {
+ String buf = String.format(Locale.ENGLISH, "%1.15e", n);
+ int ePos = buf.lastIndexOf('e');
+ int expNumPos = ePos + 1;
+ if (buf.charAt(expNumPos) == '+') {
+ expNumPos++;
+ }
+ String exponentStr = buf.substring(expNumPos);
+ int exponent = Integer.parseInt(exponentStr);
+ int numFractionDigits = ePos - 2 - exponent;
+ if (numFractionDigits < 0) {
+ return 0;
+ }
+ for (int i=ePos-1; numFractionDigits > 0; --i) {
+ if (buf.charAt(i) != '0') {
+ break;
+ }
+ --numFractionDigits;
+ }
+ return numFractionDigits;
+ }
}
/**
* @internal
* @deprecated This API is ICU internal only.
*/
+ @Deprecated
public FixedDecimal (String n) {
// Ugly, but for samples we don't care.
this(Double.parseDouble(n), getVisibleFractionCount(n));
@@ -631,6 +706,7 @@ public class PluralRules implements Serializable {
* @internal
* @deprecated This API is ICU internal only.
*/
+ @Deprecated
public double get(Operand operand) {
switch(operand) {
default: return source;
@@ -646,6 +722,7 @@ public class PluralRules implements Serializable {
* @internal
* @deprecated This API is ICU internal only.
*/
+ @Deprecated
public static Operand getOperand(String t) {
return Operand.valueOf(t);
}
@@ -655,6 +732,7 @@ public class PluralRules implements Serializable {
* @internal
* @deprecated This API is ICU internal only.
*/
+ @Deprecated
public int compareTo(FixedDecimal other) {
if (integerValue != other.integerValue) {
return integerValue < other.integerValue ? -1 : 1;
@@ -676,6 +754,7 @@ public class PluralRules implements Serializable {
* @internal
* @deprecated This API is ICU internal only.
*/
+ @Deprecated
@Override
public boolean equals(Object arg0) {
if (arg0 == null) {
@@ -695,6 +774,7 @@ public class PluralRules implements Serializable {
* @internal
* @deprecated This API is ICU internal only.
*/
+ @Deprecated
@Override
public int hashCode() {
// TODO Auto-generated method stub
@@ -705,6 +785,7 @@ public class PluralRules implements Serializable {
* @internal
* @deprecated This API is ICU internal only.
*/
+ @Deprecated
@Override
public String toString() {
return String.format("%." + visibleDecimalDigitCount + "f", source);
@@ -714,6 +795,7 @@ public class PluralRules implements Serializable {
* @internal
* @deprecated This API is ICU internal only.
*/
+ @Deprecated
public boolean hasIntegerValue() {
return hasIntegerValue;
}
@@ -722,6 +804,7 @@ public class PluralRules implements Serializable {
* @internal
* @deprecated This API is ICU internal only.
*/
+ @Deprecated
@Override
public int intValue() {
// TODO Auto-generated method stub
@@ -732,6 +815,7 @@ public class PluralRules implements Serializable {
* @internal
* @deprecated This API is ICU internal only.
*/
+ @Deprecated
@Override
public long longValue() {
return integerValue;
@@ -741,6 +825,7 @@ public class PluralRules implements Serializable {
* @internal
* @deprecated This API is ICU internal only.
*/
+ @Deprecated
@Override
public float floatValue() {
return (float) source;
@@ -750,15 +835,17 @@ public class PluralRules implements Serializable {
* @internal
* @deprecated This API is ICU internal only.
*/
+ @Deprecated
@Override
public double doubleValue() {
- return source;
+ return isNegative ? -source : source;
}
/**
* @internal
* @deprecated This API is ICU internal only.
*/
+ @Deprecated
public long getShiftedValue() {
return integerValue * baseFactor + decimalDigits;
}
@@ -780,28 +867,46 @@ public class PluralRules implements Serializable {
* @internal
* @deprecated This API is ICU internal only.
*/
- public enum SampleType {INTEGER, DECIMAL}
+ @Deprecated
+ public enum SampleType {
+ /**
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ @Deprecated
+ INTEGER,
+ /**
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ @Deprecated
+ DECIMAL
+ }
/**
* A range of NumberInfo that includes all values with the same visibleFractionDigitCount.
* @internal
* @deprecated This API is ICU internal only.
*/
+ @Deprecated
public static class FixedDecimalRange {
/**
* @internal
* @deprecated This API is ICU internal only.
*/
+ @Deprecated
public final FixedDecimal start;
/**
* @internal
* @deprecated This API is ICU internal only.
*/
+ @Deprecated
public final FixedDecimal end;
/**
* @internal
* @deprecated This API is ICU internal only.
*/
+ @Deprecated
public FixedDecimalRange(FixedDecimal start, FixedDecimal end) {
if (start.visibleDecimalDigitCount != end.visibleDecimalDigitCount) {
throw new IllegalArgumentException("Ranges must have the same number of visible decimals: " + start + "~" + end);
@@ -813,6 +918,7 @@ public class PluralRules implements Serializable {
* @internal
* @deprecated This API is ICU internal only.
*/
+ @Deprecated
@Override
public String toString() {
return start + (end == start ? "" : "~" + end);
@@ -824,21 +930,25 @@ public class PluralRules implements Serializable {
* @internal
* @deprecated This API is ICU internal only.
*/
+ @Deprecated
public static class FixedDecimalSamples {
/**
* @internal
* @deprecated This API is ICU internal only.
*/
+ @Deprecated
public final SampleType sampleType;
/**
* @internal
* @deprecated This API is ICU internal only.
*/
+ @Deprecated
public final Set<FixedDecimalRange> samples;
/**
* @internal
* @deprecated This API is ICU internal only.
*/
+ @Deprecated
public final boolean bounded;
/**
* The samples must be immutable.
@@ -908,6 +1018,7 @@ public class PluralRules implements Serializable {
* @internal
* @deprecated This API is ICU internal only.
*/
+ @Deprecated
public Set<Double> addSamples(Set<Double> result) {
for (FixedDecimalRange item : samples) {
// we have to convert to longs so we don't get strange double issues
@@ -925,6 +1036,7 @@ public class PluralRules implements Serializable {
* @internal
* @deprecated This API is ICU internal only.
*/
+ @Deprecated
@Override
public String toString() {
StringBuilder b = new StringBuilder("@").append(sampleType.toString().toLowerCase(Locale.ENGLISH));
@@ -947,6 +1059,7 @@ public class PluralRules implements Serializable {
* @internal
* @deprecated This API is ICU internal only.
*/
+ @Deprecated
public Set<FixedDecimalRange> getSamples() {
return samples;
}
@@ -955,6 +1068,7 @@ public class PluralRules implements Serializable {
* @internal
* @deprecated This API is ICU internal only.
*/
+ @Deprecated
public void getStartEndSamples(Set<FixedDecimal> target) {
for (FixedDecimalRange item : samples) {
target.add(item.start);
@@ -980,20 +1094,26 @@ public class PluralRules implements Serializable {
boolean isLimited(SampleType sampleType);
}
+ private static final boolean isBreakAndIgnore(char c) {
+ return c <= 0x20 && (c == 0x20 || c == 9 || c == 0xa || c == 0xc || c == 0xd);
+ }
+ private static final boolean isBreakAndKeep(char c) {
+ return c <= '=' && c >= '!' && (c == '!' || c == '%' || c == ',' || c == '.' || c == '=');
+ }
static class SimpleTokenizer {
- static final UnicodeSet BREAK_AND_IGNORE = new UnicodeSet(0x09, 0x0a, 0x0c, 0x0d, 0x20, 0x20).freeze();
- static final UnicodeSet BREAK_AND_KEEP = new UnicodeSet('!', '!', '%', '%', ',', ',', '.', '.', '=', '=').freeze();
+ // static final UnicodeSet BREAK_AND_IGNORE = new UnicodeSet(0x09, 0x0a, 0x0c, 0x0d, 0x20, 0x20).freeze();
+ // static final UnicodeSet BREAK_AND_KEEP = new UnicodeSet('!', '!', '%', '%', ',', ',', '.', '.', '=', '=').freeze();
static String[] split(String source) {
int last = -1;
List<String> result = new ArrayList<String>();
for (int i = 0; i < source.length(); ++i) {
char ch = source.charAt(i);
- if (BREAK_AND_IGNORE.contains(ch)) {
+ if (isBreakAndIgnore(ch) /* BREAK_AND_IGNORE.contains(ch) */) {
if (last >= 0) {
result.add(source.substring(last,i));
last = -1;
}
- } else if (BREAK_AND_KEEP.contains(ch)) {
+ } else if (isBreakAndKeep(ch) /* BREAK_AND_KEEP.contains(ch) */) {
if (last >= 0) {
result.add(source.substring(last,i));
}
@@ -1517,6 +1637,7 @@ public class PluralRules implements Serializable {
* @internal
* @deprecated This API is ICU internal only.
*/
+ @Deprecated
@Override
public int hashCode() {
return keyword.hashCode() ^ constraint.hashCode();
@@ -1570,11 +1691,10 @@ public class PluralRules implements Serializable {
}
public String select(FixedDecimal n) {
+ if (Double.isInfinite(n.source) || Double.isNaN(n.source)) {
+ return KEYWORD_OTHER;
+ }
Rule r = selectRule(n);
- // since we have explict 'other', we don't need this.
- // if (r == null) {
- // return KEYWORD_OTHER;
- // }
return r.getKeyword();
}
@@ -1655,12 +1775,43 @@ public class PluralRules implements Serializable {
* @deprecated This API is ICU internal only.
* @internal
*/
+ @Deprecated
public enum StandardPluralCategories {
+ /**
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ @Deprecated
zero,
+ /**
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ @Deprecated
one,
+ /**
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ @Deprecated
two,
+ /**
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ @Deprecated
few,
+ /**
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ @Deprecated
many,
+ /**
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ @Deprecated
other;
static StandardPluralCategories forString(String s) {
StandardPluralCategories a;
@@ -1710,8 +1861,8 @@ public class PluralRules implements Serializable {
* rules.
* @stable ICU 3.8
*/
- public static PluralRules forLocale(ULocale locale) {
- return Factory.getDefaultFactory().forLocale(locale, PluralType.CARDINAL);
+ public static PluralRules forLocale(Locale locale) {
+ return forLocale(locale, PluralType.CARDINAL);
}
/**
@@ -1732,7 +1883,7 @@ public class PluralRules implements Serializable {
* rules.
* @stable ICU 50
*/
- public static PluralRules forLocale(ULocale locale, PluralType type) {
+ public static PluralRules forLocale(Locale locale, PluralType type) {
return Factory.getDefaultFactory().forLocale(locale, type);
}
@@ -1743,7 +1894,14 @@ public class PluralRules implements Serializable {
* @return true if the token is a valid keyword.
*/
private static boolean isValidKeyword(String token) {
- return ALLOWED_ID.containsAll(token);
+ // return ALLOWED_ID.containsAll(token);
+ for (int i = 0; i < token.length(); ++i) {
+ char c = token.charAt(i);
+ if (!('a' <= c && c <= 'z')) {
+ return false;
+ }
+ }
+ return true;
}
/*
@@ -1758,6 +1916,7 @@ public class PluralRules implements Serializable {
* @internal
* @deprecated This API is ICU internal only.
*/
+ @Deprecated
@Override
public int hashCode() {
return rules.hashCode();
@@ -1783,6 +1942,7 @@ public class PluralRules implements Serializable {
* @internal
* @deprecated This API is ICU internal only.
*/
+ @Deprecated
public String select(double number, int countVisibleFractionDigits, long fractionaldigits) {
return rules.select(new FixedDecimal(number, countVisibleFractionDigits, fractionaldigits));
}
@@ -1796,6 +1956,7 @@ public class PluralRules implements Serializable {
* @internal
* @deprecated This API is ICU internal only.
*/
+ @Deprecated
public String select(FixedDecimal sample) {
return rules.select(sample);
}
@@ -1809,6 +1970,7 @@ public class PluralRules implements Serializable {
* @internal
* @deprecated This API is ICU internal only.
*/
+ @Deprecated
public boolean matches(FixedDecimal sample, String keyword) {
return rules.select(sample, keyword);
}
@@ -1858,12 +2020,14 @@ public class PluralRules implements Serializable {
* values is unlimited.
*
* @param keyword the keyword
+ * @param type the type of samples requested, INTEGER or DECIMAL
* @return the values that trigger this keyword, or null. The returned collection
* is immutable. It will be empty if the keyword is not defined.
*
* @internal
* @deprecated This API is ICU internal only.
*/
+ @Deprecated
public Collection<Double> getAllKeywordValues(String keyword, SampleType type) {
if (!isLimited(keyword, type)) {
return null;
@@ -1873,7 +2037,7 @@ public class PluralRules implements Serializable {
}
/**
- * Returns a list of values for which select() would return that keyword,
+ * Returns a list of integer values for which select() would return that keyword,
* or null if the keyword is not defined. The returned collection is unmodifiable.
* The returned list is not complete, and there might be additional values that
* would return the keyword.
@@ -1895,10 +2059,12 @@ public class PluralRules implements Serializable {
* IF there are samples for the other sampleType.
*
* @param keyword the keyword to test
+ * @param sampleType the type of samples requested, INTEGER or DECIMAL
* @return a list of values matching the keyword.
+ * @internal
* @deprecated ICU internal only
- * @internal
*/
+ @Deprecated
public Collection<Double> getSamples(String keyword, SampleType sampleType) {
if (!keywords.contains(keyword)) {
return null;
@@ -1939,6 +2105,7 @@ public class PluralRules implements Serializable {
* @internal
* @deprecated This API is ICU internal only.
*/
+ @Deprecated
public boolean addSample(String keyword, Number sample, int maxCount, Set<Double> result) {
String selectedKeyword = sample instanceof FixedDecimal ? select((FixedDecimal)sample) : select(sample.doubleValue());
if (selectedKeyword.equals(keyword)) {
@@ -1958,10 +2125,12 @@ public class PluralRules implements Serializable {
* would return the keyword.
*
* @param keyword the keyword to test
+ * @param sampleType the type of samples requested, INTEGER or DECIMAL
* @return a list of values matching the keyword.
* @internal
* @deprecated This API is ICU internal only.
*/
+ @Deprecated
public FixedDecimalSamples getDecimalSamples(String keyword, SampleType sampleType) {
return rules.getDecimalSamples(keyword, sampleType);
}
@@ -1971,10 +2140,10 @@ public class PluralRules implements Serializable {
* @return the set of locales for which PluralRules are known, as a list
* @draft ICU 4.2
* @provisional This API might change or be removed in a future release.
- */
public static ULocale[] getAvailableULocales() {
return Factory.getDefaultFactory().getAvailableULocales();
}
+ */
/**
* Returns the 'functionally equivalent' locale with respect to
@@ -1992,10 +2161,10 @@ public class PluralRules implements Serializable {
* @return the functionally-equivalent locale
* @draft ICU 4.2
* @provisional This API might change or be removed in a future release.
- */
public static ULocale getFunctionalEquivalent(ULocale locale, boolean[] isAvailable) {
return Factory.getDefaultFactory().getFunctionalEquivalent(locale, isAvailable);
}
+ */
/**
* {@inheritDoc}
@@ -2098,6 +2267,8 @@ public class PluralRules implements Serializable {
* checking against the keyword values.
* @param explicits
* a set of Doubles that are used explicitly (eg [=0], "[=1]"). May be empty or null.
+ * @param sampleType
+ * request KeywordStatus relative to INTEGER or DECIMAL values
* @param uniqueValue
* If non null, set to the unique value.
* @return the KeywordStatus
@@ -2159,10 +2330,11 @@ public class PluralRules implements Serializable {
* @internal
* @deprecated This API is ICU internal only.
*/
+ @Deprecated
public String getRules(String keyword) {
return rules.getRules(keyword);
}
-
+ /*
private void writeObject(
ObjectOutputStream out)
throws IOException {
@@ -2177,11 +2349,12 @@ public class PluralRules implements Serializable {
private Object writeReplace() throws ObjectStreamException {
return new PluralRulesSerialProxy(toString());
}
-
+ */
/**
* @internal
* @deprecated internal
*/
+ @Deprecated
public int compareTo(PluralRules other) {
return toString().compareTo(other.toString());
}
@@ -2190,6 +2363,7 @@ public class PluralRules implements Serializable {
* @internal
* @deprecated internal
*/
+ @Deprecated
public Boolean isLimited(String keyword) {
return rules.isLimited(keyword, SampleType.INTEGER);
}
@@ -2198,6 +2372,7 @@ public class PluralRules implements Serializable {
* @internal
* @deprecated internal
*/
+ @Deprecated
public boolean isLimited(String keyword, SampleType sampleType) {
return rules.isLimited(keyword, sampleType);
}
@@ -2206,6 +2381,7 @@ public class PluralRules implements Serializable {
* @internal
* @deprecated internal
*/
+ @Deprecated
public boolean computeLimited(String keyword, SampleType sampleType) {
return rules.computeLimited(keyword, sampleType);
}
diff --git a/src/com/ibm/icu/simple/PluralRulesLoader.java b/src/com/ibm/icu/simple/PluralRulesLoader.java
new file mode 100644
index 0000000..23383ea
--- /dev/null
+++ b/src/com/ibm/icu/simple/PluralRulesLoader.java
@@ -0,0 +1,180 @@
+/*
+ *******************************************************************************
+ * Copyright (C) 2008-2013, International Business Machines Corporation and *
+ * others. All Rights Reserved. *
+ *******************************************************************************
+ */
+package com.ibm.icu.simple;
+
+import java.text.ParseException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+import java.util.TreeMap;
+
+import com.ibm.icu.simple.PluralRules.PluralType;
+
+/**
+ * Loader for plural rules data.
+ */
+public class PluralRulesLoader extends PluralRules.Factory {
+ // Data created from ICU4C with the command
+ // ~/svn.icu/trunk/bld$ LD_LIBRARY_PATH=lib bin/genrb --write-java UTF-8 --java-package com.ibm.icu.simple -s ../src/source/data/misc/ plurals.txt -d /tmp/icu
+ private static final ResourceBundle DATA_RB = new LocaleElements_plurals();
+
+ private final Map<String, PluralRules> rulesIdToRules;
+ // lazy init, use getLocaleIdToRulesIdMap to access
+ private Map<String, String> localeIdToCardinalRulesId;
+ private Map<String, String> localeIdToOrdinalRulesId;
+
+ /**
+ * Access through singleton.
+ */
+ private PluralRulesLoader() {
+ rulesIdToRules = new HashMap<String, PluralRules>();
+ }
+
+ /**
+ * Returns the lazily-constructed map.
+ */
+ private Map<String, String> getLocaleIdToRulesIdMap(PluralType type) {
+ checkBuildRulesIdMaps();
+ return (type == PluralType.CARDINAL) ? localeIdToCardinalRulesId : localeIdToOrdinalRulesId;
+ }
+
+ /**
+ * Lazily constructs the localeIdToRulesId and rulesIdToEquivalentULocale
+ * maps if necessary. These exactly reflect the contents of the locales
+ * resource in plurals.res.
+ */
+ private void checkBuildRulesIdMaps() {
+ boolean haveMap;
+ synchronized (this) {
+ haveMap = localeIdToCardinalRulesId != null;
+ }
+ if (!haveMap) {
+ Map<String, String> tempLocaleIdToCardinalRulesId;
+ Map<String, String> tempLocaleIdToOrdinalRulesId;
+ try {
+ ResourceBundle pluralb = DATA_RB;
+ // Read cardinal-number rules.
+ Object[][] localeb = (Object[][]) pluralb.getObject("locales");
+
+ // sort for convenience of getAvailableULocales
+ tempLocaleIdToCardinalRulesId = new TreeMap<String, String>();
+
+ for (Object[] langAndId : localeb) {
+ String id = (String) langAndId[0];
+ String value = (String) langAndId[1];
+ tempLocaleIdToCardinalRulesId.put(id, value);
+ }
+
+ // Read ordinal-number rules.
+ localeb = (Object[][]) pluralb.getObject("locales_ordinals");
+ tempLocaleIdToOrdinalRulesId = new TreeMap<String, String>();
+ for (Object[] langAndId : localeb) {
+ String id = (String) langAndId[0];
+ String value = (String) langAndId[1];
+ tempLocaleIdToOrdinalRulesId.put(id, value);
+ }
+ } catch (MissingResourceException e) {
+ // dummy so we don't try again
+ tempLocaleIdToCardinalRulesId = Collections.emptyMap();
+ tempLocaleIdToOrdinalRulesId = Collections.emptyMap();
+ }
+
+ synchronized(this) {
+ if (localeIdToCardinalRulesId == null) {
+ localeIdToCardinalRulesId = tempLocaleIdToCardinalRulesId;
+ localeIdToOrdinalRulesId = tempLocaleIdToOrdinalRulesId;
+ }
+ }
+ }
+ }
+
+ /**
+ * Gets the rulesId from the locale,with locale fallback. If there is no
+ * rulesId, return null. The rulesId might be the empty string if the rule
+ * is the default rule.
+ */
+ public String getRulesIdForLocale(Locale locale, PluralType type) {
+ Map<String, String> idMap = getLocaleIdToRulesIdMap(type);
+ String lang = locale.getLanguage();
+ String rulesId = idMap.get(lang);
+ return rulesId;
+ }
+
+ /**
+ * Gets the rule from the rulesId. If there is no rule for this rulesId,
+ * return null.
+ */
+ public PluralRules getRulesForRulesId(String rulesId) {
+ // synchronize on the map. release the lock temporarily while we build the rules.
+ PluralRules rules = null;
+ boolean hasRules; // Separate boolean because stored rules can be null.
+ synchronized (rulesIdToRules) {
+ hasRules = rulesIdToRules.containsKey(rulesId);
+ if (hasRules) {
+ rules = rulesIdToRules.get(rulesId); // can be null
+ }
+ }
+ if (!hasRules) {
+ try {
+ ResourceBundle pluralb = DATA_RB;
+ Object[][] rulesb = (Object[][]) pluralb.getObject("rules");
+ Object[][] setb = null;
+ for (Object[] idAndRule : rulesb) { // Unbounded loop: We must find the rulesId.
+ if (rulesId.equals(idAndRule[0])) {
+ setb = (Object[][]) idAndRule[1];
+ break;
+ }
+ }
+
+ StringBuilder sb = new StringBuilder();
+ for (Object[] keywordAndRule : setb) {
+ if (sb.length() > 0) {
+ sb.append("; ");
+ }
+ sb.append((String) keywordAndRule[0]);
+ sb.append(": ");
+ sb.append((String) keywordAndRule[1]);
+ }
+ rules = PluralRules.parseDescription(sb.toString());
+ } catch (ParseException e) {
+ } catch (MissingResourceException e) {
+ }
+ synchronized (rulesIdToRules) {
+ if (rulesIdToRules.containsKey(rulesId)) {
+ rules = rulesIdToRules.get(rulesId);
+ } else {
+ rulesIdToRules.put(rulesId, rules); // can be null
+ }
+ }
+ }
+ return rules;
+ }
+
+ /**
+ * Returns the plural rules for the the locale. If we don't have data,
+ * com.ibm.icu.text.PluralRules.DEFAULT is returned.
+ */
+ public PluralRules forLocale(Locale locale, PluralRules.PluralType type) {
+ String rulesId = getRulesIdForLocale(locale, type);
+ if (rulesId == null || rulesId.trim().length() == 0) {
+ return PluralRules.DEFAULT;
+ }
+ PluralRules rules = getRulesForRulesId(rulesId);
+ if (rules == null) {
+ rules = PluralRules.DEFAULT;
+ }
+ return rules;
+ }
+
+ /**
+ * The only instance of the loader.
+ */
+ public static final PluralRulesLoader loader = new PluralRulesLoader();
+}
diff --git a/src/main/com/android/i18n/MessagePattern.java b/src/com/ibm/icu/text/MessagePattern.java
index bcb84b1..228a292 100644
--- a/src/main/com/android/i18n/MessagePattern.java
+++ b/src/com/ibm/icu/text/MessagePattern.java
@@ -1,6 +1,6 @@
/*
*******************************************************************************
-* Copyright (C) 2010-2013, International Business Machines
+* Copyright (C) 2010-2014, International Business Machines
* Corporation and others. All Rights Reserved.
*******************************************************************************
* created on: 2010aug21
@@ -15,6 +15,7 @@ import java.util.Locale;
import com.ibm.icu.impl.ICUConfig;
import com.ibm.icu.impl.PatternProps;
import com.ibm.icu.util.Freezable;
+import com.ibm.icu.util.ICUCloneNotSupportedException;
//Note: Minimize ICU dependencies, only use a very small part of the ICU core.
//In particular, do not depend on *Format classes.
@@ -308,7 +309,7 @@ public final class MessagePattern implements Cloneable, Freezable<MessagePattern
* @return true if getApostropheMode() == ApostropheMode.DOUBLE_REQUIRED
* @internal
*/
- /* package */ boolean jdkAposMode() {
+ public boolean jdkAposMode() {
return aposMode == ApostropheMode.DOUBLE_REQUIRED;
}
@@ -876,7 +877,7 @@ public final class MessagePattern implements Cloneable, Freezable<MessagePattern
try {
newMsg=(MessagePattern)super.clone();
} catch (CloneNotSupportedException e) {
- throw new RuntimeException(e);
+ throw new ICUCloneNotSupportedException(e);
}
newMsg.parts=(ArrayList<Part>)parts.clone();
if(numericValues!=null) {
diff --git a/src/com/ibm/icu/text/SelectFormat.java b/src/com/ibm/icu/text/SelectFormat.java
new file mode 100644
index 0000000..c062744
--- /dev/null
+++ b/src/com/ibm/icu/text/SelectFormat.java
@@ -0,0 +1,384 @@
+/*
+ *******************************************************************************
+ * Copyright (C) 2004-2011, International Business Machines Corporation and *
+ * others. All Rights Reserved. *
+ * Copyright (C) 2009 , Yahoo! Inc. *
+ *******************************************************************************
+ */
+package com.ibm.icu.text;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.text.FieldPosition;
+import java.text.Format;
+import java.text.ParsePosition;
+
+import com.ibm.icu.impl.PatternProps;
+
+/**
+ * <p><code>SelectFormat</code> supports the creation of internationalized
+ * messages by selecting phrases based on keywords. The pattern specifies
+ * how to map keywords to phrases and provides a default phrase. The
+ * object provided to the format method is a string that's matched
+ * against the keywords. If there is a match, the corresponding phrase
+ * is selected; otherwise, the default phrase is used.</p>
+ *
+ * <h4>Using <code>SelectFormat</code> for Gender Agreement</h4>
+ *
+ * <p>Note: Typically, select formatting is done via <code>MessageFormat</code>
+ * with a <code>select</code> argument type,
+ * rather than using a stand-alone <code>SelectFormat</code>.</p>
+ *
+ * <p>The main use case for the select format is gender based inflection.
+ * When names or nouns are inserted into sentences, their gender can affect pronouns,
+ * verb forms, articles, and adjectives. Special care needs to be
+ * taken for the case where the gender cannot be determined.
+ * The impact varies between languages:</p>
+ *
+ * <ul>
+ * <li>English has three genders, and unknown gender is handled as a special
+ * case. Names use the gender of the named person (if known), nouns referring
+ * to people use natural gender, and inanimate objects are usually neutral.
+ * The gender only affects pronouns: "he", "she", "it", "they".
+ *
+ * <li>German differs from English in that the gender of nouns is rather
+ * arbitrary, even for nouns referring to people ("M&#u00E4;dchen", girl, is neutral).
+ * The gender affects pronouns ("er", "sie", "es"), articles ("der", "die",
+ * "das"), and adjective forms ("guter Mann", "gute Frau", "gutes M&#u00E4;dchen").
+ *
+ * <li>French has only two genders; as in German the gender of nouns
+ * is rather arbitrary - for sun and moon, the genders
+ * are the opposite of those in German. The gender affects
+ * pronouns ("il", "elle"), articles ("le", "la"),
+ * adjective forms ("bon", "bonne"), and sometimes
+ * verb forms ("all&#u00E9;", "all&#u00E9e;").
+ *
+ * <li>Polish distinguishes five genders (or noun classes),
+ * human masculine, animate non-human masculine, inanimate masculine,
+ * feminine, and neuter.
+ * </ul>
+ *
+ * <p>Some other languages have noun classes that are not related to gender,
+ * but similar in grammatical use.
+ * Some African languages have around 20 noun classes.</p>
+ *
+ * <p><b>Note:</b>For the gender of a <i>person</i> in a given sentence,
+ * we usually need to distinguish only between female, male and other/unknown.</p>
+ *
+ * <p>To enable localizers to create sentence patterns that take their
+ * language's gender dependencies into consideration, software has to provide
+ * information about the gender associated with a noun or name to
+ * <code>MessageFormat</code>.
+ * Two main cases can be distinguished:</p>
+ *
+ * <ul>
+ * <li>For people, natural gender information should be maintained for each person.
+ * Keywords like "male", "female", "mixed" (for groups of people)
+ * and "unknown" could be used.
+ *
+ * <li>For nouns, grammatical gender information should be maintained for
+ * each noun and per language, e.g., in resource bundles.
+ * The keywords "masculine", "feminine", and "neuter" are commonly used,
+ * but some languages may require other keywords.
+ * </ul>
+ *
+ * <p>The resulting keyword is provided to <code>MessageFormat</code> as a
+ * parameter separate from the name or noun it's associated with. For example,
+ * to generate a message such as "Jean went to Paris", three separate arguments
+ * would be provided: The name of the person as argument 0, the gender of
+ * the person as argument 1, and the name of the city as argument 2.
+ * The sentence pattern for English, where the gender of the person has
+ * no impact on this simple sentence, would not refer to argument 1 at all:</p>
+ *
+ * <pre>{0} went to {2}.</pre>
+ *
+ * <p><b>Note:</b> The entire sentence should be included (and partially repeated)
+ * inside each phrase. Otherwise translators would have to be trained on how to
+ * move bits of the sentence in and out of the select argument of a message.
+ * (The examples below do not follow this recommendation!)</p>
+ *
+ * <p>The sentence pattern for French, where the gender of the person affects
+ * the form of the participle, uses a select format based on argument 1:</p>
+ *
+ * <pre>{0} est {1, select, female {all&#u00E9;e} other {all&#u00E9;}} &#u00E0; {2}.</pre>
+ *
+ * <p>Patterns can be nested, so that it's possible to handle interactions of
+ * number and gender where necessary. For example, if the above sentence should
+ * allow for the names of several people to be inserted, the following sentence
+ * pattern can be used (with argument 0 the list of people's names,
+ * argument 1 the number of people, argument 2 their combined gender, and
+ * argument 3 the city name):</p>
+ *
+ * <pre>{0} {1, plural,
+ * one {est {2, select, female {all&#u00E9;e} other {all&#u00E9;}}}
+ * other {sont {2, select, female {all&#u00E9;es} other {all&#u00E9;s}}}
+ * }&#u00E0; {3}.</pre>
+ *
+ * <h4>Patterns and Their Interpretation</h4>
+ *
+ * <p>The <code>SelectFormat</code> pattern string defines the phrase output
+ * for each user-defined keyword.
+ * The pattern is a sequence of (keyword, message) pairs.
+ * A keyword is a "pattern identifier": [^[[:Pattern_Syntax:][:Pattern_White_Space:]]]+</p>
+ *
+ * <p>Each message is a MessageFormat pattern string enclosed in {curly braces}.</p>
+ *
+ * <p>You always have to define a phrase for the default keyword
+ * <code>other</code>; this phrase is returned when the keyword
+ * provided to
+ * the <code>format</code> method matches no other keyword.
+ * If a pattern does not provide a phrase for <code>other</code>, the method
+ * it's provided to returns the error <code>U_DEFAULT_KEYWORD_MISSING</code>.
+ * <br/>
+ * Pattern_White_Space between keywords and messages is ignored.
+ * Pattern_White_Space within a message is preserved and output.</p>
+ *
+ * <p><pre>Example:
+ * MessageFormat msgFmt = new MessageFormat("{0} est " +
+ * "{1, select, female {all&#u00E9;e} other {all&#u00E9;}} &#u00E0; Paris.",
+ * new ULocale("fr"));
+ * Object args[] = {"Kirti","female"};
+ * System.out.println(msgFmt.format(args));
+ * </pre>
+ * <p>
+ * Produces the output:<br/>
+ * <code>Kirti est all&#u00E9;e &#u00E0; Paris.</code>
+ * </p>
+ *
+ * @stable ICU 4.4
+ */
+
+public class SelectFormat extends Format{
+ // Generated by serialver from JDK 1.5
+ private static final long serialVersionUID = 2993154333257524984L;
+
+ /*
+ * The applied pattern string.
+ */
+ private String pattern = null;
+
+ /**
+ * The MessagePattern which contains the parsed structure of the pattern string.
+ */
+ transient private MessagePattern msgPattern;
+
+ /**
+ * Creates a new <code>SelectFormat</code> for a given pattern string.
+ * @param pattern the pattern for this <code>SelectFormat</code>.
+ * @stable ICU 4.4
+ */
+ public SelectFormat(String pattern) {
+ applyPattern(pattern);
+ }
+
+ /*
+ * Resets the <code>SelectFormat</code> object.
+ */
+ private void reset() {
+ pattern = null;
+ if(msgPattern != null) {
+ msgPattern.clear();
+ }
+ }
+
+ /**
+ * Sets the pattern used by this select format.
+ * Patterns and their interpretation are specified in the class description.
+ *
+ * @param pattern the pattern for this select format.
+ * @throws IllegalArgumentException when the pattern is not a valid select format pattern.
+ * @stable ICU 4.4
+ */
+ public void applyPattern(String pattern) {
+ this.pattern = pattern;
+ if (msgPattern == null) {
+ msgPattern = new MessagePattern();
+ }
+ try {
+ msgPattern.parseSelectStyle(pattern);
+ } catch(RuntimeException e) {
+ reset();
+ throw e;
+ }
+ }
+
+ /**
+ * Returns the pattern for this <code>SelectFormat</code>
+ *
+ * @return the pattern string
+ * @stable ICU 4.4
+ */
+ public String toPattern() {
+ return pattern;
+ }
+
+ /**
+ * Finds the SelectFormat sub-message for the given keyword, or the "other" sub-message.
+ * @param pattern A MessagePattern.
+ * @param partIndex the index of the first SelectFormat argument style part.
+ * @param keyword a keyword to be matched to one of the SelectFormat argument's keywords.
+ * @return the sub-message start part index.
+ */
+ public static int findSubMessage(MessagePattern pattern, int partIndex, String keyword) {
+ int count=pattern.countParts();
+ int msgStart=0;
+ // Iterate over (ARG_SELECTOR, message) pairs until ARG_LIMIT or end of select-only pattern.
+ do {
+ MessagePattern.Part part=pattern.getPart(partIndex++);
+ MessagePattern.Part.Type type=part.getType();
+ if(type==MessagePattern.Part.Type.ARG_LIMIT) {
+ break;
+ }
+ assert type==MessagePattern.Part.Type.ARG_SELECTOR;
+ // part is an ARG_SELECTOR followed by a message
+ if(pattern.partSubstringMatches(part, keyword)) {
+ // keyword matches
+ return partIndex;
+ } else if(msgStart==0 && pattern.partSubstringMatches(part, "other")) {
+ msgStart=partIndex;
+ }
+ partIndex=pattern.getLimitPartIndex(partIndex);
+ } while(++partIndex<count);
+ return msgStart;
+ }
+
+ /**
+ * Selects the phrase for the given keyword.
+ *
+ * @param keyword a phrase selection keyword.
+ * @return the string containing the formatted select message.
+ * @throws IllegalArgumentException when the given keyword is not a "pattern identifier"
+ * @stable ICU 4.4
+ */
+ public final String format(String keyword) {
+ //Check for the validity of the keyword
+ if (!PatternProps.isIdentifier(keyword)) {
+ throw new IllegalArgumentException("Invalid formatting argument.");
+ }
+ // If no pattern was applied, throw an exception
+ if (msgPattern == null || msgPattern.countParts() == 0) {
+ throw new IllegalStateException("Invalid format error.");
+ }
+
+ // Get the appropriate sub-message.
+ int msgStart = findSubMessage(msgPattern, 0, keyword);
+ if (!msgPattern.jdkAposMode()) {
+ int msgLimit = msgPattern.getLimitPartIndex(msgStart);
+ return msgPattern.getPatternString().substring(msgPattern.getPart(msgStart).getLimit(),
+ msgPattern.getPatternIndex(msgLimit));
+ }
+ // JDK compatibility mode: Remove SKIP_SYNTAX.
+ StringBuilder result = null;
+ int prevIndex = msgPattern.getPart(msgStart).getLimit();
+ for (int i = msgStart;;) {
+ MessagePattern.Part part = msgPattern.getPart(++i);
+ MessagePattern.Part.Type type = part.getType();
+ int index = part.getIndex();
+ if (type == MessagePattern.Part.Type.MSG_LIMIT) {
+ if (result == null) {
+ return pattern.substring(prevIndex, index);
+ } else {
+ return result.append(pattern, prevIndex, index).toString();
+ }
+ } else if (type == MessagePattern.Part.Type.SKIP_SYNTAX) {
+ if (result == null) {
+ result = new StringBuilder();
+ }
+ result.append(pattern, prevIndex, index);
+ prevIndex = part.getLimit();
+ } else if (type == MessagePattern.Part.Type.ARG_START) {
+ if (result == null) {
+ result = new StringBuilder();
+ }
+ result.append(pattern, prevIndex, index);
+ prevIndex = index;
+ i = msgPattern.getLimitPartIndex(i);
+ index = msgPattern.getPart(i).getLimit();
+ MessagePattern.appendReducedApostrophes(pattern, prevIndex, index, result);
+ prevIndex = index;
+ }
+ }
+ }
+
+ /**
+ * Selects the phrase for the given keyword.
+ * and appends the formatted message to the given <code>StringBuffer</code>.
+ * @param keyword a phrase selection keyword.
+ * @param toAppendTo the selected phrase will be appended to this
+ * <code>StringBuffer</code>.
+ * @param pos will be ignored by this method.
+ * @throws IllegalArgumentException when the given keyword is not a String
+ * or not a "pattern identifier"
+ * @return the string buffer passed in as toAppendTo, with formatted text
+ * appended.
+ * @stable ICU 4.4
+ */
+ public StringBuffer format(Object keyword, StringBuffer toAppendTo,
+ FieldPosition pos) {
+ if (keyword instanceof String) {
+ toAppendTo.append(format( (String)keyword));
+ }else{
+ throw new IllegalArgumentException("'" + keyword + "' is not a String");
+ }
+ return toAppendTo;
+ }
+
+ /**
+ * This method is not supported by <code>SelectFormat</code>.
+ * @param source the string to be parsed.
+ * @param pos defines the position where parsing is to begin,
+ * and upon return, the position where parsing left off. If the position
+ * has not changed upon return, then parsing failed.
+ * @return nothing because this method is not supported.
+ * @throws UnsupportedOperationException thrown always.
+ * @stable ICU 4.4
+ */
+ public Object parseObject(String source, ParsePosition pos) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ * @stable ICU 4.4
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if(this == obj) {
+ return true;
+ }
+ if(obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ SelectFormat sf = (SelectFormat) obj;
+ return msgPattern == null ? sf.msgPattern == null : msgPattern.equals(sf.msgPattern);
+ }
+
+ /**
+ * {@inheritDoc}
+ * @stable ICU 4.4
+ */
+ @Override
+ public int hashCode() {
+ if (pattern != null) {
+ return pattern.hashCode();
+ }
+ return 0;
+ }
+
+ /**
+ * {@inheritDoc}
+ * @stable ICU 4.4
+ */
+ @Override
+ public String toString() {
+ return "pattern='" + pattern + "'";
+ }
+
+ private void readObject(ObjectInputStream in)
+ throws IOException, ClassNotFoundException {
+ in.defaultReadObject();
+ if (pattern != null) {
+ applyPattern(pattern);
+ }
+ }
+}
diff --git a/src/com/ibm/icu/util/Freezable.java b/src/com/ibm/icu/util/Freezable.java
new file mode 100644
index 0000000..4cf37dd
--- /dev/null
+++ b/src/com/ibm/icu/util/Freezable.java
@@ -0,0 +1,320 @@
+/*
+ ******************************************************************************
+ * Copyright (C) 2005-2011, International Business Machines Corporation and *
+ * others. All Rights Reserved. *
+ ******************************************************************************
+*/
+package com.ibm.icu.util;
+
+/**
+ * Provides a flexible mechanism for controlling access, without requiring that
+ * a class be immutable. Once frozen, an object can never be unfrozen, so it is
+ * thread-safe from that point onward. Once the object has been frozen,
+ * it must guarantee that no changes can be made to it. Any attempt to alter
+ * it must raise an UnsupportedOperationException exception. This means that when
+ * the object returns internal objects, or if anyone has references to those internal
+ * objects, that those internal objects must either be immutable, or must also
+ * raise exceptions if any attempt to modify them is made. Of course, the object
+ * can return clones of internal objects, since those are safe.
+ * <h2>Background</h2>
+ * <p>
+ * There are often times when you need objects to be objects 'safe', so that
+ * they can't be modified. Examples are when objects need to be thread-safe, or
+ * in writing robust code, or in caches. If you are only creating your own
+ * objects, you can guarantee this, of course -- but only if you don't make a
+ * mistake. If you have objects handed into you, or are creating objects using
+ * others handed into you, it is a different story. It all comes down to whether
+ * you want to take the Blanche Dubois approach (&quot;depend on the kindness of
+ * strangers&quot;) or the Andy Grove approach (&quot;Only the Paranoid
+ * Survive&quot;).
+ * </p>
+ * <p>
+ * For example, suppose we have a simple class:
+ * </p>
+ *
+ * <pre>
+ * public class A {
+ * protected Collection b;
+ *
+ * protected Collection c;
+ *
+ * public Collection get_b() {
+ * return b;
+ * }
+ *
+ * public Collection get_c() {
+ * return c;
+ * }
+ *
+ * public A(Collection new_b, Collection new_c) {
+ * b = new_b;
+ * c = new_c;
+ * }
+ * }
+ * </pre>
+ *
+ * <p>
+ * Since the class doesn't have any setters, someone might think that it is
+ * immutable. You know where this is leading, of course; this class is unsafe in
+ * a number of ways. The following illustrates that.
+ * </p>
+ *
+ * <pre>
+ * public test1(SupposedlyImmutableClass x, SafeStorage y) {
+ * // unsafe getter
+ * A a = x.getA();
+ * Collection col = a.get_b();
+ * col.add(something); // a has now been changed, and x too
+ *
+ * // unsafe constructor
+ * a = new A(col, col);
+ * y.store(a);
+ * col.add(something); // a has now been changed, and y too
+ * }
+ * </pre>
+ *
+ * <p>
+ * There are a few different techniques for having safe classes.
+ * </p>
+ * <ol>
+ * <li>Const objects. In C++, you can declare parameters const.</li>
+ * <li>Immutable wrappers. For example, you can put a collection in an
+ * immutable wrapper.</li>
+ * <li>Always-Immutable objects. Java uses this approach, with a few
+ * variations. Examples:
+ * <ol>
+ * <li>Simple. Once a Color is created (eg from R, G, and B integers) it is
+ * immutable.</li>
+ * <li>Builder Class. There is a separate 'builder' class. For example,
+ * modifiable Strings are created using StringBuffer (which doesn't have the
+ * full String API available). Once you want an immutable form, you create one
+ * with toString().</li>
+ * <li>Primitives. These are always safe, since they are copied on input/output
+ * from methods.</li>
+ * </ol>
+ * </li>
+ * <li>Cloning. Where you need an object to be safe, you clone it.</li>
+ * </ol>
+ * <p>
+ * There are advantages and disadvantages of each of these.
+ * </p>
+ * <ol>
+ * <li>Const provides a certain level of protection, but since const can be and
+ * is often cast away, it only protects against most inadvertent mistakes. It
+ * also offers no threading protection, since anyone who has a pointer to the
+ * (unconst) object in another thread can mess you up.</li>
+ * <li>Immutable wrappers are safer than const in that the constness can't be
+ * cast away. But other than that they have all the same problems: not safe if
+ * someone else keeps hold of the original object, or if any of the objects
+ * returned by the class are mutable.</li>
+ * <li>Always-Immutable Objects are safe, but usage can require excessive
+ * object creation.</li>
+ * <li>Cloning is only safe if the object truly has a 'safe' clone; defined as
+ * one that <i>ensures that no change to the clone affects the original</i>.
+ * Unfortunately, many objects don't have a 'safe' clone, and always cloning can
+ * require excessive object creation.</li>
+ * </ol>
+ * <h2>Freezable Model</h2>
+ * <p>
+ * The <code>Freezable</code> model supplements these choices by giving you
+ * the ability to build up an object by calling various methods, then when it is
+ * in a final state, you can <i>make</i> it immutable. Once immutable, an
+ * object cannot <i>ever </i>be modified, and is completely thread-safe: that
+ * is, multiple threads can have references to it without any synchronization.
+ * If someone needs a mutable version of an object, they can use
+ * <code>cloneAsThawed()</code>, and modify the copy. This provides a simple,
+ * effective mechanism for safe classes in circumstances where the alternatives
+ * are insufficient or clumsy. (If an object is shared before it is immutable,
+ * then it is the responsibility of each thread to mutex its usage (as with
+ * other objects).)
+ * </p>
+ * <p>
+ * Here is what needs to be done to implement this interface, depending on the
+ * type of the object.
+ * </p>
+ * <h3><b>Immutable Objects</b></h3>
+ * <p>
+ * These are the easiest. You just use the interface to reflect that, by adding
+ * the following:
+ * </p>
+ *
+ * <pre>
+ * public class A implements Freezable<A> {
+ * ...
+ * public final boolean isFrozen() {return true;}
+ * public final A freeze() {return this;}
+ * public final A cloneAsThawed() { return this; }
+ * }
+ * </pre>
+ *
+ * <p>
+ * These can be final methods because subclasses of immutable objects must
+ * themselves be immutable. (Note: <code>freeze</code> is returning
+ * <code>this</code> for chaining.)
+ * </p>
+ * <h3><b>Mutable Objects</b></h3>
+ * <p>
+ * Add a protected 'flagging' field:
+ * </p>
+ *
+ * <pre>
+ * protected boolean immutable;
+ * </pre>
+ *
+ * <p>
+ * Add the following methods:
+ * </p>
+ *
+ * <pre>
+ * public final boolean isFrozen() {
+ * return frozen;
+ * };
+ *
+ * public A freeze() {
+ * frozen = true;
+ * return this;
+ * }
+ * </pre>
+ *
+ * <p>
+ * Add a <code>cloneAsThawed()</code> method following the normal pattern for
+ * <code>clone()</code>, except that <code>frozen=false</code> in the new
+ * clone.
+ * </p>
+ * <p>
+ * Then take the setters (that is, any method that can change the internal state
+ * of the object), and add the following as the first statement:
+ * </p>
+ *
+ * <pre>
+ * if (isFrozen()) {
+ * throw new UnsupportedOperationException(&quot;Attempt to modify frozen object&quot;);
+ * }
+ * </pre>
+ *
+ * <h4><b>Subclassing</b></h4>
+ * <p>
+ * Any subclass of a <code>Freezable</code> will just use its superclass's
+ * flagging field. It must override <code>freeze()</code> and
+ * <code>cloneAsThawed()</code> to call the superclass, but normally does not
+ * override <code>isFrozen()</code>. It must then just pay attention to its
+ * own getters, setters and fields.
+ * </p>
+ * <h4><b>Internal Caches</b></h4>
+ * <p>
+ * Internal caches are cases where the object is logically unmodified, but
+ * internal state of the object changes. For example, there are const C++
+ * functions that cast away the const on the &quot;this&quot; pointer in order
+ * to modify an object cache. These cases are handled by mutexing the internal
+ * cache to ensure thread-safety. For example, suppose that UnicodeSet had an
+ * internal marker to the last code point accessed. In this case, the field is
+ * not externally visible, so the only thing you need to do is to synchronize
+ * the field for thread safety.
+ * </p>
+ * <h4>Unsafe Internal Access</h4>
+ * <p>
+ * Internal fields are called <i>safe</i> if they are either
+ * <code>frozen</code> or immutable (such as String or primitives). If you've
+ * never allowed internal access to these, then you are all done. For example,
+ * converting UnicodeSet to be <code>Freezable</code> is just accomplished
+ * with the above steps. But remember that you <i><b>have</b></i> allowed
+ * access to unsafe internals if you have any code like the following, in a
+ * getter, setter, or constructor:
+ * </p>
+ *
+ * <pre>
+ * Collection getStuff() {
+ * return stuff;
+ * } // caller could keep reference &amp; modify
+ *
+ * void setStuff(Collection x) {
+ * stuff = x;
+ * } // caller could keep reference &amp; modify
+ *
+ * MyClass(Collection x) {
+ * stuff = x;
+ * } // caller could keep reference &amp; modify
+ * </pre>
+ *
+ * <p>
+ * These also illustrated in the code sample in <b>Background</b> above.
+ * </p>
+ * <p>
+ * To deal with unsafe internals, the simplest course of action is to do the
+ * work in the <code>freeze()</code> function. Just make all of your internal
+ * fields frozen, and set the frozen flag. Any subsequent getter/setter will
+ * work properly. Here is an example:
+ * </p>
+ *
+ * <pre>
+ * public A freeze() {
+ * if (!frozen) {
+ * foo.freeze();
+ * frozen = true;
+ * }
+ * return this;
+ * }
+ * </pre>
+ *
+ * <p>
+ * If the field is a <code>Collection</code> or <code>Map</code>, then to
+ * make it frozen you have two choices. If you have never allowed access to the
+ * collection from outside your object, then just wrap it to prevent future
+ * modification.
+ * </p>
+ *
+ * <pre>
+ * zone_to_country = Collections.unmodifiableMap(zone_to_country);
+ * </pre>
+ *
+ * <p>
+ * If you have <i>ever</i> allowed access, then do a <code>clone()</code>
+ * before wrapping it.
+ * </p>
+ *
+ * <pre>
+ * zone_to_country = Collections.unmodifiableMap(zone_to_country.clone());
+ * </pre>
+ *
+ * <p>
+ * If a collection <i>(or any other container of objects)</i> itself can
+ * contain mutable objects, then for a safe clone you need to recurse through it
+ * to make the entire collection immutable. The recursing code should pick the
+ * most specific collection available, to avoid the necessity of later
+ * downcasing.
+ * </p>
+ * <blockquote>
+ * <p>
+ * <b>Note: </b>An annoying flaw in Java is that the generic collections, like
+ * <code>Map</code> or <code>Set</code>, don't have a <code>clone()</code>
+ * operation. When you don't know the type of the collection, the simplest
+ * course is to just create a new collection:
+ * </p>
+ *
+ * <pre>
+ * zone_to_country = Collections.unmodifiableMap(new HashMap(zone_to_country));
+ * </pre>
+ *
+ * </blockquote>
+ * @stable ICU 3.8
+ */
+public interface Freezable<T> extends Cloneable {
+ /**
+ * Determines whether the object has been frozen or not.
+ * @stable ICU 3.8
+ */
+ public boolean isFrozen();
+
+ /**
+ * Freezes the object.
+ * @return the object itself.
+ * @stable ICU 3.8
+ */
+ public T freeze();
+
+ /**
+ * Provides for the clone operation. Any clone is initially unfrozen.
+ * @stable ICU 3.8
+ */
+ public T cloneAsThawed();
+}
diff --git a/src/com/ibm/icu/util/ICUCloneNotSupportedException.java b/src/com/ibm/icu/util/ICUCloneNotSupportedException.java
new file mode 100644
index 0000000..7be1b91
--- /dev/null
+++ b/src/com/ibm/icu/util/ICUCloneNotSupportedException.java
@@ -0,0 +1,62 @@
+/*
+ *******************************************************************************
+ * Copyright (C) 2014, International Business Machines Corporation and
+ * others. All Rights Reserved.
+ *******************************************************************************
+ */
+package com.ibm.icu.util;
+
+/**
+ * Unchecked version of {@link CloneNotSupportedException}.
+ * Some ICU APIs do not throw the standard exception but instead wrap it
+ * into this unchecked version.
+ *
+ * @draft ICU 53
+ * @provisional This API might change or be removed in a future release.
+ */
+public class ICUCloneNotSupportedException extends ICUException {
+ private static final long serialVersionUID = -4824446458488194964L;
+
+ /**
+ * Default constructor.
+ *
+ * @draft ICU 53
+ * @provisional This API might change or be removed in a future release.
+ */
+ public ICUCloneNotSupportedException() {
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param message exception message string
+ * @draft ICU 53
+ * @provisional This API might change or be removed in a future release.
+ */
+ public ICUCloneNotSupportedException(String message) {
+ super(message);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param cause original exception (normally a {@link CloneNotSupportedException})
+ * @draft ICU 53
+ * @provisional This API might change or be removed in a future release.
+ */
+ public ICUCloneNotSupportedException(Throwable cause) {
+ super(cause);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param message exception message string
+ * @param cause original exception (normally a {@link CloneNotSupportedException})
+ * @draft ICU 53
+ * @provisional This API might change or be removed in a future release.
+ */
+ public ICUCloneNotSupportedException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/src/com/ibm/icu/util/ICUException.java b/src/com/ibm/icu/util/ICUException.java
new file mode 100644
index 0000000..e37a97b
--- /dev/null
+++ b/src/com/ibm/icu/util/ICUException.java
@@ -0,0 +1,60 @@
+/*
+ *******************************************************************************
+ * Copyright (C) 2014, International Business Machines Corporation and
+ * others. All Rights Reserved.
+ *******************************************************************************
+ */
+package com.ibm.icu.util;
+
+/**
+ * Base class for unchecked, ICU-specific exceptions.
+ *
+ * @draft ICU 53
+ * @provisional This API might change or be removed in a future release.
+ */
+public class ICUException extends RuntimeException {
+ private static final long serialVersionUID = -3067399656455755650L;
+
+ /**
+ * Default constructor.
+ *
+ * @draft ICU 53
+ * @provisional This API might change or be removed in a future release.
+ */
+ public ICUException() {
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param message exception message string
+ * @draft ICU 53
+ * @provisional This API might change or be removed in a future release.
+ */
+ public ICUException(String message) {
+ super(message);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param cause original exception
+ * @draft ICU 53
+ * @provisional This API might change or be removed in a future release.
+ */
+ public ICUException(Throwable cause) {
+ super(cause);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param message exception message string
+ * @param cause original exception
+ * @draft ICU 53
+ * @provisional This API might change or be removed in a future release.
+ */
+ public ICUException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/src/com/ibm/icu/util/ICUUncheckedIOException.java b/src/com/ibm/icu/util/ICUUncheckedIOException.java
new file mode 100644
index 0000000..fd2a162
--- /dev/null
+++ b/src/com/ibm/icu/util/ICUUncheckedIOException.java
@@ -0,0 +1,66 @@
+/*
+ *******************************************************************************
+ * Copyright (C) 2014, International Business Machines Corporation and
+ * others. All Rights Reserved.
+ *******************************************************************************
+ */
+package com.ibm.icu.util;
+
+/**
+ * Unchecked version of {@link java.io.IOException}.
+ * Some ICU APIs do not throw the standard exception but instead wrap it
+ * into this unchecked version.
+ *
+ * <p>This currently extends {@link RuntimeException},
+ * but when ICU can rely on Java 8 this class should be changed to extend
+ * java.io.UncheckedIOException instead.
+ *
+ * @draft ICU 53
+ * @provisional This API might change or be removed in a future release.
+ */
+public class ICUUncheckedIOException extends RuntimeException {
+ private static final long serialVersionUID = 1210263498513384449L;
+
+ /**
+ * Default constructor.
+ *
+ * @draft ICU 53
+ * @provisional This API might change or be removed in a future release.
+ */
+ public ICUUncheckedIOException() {
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param message exception message string
+ * @draft ICU 53
+ * @provisional This API might change or be removed in a future release.
+ */
+ public ICUUncheckedIOException(String message) {
+ super(message);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param cause original exception (normally a {@link java.io.IOException})
+ * @draft ICU 53
+ * @provisional This API might change or be removed in a future release.
+ */
+ public ICUUncheckedIOException(Throwable cause) {
+ super(cause);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param message exception message string
+ * @param cause original exception (normally a {@link java.io.IOException})
+ * @draft ICU 53
+ * @provisional This API might change or be removed in a future release.
+ */
+ public ICUUncheckedIOException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/src/com/ibm/icu/util/Output.java b/src/com/ibm/icu/util/Output.java
new file mode 100644
index 0000000..2f40475
--- /dev/null
+++ b/src/com/ibm/icu/util/Output.java
@@ -0,0 +1,45 @@
+/*
+ *******************************************************************************
+ * Copyright (C) 2011-2012, International Business Machines Corporation and *
+ * others. All Rights Reserved. *
+ *******************************************************************************
+ */
+package com.ibm.icu.util;
+
+/**
+ * Simple struct-like class for output parameters.
+ * @param <T> The type of the parameter.
+ * @stable ICU 4.8
+ */
+public class Output<T> {
+ /**
+ * The value field
+ * @stable ICU 4.8
+ */
+ public T value;
+
+ /**
+ * {@inheritDoc}
+ * @stable ICU 4.8
+ */
+ public String toString() {
+ return value == null ? "null" : value.toString();
+ }
+
+ /**
+ * Constructs an empty <code>Output</code>
+ * @stable ICU 4.8
+ */
+ public Output() {
+
+ }
+
+ /**
+ * Constructs an <code>Output</code> withe the given value.
+ * @param value the initial value
+ * @stable ICU 4.8
+ */
+ public Output(T value) {
+ this.value = value;
+ }
+}
diff --git a/tests/src/com/android/messageformat/SimpleMessageFormatTest.java b/tests/src/com/android/messageformat/SimpleMessageFormatTest.java
new file mode 100644
index 0000000..e132b14
--- /dev/null
+++ b/tests/src/com/android/messageformat/SimpleMessageFormatTest.java
@@ -0,0 +1,82 @@
+/*
+ *******************************************************************************
+ * Copyright (C) 2014, International Business Machines Corporation and
+ * others. All Rights Reserved.
+ *******************************************************************************
+ */
+package com.android.messageformat;
+
+import java.util.Date;
+import java.util.Locale;
+import junit.framework.TestCase;
+
+public class SimpleMessageFormatTest extends TestCase {
+ public void testBasic() {
+ assertEquals("one simple argument", "Going to Germany and back",
+ MessageFormat.formatNamedArgs(
+ Locale.US, "Going to {place} and back", "place", "Germany"));
+ }
+
+ public void testSelect() {
+ String msg = "{gender,select,female{her book}male{his book}other{their book}}";
+ assertEquals("select female", "her book",
+ MessageFormat.formatNamedArgs(Locale.US, msg, "gender", "female"));
+ assertEquals("select male", "his book",
+ MessageFormat.formatNamedArgs(Locale.US, msg, "gender", "male"));
+ assertEquals("select neutral", "their book",
+ MessageFormat.formatNamedArgs(Locale.US, msg, "gender", "unknown"));
+ }
+
+ public void testPlural() {
+ // Using Serbian, see
+ // http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html
+ Locale sr = new Locale("sr");
+ String msg =
+ "{num,plural,offset:1 =1{only {name}}=2{{name} and one other}" +
+ "one{{name} and #-one others}few{{name} and #-few others}" +
+ "other{{name} and #... others}}";
+ assertEquals("plural 1", "only Peter",
+ MessageFormat.formatNamedArgs(sr, msg, "num", 1, "name", "Peter"));
+ assertEquals("plural 2", "Paul and one other",
+ MessageFormat.formatNamedArgs(sr, msg, "num", 2, "name", "Paul"));
+ assertEquals("plural 22", "Mary and 21-one others",
+ MessageFormat.formatNamedArgs(sr, msg, "num", 22, "name", "Mary"));
+ assertEquals("plural 33", "John and 32-few others",
+ MessageFormat.formatNamedArgs(sr, msg, "num", 33, "name", "John"));
+ assertEquals("plural 6", "Yoko and 5... others",
+ MessageFormat.formatNamedArgs(sr, msg, "num", 6, "name", "Yoko"));
+ }
+
+ public void testSelectAndPlural() {
+ Locale ja = Locale.JAPANESE; // always "other"
+ String msg =
+ "{gender,select,female{" +
+ "{num,plural,=1{her book}other{her # books}}" +
+ "}male{" +
+ "{num,plural,=1{his book}other{his # books}}" +
+ "}other{" +
+ "{num,plural,=1{their book}other{their # books}}" +
+ "}}";
+ assertEquals("female 1", "her book",
+ MessageFormat.formatNamedArgs(ja, msg, "gender", "female", "num", 1));
+ assertEquals("male 2", "his 2 books",
+ MessageFormat.formatNamedArgs(ja, msg, "gender", "male", "num", 2));
+ assertEquals("unknown 3000", "their 3,000 books",
+ MessageFormat.formatNamedArgs(ja, msg, "gender", "?", "num", 3000));
+ }
+
+ public void testSelectOrdinal() {
+ Locale en = Locale.ENGLISH;
+ String msg =
+ "{num,selectordinal,one{#st floor}two{#nd floor}few{#rd floor}other{#th floor}}";
+ assertEquals("91", "91st floor",
+ MessageFormat.formatNamedArgs(en, msg, "num", 91));
+ assertEquals("22", "22nd floor",
+ MessageFormat.formatNamedArgs(en, msg, "num", 22));
+ assertEquals("33", "33rd floor",
+ MessageFormat.formatNamedArgs(en, msg, "num", 33));
+ assertEquals("11", "11th floor",
+ MessageFormat.formatNamedArgs(en, msg, "num", 11));
+ }
+}
+