diff options
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) --> <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 ("depend on the kindness of + * strangers") or the Andy Grove approach ("Only the Paranoid + * Survive"). + * </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("Attempt to modify frozen object"); + * } + * </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 "this" 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 & modify + * + * void setStuff(Collection x) { + * stuff = x; + * } // caller could keep reference & modify + * + * MyClass(Collection x) { + * stuff = x; + * } // caller could keep reference & 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)); + } +} + |