[automerger skipped] DO NOT MERGE Track tzdb 2023d update. [SC_V2 CTS] am: 9c14af3c90 -s ours am: 25d9d020f2 -s ours am: 684fd74c35 -s ours am: d43668518d -s ours am: ff127c78f4 -s ours
am skip reason: contains skip directive
Original change: https://android-review.googlesource.com/c/platform/external/icu/+/2985516
Change-Id: Ic1b8e1e5c33fcd8f14b948cc904bdc2093dd0cda
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/Android.bp b/Android.bp
index d7fb36b..f1c850e 100644
--- a/Android.bp
+++ b/Android.bp
@@ -53,7 +53,6 @@
visibility: [
"//external/icu:__subpackages__",
],
- path: "NOTICE",
srcs: ["NOTICE"],
}
@@ -68,10 +67,6 @@
"-Wno-unneeded-internal-declaration",
"-Wno-deprecated-declarations",
],
- // Upstream requires C++11 as the minimum version.
- cppflags: [
- "-std=c++11",
- ],
target: {
android: {
cflags: [
diff --git a/METADATA b/METADATA
index 5c3f89c..25aa1d6 100644
--- a/METADATA
+++ b/METADATA
@@ -1,3 +1,17 @@
+name: "ICU"
+description:
+ "International Components for Unicode"
+
third_party {
+ url {
+ type: HOMEPAGE
+ value: "https://icu.unicode.org/"
+ }
+ url {
+ type: GIT
+ value: "https://github.com/unicode-org/icu"
+ }
+ version: "72"
+ last_upgrade_date { year: 2022 month: 11 day: 1 }
license_type: RECIPROCAL
}
diff --git a/OWNERS b/OWNERS
index 18026a4..5737ec8 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,9 +1,17 @@
-icu-team+reviews@google.com
-mscherer@google.com
-roubert@google.com
-
-android-libcore-team+review@google.com
+# Default reviewers
vichang@google.com
-nfuller@google.com
-nikitai@google.com
-ngeoffray@google.com
+mingaleev@google.com
+mscherer@google.com
+oth@google.com
+
+# Bug component: 24949
+# libcore team is the larger team owning the library.
+include platform/libcore:/OWNERS
+
+libcore-bugs-triage@google.com
+# g2.corp.android-icu-maintainers@google.com
+# android-libcore-team+review@google.com
+# icu-team+reviews@google.com
+
+# Others in the ICU team
+roubert@google.com
diff --git a/README.android b/README.android
index 5b15c78..c243801 100644
--- a/README.android
+++ b/README.android
@@ -30,3 +30,5 @@
libandroidicu and libjavacore to initialize ICU4C.
tools/ - Code / data maintenance tools. See tools/README.android.
+
+libicu/ - NDK headers and the shim implementation in the libicu.so.
diff --git a/README.version b/README.version
index 70eadc7..3939e1b 100644
--- a/README.version
+++ b/README.version
@@ -1,3 +1,3 @@
URL: https://github.com/unicode-org/icu
-Version: 70.1
+Version: 72.1
BugComponent: 23970
diff --git a/android_icu4j/Android.bp b/android_icu4j/Android.bp
index d912c19..e0e2851 100644
--- a/android_icu4j/Android.bp
+++ b/android_icu4j/Android.bp
@@ -77,7 +77,7 @@
name: "timezone-host",
visibility: [
"//packages/modules/RuntimeI18n/apex",
- "//system/timezone/distro/core",
+ "//system/timezone/input_tools/version",
],
srcs: [
"libcore_bridge/src/java/com/android/i18n/timezone/TzDataSetVersion.java",
@@ -111,8 +111,14 @@
javacflags: [
"-Xep:MissingOverride:OFF", // Ignore missing @Override.
"-Xep:ConstantOverflow:WARN", // Known constant overflow in SplittableRandom
+ "-Xep:BoxedPrimitiveEquality:WARN",
+ "-Xep:ComparableType:WARN",
+ "-Xep:EmptyTopLevelDeclaration:WARN",
],
},
+ lint: {
+ warning_checks: ["SuspiciousIndentation"],
+ },
}
// A separated core library that contains ICU4J because ICU4J will be in a different APEX module,
@@ -192,6 +198,9 @@
"-Xep:MissingOverride:OFF",
],
},
+ lint: {
+ warning_checks: ["SuspiciousIndentation"],
+ },
public: {
enabled: true,
@@ -255,6 +264,10 @@
// Don't copy any output files to the dist.
no_dist: true,
+
+ lint: {
+ warning_checks: ["SuspiciousIndentation"],
+ },
}
// Referenced implicitly from i18n.module.intra.core.api.
@@ -367,6 +380,10 @@
// Don't copy any output files to the dist.
no_dist: true,
+
+ lint: {
+ warning_checks: ["SuspiciousIndentation"],
+ },
}
java_sdk_library {
@@ -397,6 +414,10 @@
// Don't copy any output files to the dist.
no_dist: true,
+
+ lint: {
+ warning_checks: ["SuspiciousIndentation"],
+ },
}
//==========================================================
@@ -434,6 +455,42 @@
system_modules: "art-module-intra-core-api-stubs-system-modules",
errorprone: {
- javacflags: ["-Xep:EqualsNull:WARN"],
+ javacflags: [
+ "-Xep:EqualsNull:WARN",
+ "-Xep:ArrayToString:WARN",
+ "-Xep:SelfEquals:WARN",
+ "-Xep:SelfComparison:WARN",
+ "-Xep:ReturnValueIgnored:WARN",
+ "-Xep:IdentityBinaryExpression:WARN",
+ "-Xep:BoxedPrimitiveEquality:WARN",
+ "-Xep:ComparableType:WARN",
+ ],
},
}
+
+java_api_contribution {
+ name: "i18n-intra-core-stubs",
+ api_surface: "intra-core",
+ api_file: "api/intra/current.txt",
+ visibility: [
+ "//build/orchestrator/apis",
+ ],
+}
+
+java_api_contribution {
+ name: "i18n-public-stubs",
+ api_surface: "public",
+ api_file: "api/public/current.txt",
+ visibility: [
+ "//build/orchestrator/apis",
+ ],
+}
+
+java_api_contribution {
+ name: "i18n-module-lib-stubs",
+ api_surface: "module-lib",
+ api_file: "api/stable_platform/current.txt",
+ visibility: [
+ "//build/orchestrator/apis",
+ ],
+}
diff --git a/android_icu4j/api/legacy_platform/current.txt b/android_icu4j/api/legacy_platform/current.txt
index 337a712..ca3fafb 100644
--- a/android_icu4j/api/legacy_platform/current.txt
+++ b/android_icu4j/api/legacy_platform/current.txt
@@ -88,8 +88,6 @@
}
public final class TimeZoneDataFiles {
- method public static String getDataTimeZoneFile(String);
- method public static String getDataTimeZoneRootDir();
method public static String getTimeZoneModuleTzVersionFile();
method public static com.android.i18n.timezone.TzDataSetVersion readTimeZoneModuleVersion() throws java.io.IOException, com.android.i18n.timezone.TzDataSetVersion.TzDataSetException;
}
diff --git a/android_icu4j/api/public/current.txt b/android_icu4j/api/public/current.txt
index 93473d7..fd2e6fe 100644
--- a/android_icu4j/api/public/current.txt
+++ b/android_icu4j/api/public/current.txt
@@ -50,6 +50,7 @@
method public static double getUnicodeNumericValue(int);
method public static android.icu.util.VersionInfo getUnicodeVersion();
method public static boolean hasBinaryProperty(int, int);
+ method public static boolean hasBinaryProperty(CharSequence, int);
method public static boolean isBMP(int);
method public static boolean isBaseForm(int);
method public static boolean isDefined(int);
@@ -468,6 +469,8 @@
field public static final int ARABIC_EXTENDED_A_ID = 210; // 0xd2
field public static final android.icu.lang.UCharacter.UnicodeBlock ARABIC_EXTENDED_B;
field public static final int ARABIC_EXTENDED_B_ID = 309; // 0x135
+ field public static final android.icu.lang.UCharacter.UnicodeBlock ARABIC_EXTENDED_C;
+ field public static final int ARABIC_EXTENDED_C_ID = 321; // 0x141
field public static final int ARABIC_ID = 12; // 0xc
field public static final android.icu.lang.UCharacter.UnicodeBlock ARABIC_MATHEMATICAL_ALPHABETIC_SYMBOLS;
field public static final int ARABIC_MATHEMATICAL_ALPHABETIC_SYMBOLS_ID = 211; // 0xd3
@@ -562,6 +565,8 @@
field public static final int CJK_UNIFIED_IDEOGRAPHS_EXTENSION_F_ID = 274; // 0x112
field public static final android.icu.lang.UCharacter.UnicodeBlock CJK_UNIFIED_IDEOGRAPHS_EXTENSION_G;
field public static final int CJK_UNIFIED_IDEOGRAPHS_EXTENSION_G_ID = 302; // 0x12e
+ field public static final android.icu.lang.UCharacter.UnicodeBlock CJK_UNIFIED_IDEOGRAPHS_EXTENSION_H;
+ field public static final int CJK_UNIFIED_IDEOGRAPHS_EXTENSION_H_ID = 322; // 0x142
field public static final int CJK_UNIFIED_IDEOGRAPHS_ID = 71; // 0x47
field public static final android.icu.lang.UCharacter.UnicodeBlock COMBINING_DIACRITICAL_MARKS;
field public static final android.icu.lang.UCharacter.UnicodeBlock COMBINING_DIACRITICAL_MARKS_EXTENDED;
@@ -600,6 +605,8 @@
field public static final int CYRILLIC_EXTENDED_B_ID = 160; // 0xa0
field public static final android.icu.lang.UCharacter.UnicodeBlock CYRILLIC_EXTENDED_C;
field public static final int CYRILLIC_EXTENDED_C_ID = 265; // 0x109
+ field public static final android.icu.lang.UCharacter.UnicodeBlock CYRILLIC_EXTENDED_D;
+ field public static final int CYRILLIC_EXTENDED_D_ID = 323; // 0x143
field public static final int CYRILLIC_ID = 9; // 0x9
field public static final android.icu.lang.UCharacter.UnicodeBlock CYRILLIC_SUPPLEMENT;
field public static final android.icu.lang.UCharacter.UnicodeBlock CYRILLIC_SUPPLEMENTARY;
@@ -609,6 +616,8 @@
field public static final int DESERET_ID = 90; // 0x5a
field public static final android.icu.lang.UCharacter.UnicodeBlock DEVANAGARI;
field public static final android.icu.lang.UCharacter.UnicodeBlock DEVANAGARI_EXTENDED;
+ field public static final android.icu.lang.UCharacter.UnicodeBlock DEVANAGARI_EXTENDED_A;
+ field public static final int DEVANAGARI_EXTENDED_A_ID = 324; // 0x144
field public static final int DEVANAGARI_EXTENDED_ID = 179; // 0xb3
field public static final int DEVANAGARI_ID = 15; // 0xf
field public static final android.icu.lang.UCharacter.UnicodeBlock DINGBATS;
@@ -727,6 +736,8 @@
field public static final int JAVANESE_ID = 181; // 0xb5
field public static final android.icu.lang.UCharacter.UnicodeBlock KAITHI;
field public static final int KAITHI_ID = 193; // 0xc1
+ field public static final android.icu.lang.UCharacter.UnicodeBlock KAKTOVIK_NUMERALS;
+ field public static final int KAKTOVIK_NUMERALS_ID = 325; // 0x145
field public static final android.icu.lang.UCharacter.UnicodeBlock KANA_EXTENDED_A;
field public static final int KANA_EXTENDED_A_ID = 275; // 0x113
field public static final android.icu.lang.UCharacter.UnicodeBlock KANA_EXTENDED_B;
@@ -743,6 +754,8 @@
field public static final int KATAKANA_ID = 63; // 0x3f
field public static final android.icu.lang.UCharacter.UnicodeBlock KATAKANA_PHONETIC_EXTENSIONS;
field public static final int KATAKANA_PHONETIC_EXTENSIONS_ID = 107; // 0x6b
+ field public static final android.icu.lang.UCharacter.UnicodeBlock KAWI;
+ field public static final int KAWI_ID = 326; // 0x146
field public static final android.icu.lang.UCharacter.UnicodeBlock KAYAH_LI;
field public static final int KAYAH_LI_ID = 162; // 0xa2
field public static final android.icu.lang.UCharacter.UnicodeBlock KHAROSHTHI;
@@ -869,6 +882,8 @@
field public static final int MYANMAR_ID = 28; // 0x1c
field public static final android.icu.lang.UCharacter.UnicodeBlock NABATAEAN;
field public static final int NABATAEAN_ID = 239; // 0xef
+ field public static final android.icu.lang.UCharacter.UnicodeBlock NAG_MUNDARI;
+ field public static final int NAG_MUNDARI_ID = 327; // 0x147
field public static final android.icu.lang.UCharacter.UnicodeBlock NANDINAGARI;
field public static final int NANDINAGARI_ID = 294; // 0x126
field public static final android.icu.lang.UCharacter.UnicodeBlock NEWA;
@@ -1224,6 +1239,7 @@
field public static final int AGE = 16384; // 0x4000
field public static final int ALPHABETIC = 0; // 0x0
field public static final int ASCII_HEX_DIGIT = 1; // 0x1
+ field public static final int BASIC_EMOJI = 65; // 0x41
field public static final int BIDI_CLASS = 4096; // 0x1000
field public static final int BIDI_CONTROL = 2; // 0x2
field public static final int BIDI_MIRRORED = 3; // 0x3
@@ -1252,6 +1268,7 @@
field public static final int EAST_ASIAN_WIDTH = 4100; // 0x1004
field public static final int EMOJI = 57; // 0x39
field public static final int EMOJI_COMPONENT = 61; // 0x3d
+ field public static final int EMOJI_KEYCAP_SEQUENCE = 66; // 0x42
field public static final int EMOJI_MODIFIER = 59; // 0x3b
field public static final int EMOJI_MODIFIER_BASE = 60; // 0x3c
field public static final int EMOJI_PRESENTATION = 58; // 0x3a
@@ -1309,6 +1326,11 @@
field public static final int QUOTATION_MARK = 25; // 0x19
field public static final int RADICAL = 26; // 0x1a
field public static final int REGIONAL_INDICATOR = 62; // 0x3e
+ field public static final int RGI_EMOJI = 71; // 0x47
+ field public static final int RGI_EMOJI_FLAG_SEQUENCE = 68; // 0x44
+ field public static final int RGI_EMOJI_MODIFIER_SEQUENCE = 67; // 0x43
+ field public static final int RGI_EMOJI_TAG_SEQUENCE = 69; // 0x45
+ field public static final int RGI_EMOJI_ZWJ_SEQUENCE = 70; // 0x46
field public static final int SCRIPT = 4106; // 0x100a
field public static final int SCRIPT_EXTENSIONS = 28672; // 0x7000
field public static final int SEGMENT_STARTER = 41; // 0x29
@@ -1431,6 +1453,7 @@
field public static final int KANNADA = 21; // 0x15
field public static final int KATAKANA = 22; // 0x16
field public static final int KATAKANA_OR_HIRAGANA = 54; // 0x36
+ field public static final int KAWI = 198; // 0xc6
field public static final int KAYAH_LI = 79; // 0x4f
field public static final int KHAROSHTHI = 57; // 0x39
field public static final int KHITAN_SMALL_SCRIPT = 191; // 0xbf
@@ -1477,6 +1500,7 @@
field public static final int MULTANI = 164; // 0xa4
field public static final int MYANMAR = 28; // 0x1c
field public static final int NABATAEAN = 143; // 0x8f
+ field public static final int NAG_MUNDARI = 199; // 0xc7
field public static final int NAKHI_GEBA = 132; // 0x84
field public static final int NANDINAGARI = 187; // 0xbb
field public static final int NEWA = 170; // 0xaa
@@ -1708,6 +1732,7 @@
public abstract class FractionPrecision extends android.icu.number.Precision {
method public android.icu.number.Precision withMaxDigits(int);
method public android.icu.number.Precision withMinDigits(int);
+ method public android.icu.number.Precision withSignificantDigits(int, int, android.icu.number.NumberFormatter.RoundingPriority);
}
public class IntegerWidth {
@@ -1756,16 +1781,28 @@
enum_constant public static final android.icu.number.NumberFormatter.GroupingStrategy THOUSANDS;
}
+ public enum NumberFormatter.RoundingPriority {
+ enum_constant public static final android.icu.number.NumberFormatter.RoundingPriority RELAXED;
+ enum_constant public static final android.icu.number.NumberFormatter.RoundingPriority STRICT;
+ }
+
public enum NumberFormatter.SignDisplay {
enum_constant public static final android.icu.number.NumberFormatter.SignDisplay ACCOUNTING;
enum_constant public static final android.icu.number.NumberFormatter.SignDisplay ACCOUNTING_ALWAYS;
enum_constant public static final android.icu.number.NumberFormatter.SignDisplay ACCOUNTING_EXCEPT_ZERO;
+ enum_constant public static final android.icu.number.NumberFormatter.SignDisplay ACCOUNTING_NEGATIVE;
enum_constant public static final android.icu.number.NumberFormatter.SignDisplay ALWAYS;
enum_constant public static final android.icu.number.NumberFormatter.SignDisplay AUTO;
enum_constant public static final android.icu.number.NumberFormatter.SignDisplay EXCEPT_ZERO;
+ enum_constant public static final android.icu.number.NumberFormatter.SignDisplay NEGATIVE;
enum_constant public static final android.icu.number.NumberFormatter.SignDisplay NEVER;
}
+ public enum NumberFormatter.TrailingZeroDisplay {
+ enum_constant public static final android.icu.number.NumberFormatter.TrailingZeroDisplay AUTO;
+ enum_constant public static final android.icu.number.NumberFormatter.TrailingZeroDisplay HIDE_IF_WHOLE;
+ }
+
public enum NumberFormatter.UnitWidth {
enum_constant public static final android.icu.number.NumberFormatter.UnitWidth FORMAL;
enum_constant public static final android.icu.number.NumberFormatter.UnitWidth FULL_NAME;
@@ -1839,6 +1876,7 @@
method public static android.icu.number.FractionPrecision minMaxFraction(int, int);
method public static android.icu.number.Precision minMaxSignificantDigits(int, int);
method public static android.icu.number.Precision minSignificantDigits(int);
+ method public android.icu.number.Precision trailingZeroDisplay(android.icu.number.NumberFormatter.TrailingZeroDisplay);
method public static android.icu.number.Precision unlimited();
}
@@ -3845,6 +3883,7 @@
method public int getRangeCount();
method public int getRangeEnd(int);
method public int getRangeStart(int);
+ method public boolean hasStrings();
method public int indexOf(int);
method public boolean isEmpty();
method public boolean isFrozen();
@@ -3909,6 +3948,7 @@
method public boolean nextRange();
method public void reset(android.icu.text.UnicodeSet);
method public void reset();
+ method public android.icu.text.UnicodeSetIterator skipToStrings();
field public static int IS_STRING;
field public int codepoint;
field public int codepointEnd;
@@ -4483,12 +4523,14 @@
method public android.icu.util.MeasureUnit.Complexity getComplexity();
method public int getDimensionality();
method public String getIdentifier();
+ method public android.icu.util.MeasureUnit.MeasurePrefix getPrefix();
method public String getSubtype();
method public String getType();
method public android.icu.util.MeasureUnit product(android.icu.util.MeasureUnit);
method public android.icu.util.MeasureUnit reciprocal();
method public java.util.List<android.icu.util.MeasureUnit> splitToSingleUnits();
method public android.icu.util.MeasureUnit withDimensionality(int);
+ method public android.icu.util.MeasureUnit withPrefix(android.icu.util.MeasureUnit.MeasurePrefix);
field public static final android.icu.util.MeasureUnit ACRE;
field public static final android.icu.util.MeasureUnit ACRE_FOOT;
field public static final android.icu.util.MeasureUnit AMPERE;
@@ -4547,6 +4589,7 @@
field public static final android.icu.util.TimeUnit HOUR;
field public static final android.icu.util.MeasureUnit INCH;
field public static final android.icu.util.MeasureUnit INCH_HG;
+ field public static final android.icu.util.MeasureUnit ITEM;
field public static final android.icu.util.MeasureUnit JOULE;
field public static final android.icu.util.MeasureUnit KARAT;
field public static final android.icu.util.MeasureUnit KELVIN;
@@ -4560,6 +4603,7 @@
field public static final android.icu.util.MeasureUnit KILOMETER_PER_HOUR;
field public static final android.icu.util.MeasureUnit KILOWATT;
field public static final android.icu.util.MeasureUnit KILOWATT_HOUR;
+ field public static final android.icu.util.MeasureUnit KILOWATT_HOUR_PER_100_KILOMETER;
field public static final android.icu.util.MeasureUnit KNOT;
field public static final android.icu.util.MeasureUnit LIGHT_YEAR;
field public static final android.icu.util.MeasureUnit LITER;
@@ -4588,6 +4632,7 @@
field public static final android.icu.util.MeasureUnit MILLIAMPERE;
field public static final android.icu.util.MeasureUnit MILLIBAR;
field public static final android.icu.util.MeasureUnit MILLIGRAM;
+ field public static final android.icu.util.MeasureUnit MILLIGRAM_OFGLUCOSE_PER_DECILITER;
field public static final android.icu.util.MeasureUnit MILLIGRAM_PER_DECILITER;
field public static final android.icu.util.MeasureUnit MILLILITER;
field public static final android.icu.util.MeasureUnit MILLIMETER;
@@ -4647,6 +4692,40 @@
enum_constant public static final android.icu.util.MeasureUnit.Complexity SINGLE;
}
+ public enum MeasureUnit.MeasurePrefix {
+ method public int getBase();
+ method public int getPower();
+ enum_constant public static final android.icu.util.MeasureUnit.MeasurePrefix ATTO;
+ enum_constant public static final android.icu.util.MeasureUnit.MeasurePrefix CENTI;
+ enum_constant public static final android.icu.util.MeasureUnit.MeasurePrefix DECI;
+ enum_constant public static final android.icu.util.MeasureUnit.MeasurePrefix DEKA;
+ enum_constant public static final android.icu.util.MeasureUnit.MeasurePrefix EXA;
+ enum_constant public static final android.icu.util.MeasureUnit.MeasurePrefix EXBI;
+ enum_constant public static final android.icu.util.MeasureUnit.MeasurePrefix FEMTO;
+ enum_constant public static final android.icu.util.MeasureUnit.MeasurePrefix GIBI;
+ enum_constant public static final android.icu.util.MeasureUnit.MeasurePrefix GIGA;
+ enum_constant public static final android.icu.util.MeasureUnit.MeasurePrefix HECTO;
+ enum_constant public static final android.icu.util.MeasureUnit.MeasurePrefix KIBI;
+ enum_constant public static final android.icu.util.MeasureUnit.MeasurePrefix KILO;
+ enum_constant public static final android.icu.util.MeasureUnit.MeasurePrefix MEBI;
+ enum_constant public static final android.icu.util.MeasureUnit.MeasurePrefix MEGA;
+ enum_constant public static final android.icu.util.MeasureUnit.MeasurePrefix MICRO;
+ enum_constant public static final android.icu.util.MeasureUnit.MeasurePrefix MILLI;
+ enum_constant public static final android.icu.util.MeasureUnit.MeasurePrefix NANO;
+ enum_constant public static final android.icu.util.MeasureUnit.MeasurePrefix ONE;
+ enum_constant public static final android.icu.util.MeasureUnit.MeasurePrefix PEBI;
+ enum_constant public static final android.icu.util.MeasureUnit.MeasurePrefix PETA;
+ enum_constant public static final android.icu.util.MeasureUnit.MeasurePrefix PICO;
+ enum_constant public static final android.icu.util.MeasureUnit.MeasurePrefix TEBI;
+ enum_constant public static final android.icu.util.MeasureUnit.MeasurePrefix TERA;
+ enum_constant public static final android.icu.util.MeasureUnit.MeasurePrefix YOBI;
+ enum_constant public static final android.icu.util.MeasureUnit.MeasurePrefix YOCTO;
+ enum_constant public static final android.icu.util.MeasureUnit.MeasurePrefix YOTTA;
+ enum_constant public static final android.icu.util.MeasureUnit.MeasurePrefix ZEBI;
+ enum_constant public static final android.icu.util.MeasureUnit.MeasurePrefix ZEPTO;
+ enum_constant public static final android.icu.util.MeasureUnit.MeasurePrefix ZETTA;
+ }
+
public class Output<T> {
ctor public Output();
ctor public Output(T);
@@ -4955,6 +5034,7 @@
field public static final android.icu.util.VersionInfo UNICODE_12_1;
field public static final android.icu.util.VersionInfo UNICODE_13_0;
field public static final android.icu.util.VersionInfo UNICODE_14_0;
+ field public static final android.icu.util.VersionInfo UNICODE_15_0;
field public static final android.icu.util.VersionInfo UNICODE_1_0;
field public static final android.icu.util.VersionInfo UNICODE_1_0_1;
field public static final android.icu.util.VersionInfo UNICODE_1_1_0;
diff --git a/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/TimeZoneDataFiles.java b/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/TimeZoneDataFiles.java
index 1dea86a..6f3b16e 100644
--- a/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/TimeZoneDataFiles.java
+++ b/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/TimeZoneDataFiles.java
@@ -32,7 +32,6 @@
public final class TimeZoneDataFiles {
private static final String ANDROID_ROOT_ENV = AndroidDataFiles.ANDROID_ROOT_ENV;
private static final String ANDROID_TZDATA_ROOT_ENV = AndroidDataFiles.ANDROID_TZDATA_ROOT_ENV;
- private static final String ANDROID_DATA_ENV = AndroidDataFiles.ANDROID_DATA_ENV;
private TimeZoneDataFiles() {}
@@ -40,28 +39,12 @@
* Returns time zone file paths for the specified file name in an array in the order they
* should be tried. See {@link AndroidDataFiles#generateIcuDataPath()} for ICU files instead.
* <ul>
- * <li>[0] - the location of the file in the /data partition (may not exist).</li>
- * <li>[1] - the location of the file from the time zone module under /apex (must exist).</li>
+ * <li>[0] - the location of the file from the time zone module under /apex (must exist).</li>
* </ul>
*/
// VisibleForTesting
public static String[] getTimeZoneFilePaths(String fileName) {
- return new String[] {
- getDataTimeZoneFile(fileName),
- getTimeZoneModuleTzFile(fileName),
- };
- }
-
- // Remove from CorePlatformApi when all users in platform code are removed. http://b/123398797
- @libcore.api.CorePlatformApi
- public static String getDataTimeZoneRootDir() {
- return System.getenv(ANDROID_DATA_ENV) + "/misc/zoneinfo/";
- }
-
- // Remove from CorePlatformApi when all users in platform code are removed. http://b/123398797
- @libcore.api.CorePlatformApi
- public static String getDataTimeZoneFile(String fileName) {
- return getDataTimeZoneRootDir() + "current/" + fileName;
+ return new String[] { getTimeZoneModuleTzFile(fileName) };
}
public static String getTimeZoneModuleTzFile(String fileName) {
diff --git a/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/TzDataSetVersion.java b/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/TzDataSetVersion.java
index 5af6d45..4c07f84 100644
--- a/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/TzDataSetVersion.java
+++ b/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/TzDataSetVersion.java
@@ -61,7 +61,7 @@
* version to 1 when doing so.
*/
// @VisibleForTesting : Keep this inline-able: it is used from CTS tests.
- public static final int CURRENT_FORMAT_MAJOR_VERSION = 6; // Android T
+ public static final int CURRENT_FORMAT_MAJOR_VERSION = 7; // Android U
/**
* Returns the major tz data format version supported by this device.
diff --git a/android_icu4j/resources/android/icu/ICUConfig.properties b/android_icu4j/resources/android/icu/ICUConfig.properties
index 9d26445..ae97732 100644
--- a/android_icu4j/resources/android/icu/ICUConfig.properties
+++ b/android_icu4j/resources/android/icu/ICUConfig.properties
@@ -63,3 +63,10 @@
# LocaleDisplayNames implementation class
# @internal
# android.icu.text.LocaleDisplayNames.impl = com.ibm.icu.impl.LocaleDisplayNamesImpl
+
+#
+# [Internal Use Only]
+# Enable ML phrase breaking
+# Android patch, http://b/219529457, for ML-based phrase line breaking
+# @internal
+android.icu.impl.breakiter.useMLPhraseBreaking = false
diff --git a/android_icu4j/src/main/java/android/icu/impl/CaseMapImpl.java b/android_icu4j/src/main/java/android/icu/impl/CaseMapImpl.java
index 7925b3c..24c5487 100644
--- a/android_icu4j/src/main/java/android/icu/impl/CaseMapImpl.java
+++ b/android_icu4j/src/main/java/android/icu/impl/CaseMapImpl.java
@@ -75,6 +75,10 @@
cpStart=cpLimit=limit;
}
+ public void moveTo(int i) {
+ cpStart=cpLimit=i;
+ }
+
/**
* Iterate forward through the string to fetch the next code point
* to be case-mapped, and set the context indexes for it.
@@ -194,6 +198,13 @@
return options | newOption;
}
+ private static final char ACUTE = '\u0301';
+
+ private static final int U_GC_M_MASK =
+ (1 << UCharacterCategory.NON_SPACING_MARK) |
+ (1 << UCharacterCategory.COMBINING_SPACING_MARK) |
+ (1 << UCharacterCategory.ENCLOSING_MARK);
+
private static final int LNS =
(1 << UCharacterCategory.UPPERCASE_LETTER) |
(1 << UCharacterCategory.LOWERCASE_LETTER) |
@@ -731,34 +742,25 @@
}
if(titleStart<index) {
- int titleLimit=iter.getCPLimit();
// titlecase c which is from [titleStart..titleLimit[
c = UCaseProps.INSTANCE.toFullTitle(c, iter, dest, caseLocale);
appendResult(c, dest, iter.getCPLength(), options, edits);
// Special case Dutch IJ titlecasing
+ int titleLimit;
if (titleStart+1 < index && caseLocale == UCaseProps.LOC_DUTCH) {
- char c1 = src.charAt(titleStart);
- if ((c1 == 'i' || c1 == 'I')) {
- char c2 = src.charAt(titleStart+1);
- if (c2 == 'j') {
- dest.append('J');
- if (edits != null) {
- edits.addReplace(1, 1);
- }
- c = iter.nextCaseMapCP();
- titleLimit++;
- assert c == c2;
- assert titleLimit == iter.getCPLimit();
- } else if (c2 == 'J') {
- // Keep the capital J from getting lowercased.
- appendUnchanged(src, titleStart + 1, 1, dest, options, edits);
- c = iter.nextCaseMapCP();
- titleLimit++;
- assert c == c2;
- assert titleLimit == iter.getCPLimit();
- }
+ if (c < 0) {
+ c = ~c;
}
+ if (c == 'I' || c == 'Í') {
+ titleLimit = maybeTitleDutchIJ(src, c, titleStart + 1, index, dest, options, edits);
+ iter.moveTo(titleLimit);
+ }
+ else {
+ titleLimit = iter.getCPLimit();
+ }
+ } else {
+ titleLimit = iter.getCPLimit();
}
// lowercase [titleLimit..index[
@@ -784,6 +786,84 @@
}
}
+ /**
+ * Input: c is a letter I with or without acute accent.
+ * start is the index in src after c, and is less than segmentLimit.
+ * If a plain i/I is followed by a plain j/J,
+ * or an i/I with acute (precomposed or decomposed) is followed by a j/J with acute,
+ * then we output accordingly.
+ *
+ * @return the src index after the titlecased sequence, or the start index if no Dutch IJ
+ * @throws IOException
+ */
+ private static <A extends Appendable> int maybeTitleDutchIJ(
+ CharSequence src, int c, int start, int segmentLimit,
+ A dest, int options, Edits edits) throws IOException {
+ assert start < segmentLimit;
+
+ int index = start;
+ boolean withAcute = false;
+
+ // If the conditions are met, then the following variables tell us what to output.
+ int unchanged1 = 0; // code units before the j, or the whole sequence (0..3)
+ boolean doTitleJ = false; // true if the j needs to be titlecased
+ int unchanged2 = 0; // after the j (0 or 1)
+
+ // next character after the first letter
+ char c2 = src.charAt(index++);
+
+ // Is the first letter an i/I with accent?
+ if (c == 'I') {
+ if (c2 == ACUTE) {
+ withAcute = true;
+ unchanged1 = 1;
+ if (index == segmentLimit) { return start; }
+ c2 = src.charAt(index++);
+ }
+ } else { // Í
+ withAcute = true;
+ }
+ // Is the next character a j/J?
+ if (c2 == 'j') {
+ doTitleJ = true;
+ } else if (c2 == 'J') {
+ ++unchanged1;
+ } else {
+ return start;
+ }
+ // A plain i/I must be followed by a plain j/J.
+ // An i/I with acute must be followed by a j/J with acute.
+ if (withAcute) {
+ if (index == segmentLimit || src.charAt(index++) != ACUTE) { return start; }
+ if (doTitleJ) {
+ unchanged2 = 1;
+ } else {
+ ++unchanged1;
+ }
+ }
+ // There must not be another combining mark.
+ if (index < segmentLimit) {
+ int cp = Character.codePointAt(src, index);
+ int bit = 1 << UCharacter.getType(cp);
+ if ((bit & U_GC_M_MASK) != 0) {
+ return start;
+ }
+ }
+ // Output the rest of the Dutch IJ.
+ appendUnchanged(src, start, unchanged1, dest, options, edits);
+ start += unchanged1;
+ if (doTitleJ) {
+ dest.append('J');
+ if (edits != null) {
+ edits.addReplace(1, 1);
+ }
+ ++start;
+ }
+ appendUnchanged(src, start, unchanged2, dest, options, edits);
+ assert start + unchanged2 == index;
+ return index;
+ }
+
public static String fold(int options, CharSequence src) {
if (src.length() <= 100 && (options & OMIT_UNCHANGED_TEXT) == 0) {
if (src.length() == 0) {
diff --git a/android_icu4j/src/main/java/android/icu/impl/ICUConfig.java b/android_icu4j/src/main/java/android/icu/impl/ICUConfig.java
index 5e47011..7fa7ff4 100644
--- a/android_icu4j/src/main/java/android/icu/impl/ICUConfig.java
+++ b/android_icu4j/src/main/java/android/icu/impl/ICUConfig.java
@@ -78,7 +78,7 @@
val = System.getProperty(name);
}
- if (val == null) {
+ if (val == null || val.equals("")) {
val = CONFIG_PROPS.getProperty(name, def);
}
return val;
diff --git a/android_icu4j/src/main/java/android/icu/impl/ICUResourceBundle.java b/android_icu4j/src/main/java/android/icu/impl/ICUResourceBundle.java
index db3af94..8113915 100644
--- a/android_icu4j/src/main/java/android/icu/impl/ICUResourceBundle.java
+++ b/android_icu4j/src/main/java/android/icu/impl/ICUResourceBundle.java
@@ -17,6 +17,7 @@
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.Comparator;
import java.util.EnumMap;
import java.util.Enumeration;
import java.util.HashMap;
@@ -430,7 +431,7 @@
}
UResource.Key key = new UResource.Key();
ReaderValue readerValue = new ReaderValue();
- rb.getAllItemsWithFallback(key, readerValue, sink);
+ rb.getAllItemsWithFallback(key, readerValue, sink, this);
}
/**
@@ -475,7 +476,7 @@
}
private void getAllItemsWithFallback(
- UResource.Key key, ReaderValue readerValue, UResource.Sink sink) {
+ UResource.Key key, ReaderValue readerValue, UResource.Sink sink, UResourceBundle requested) {
// We recursively enumerate child-first,
// only storing parent items in the absence of child items.
// The sink needs to store a placeholder value for the no-fallback/no-inheritance marker
@@ -504,10 +505,10 @@
// if we had followed an alias.
String[] pathKeys = new String[depth];
getResPathKeys(pathKeys, depth);
- rb = findResourceWithFallback(pathKeys, 0, parentBundle, null);
+ rb = findResourceWithFallback(pathKeys, 0, parentBundle, requested);
}
if (rb != null) {
- rb.getAllItemsWithFallback(key, readerValue, sink);
+ rb.getAllItemsWithFallback(key, readerValue, sink, requested);
}
}
}
@@ -1252,10 +1253,10 @@
localeID = ULocale.getBaseName(localeID);
ICUResourceBundle b;
if (openType == OpenType.LOCALE_DEFAULT_ROOT) {
- b = instantiateBundle(baseName, localeID, ULocale.getDefault().getBaseName(),
+ b = instantiateBundle(baseName, localeID, null, ULocale.getDefault().getBaseName(),
root, openType);
} else {
- b = instantiateBundle(baseName, localeID, null, root, openType);
+ b = instantiateBundle(baseName, localeID, null, null, root, openType);
}
if(b==null){
throw new MissingResourceException(
@@ -1269,8 +1270,99 @@
(localeID.length() == lang.length() || localeID.charAt(lang.length()) == '_');
}
+ private static final Comparator<String[]> COMPARE_FIRST_ELEMENT = new Comparator<String[]>() {
+ @Override
+ public int compare(String[] pair1, String[] pair2) {
+ return pair1[0].compareTo(pair2[0]);
+ }
+ };
+
+ private static String getExplicitParent(String localeID) {
+ return LocaleFallbackData.PARENT_LOCALE_TABLE.get(localeID);
+ }
+
+ private static String getDefaultScript(String language, String region) {
+ String localeID = language + "_" + region;
+ String result = LocaleFallbackData.DEFAULT_SCRIPT_TABLE.get(localeID);
+ if (result == null) {
+ result = LocaleFallbackData.DEFAULT_SCRIPT_TABLE.get(language);
+ }
+ if (result == null) {
+ result = "Latn";
+ }
+ return result;
+ }
+
+ private static String getParentLocaleID(String name, String origName, OpenType openType) {
+ // early out if the locale ID has a variant code or ends with _
+ if (name.endsWith("_") || !ULocale.getVariant(name).isEmpty()) {
+ int lastUnderbarPos = name.lastIndexOf('_');
+ if (lastUnderbarPos >= 0) {
+ return name.substring(0, lastUnderbarPos);
+ } else {
+ return null;
+ }
+ }
+
+ // TODO: Is there a better way to break the locale ID up into its consituent parts?
+ ULocale nameLocale = new ULocale(name);
+ String language = nameLocale.getLanguage();
+ String script = nameLocale.getScript();
+ String region = nameLocale.getCountry();
+
+ // if our open type is LOCALE_DEFAULT_ROOT, first look the locale ID up in the parent locale table; if that
+ // table specifies a parent for it, return that (we don't do this for the other open types-- if we're not
+ // falling back through the system default locale, we also want to do straight truncation fallback instead
+ // of looking things up in the parent locale table-- see https://www.unicode.org/reports/tr35/tr35.html#Parent_Locales:
+ // "Collation data, however, is an exception...")
+ if (openType == OpenType.LOCALE_DEFAULT_ROOT) {
+ String parentID = getExplicitParent(name);
+ if (parentID != null) {
+ return parentID.equals("root") ? null : parentID;
+ }
+ }
+
+ // if it's not in the parent locale table, figure out the fallback script algorithmically
+ // (see CLDR-15265 for an explanation of the algorithm)
+ if (!script.isEmpty() && !region.isEmpty()) {
+ // if "name" has both script and region, is the script the default script?
+ // - if so, remove it and keep the region
+ // - if not, remove the region and keep the script
+ if (getDefaultScript(language, region).equals(script)) {
+ return language + "_" + region;
+ } else {
+ return language + "_" + script;
+ }
+ } else if (!region.isEmpty()) {
+ // if "name" has region but not script, did the original locale ID specify a script?
+ // - if yes, replace the region with the script from the original locale ID
+ // - if no, replace the region with the default script for that language and region
+ String origNameScript = ULocale.getScript(origName);
+ if (!origNameScript.isEmpty()) {
+ return language + "_" + origNameScript;
+ } else {
+ return language + "_" + getDefaultScript(language, region);
+ }
+ } else if (!script.isEmpty()) {
+ // if "name" has script but not region (and our open type is LOCALE_DEFAULT_ROOT), is the script the
+ // default script for the language?
+ // - if so, remove it from the locale ID
+ // - if not, return "root" (bypassing the system default locale ID)
+ // (we don't do this for other open types for the same reason we don't look things up in the parent
+ // locale table for other open types-- see the reference to UTS #35 above)
+ if (openType != OpenType.LOCALE_DEFAULT_ROOT || getDefaultScript(language, null).equals(script)) {
+ return language;
+ } else {
+ return /*"root"*/null;
+ }
+ } else {
+ // if "name" just contains a language code, return null so the calling code falls back to "root"
+ return null;
+ }
+ }
+
private static ICUResourceBundle instantiateBundle(
- final String baseName, final String localeID, final String defaultID,
+ final String baseName, final String localeID, final String origLocaleID, final String defaultID,
final ClassLoader root, final OpenType openType) {
assert localeID.indexOf('@') < 0;
assert defaultID == null || defaultID.indexOf('@') < 0;
@@ -1316,17 +1408,15 @@
if (openType == OpenType.LOCALE_DEFAULT_ROOT && localeName.equals(defaultID)) {
localOpenType = OpenType.LOCALE_ROOT;
}
- int i = localeName.lastIndexOf('_');
- if (i != -1) {
- // Chop off the last underscore and the subtag after that.
- String temp = localeName.substring(0, i);
- b = instantiateBundle(baseName, temp, defaultID, root, localOpenType);
+ String origLocaleName = (origLocaleID != null) ? origLocaleID : localeName;
+ String fallbackLocaleID = getParentLocaleID(localeName, origLocaleName, openType);
+ if (fallbackLocaleID != null) {
+ b = instantiateBundle(baseName, fallbackLocaleID, origLocaleName, defaultID, root, localOpenType);
}else{
- // No underscore, only a base language subtag.
if(localOpenType == OpenType.LOCALE_DEFAULT_ROOT &&
!localeIDStartsWithLangSubtag(defaultID, localeName)) {
// Go to the default locale before root.
- b = instantiateBundle(baseName, defaultID, defaultID, root, localOpenType);
+ b = instantiateBundle(baseName, defaultID, null, defaultID, root, localOpenType);
} else if(localOpenType != OpenType.LOCALE_ONLY && !rootLocale.isEmpty()) {
// Ultimately go to root.
b = ICUResourceBundle.createBundle(baseName, rootLocale, root);
@@ -1340,11 +1430,11 @@
// TODO: C++ uresbund.cpp also checks for %%ParentIsRoot. Why not Java?
String parentLocaleName = ((ICUResourceBundleImpl.ResourceTable)b).findString("%%Parent");
if (parentLocaleName != null) {
- parent = instantiateBundle(baseName, parentLocaleName, defaultID, root, openType);
+ parent = instantiateBundle(baseName, parentLocaleName, null, defaultID, root, openType);
} else if (i != -1) {
- parent = instantiateBundle(baseName, localeName.substring(0, i), defaultID, root, openType);
+ parent = instantiateBundle(baseName, localeName.substring(0, i), null, defaultID, root, openType);
} else if (!localeName.equals(rootLocale)){
- parent = instantiateBundle(baseName, rootLocale, defaultID, root, openType);
+ parent = instantiateBundle(baseName, rootLocale, null, defaultID, root, openType);
}
if (!b.equals(parent)){
diff --git a/android_icu4j/src/main/java/android/icu/impl/ICUResourceBundleReader.java b/android_icu4j/src/main/java/android/icu/impl/ICUResourceBundleReader.java
index ce7b1c7..daadfeb 100644
--- a/android_icu4j/src/main/java/android/icu/impl/ICUResourceBundleReader.java
+++ b/android_icu4j/src/main/java/android/icu/impl/ICUResourceBundleReader.java
@@ -15,6 +15,7 @@
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.IntBuffer;
+import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import android.icu.util.ICUException;
@@ -446,13 +447,12 @@
}
private static String makeKeyStringFromBytes(byte[] keyBytes, int keyOffset) {
- StringBuilder sb = new StringBuilder();
- byte b;
- while((b = keyBytes[keyOffset]) != 0) {
- ++keyOffset;
- sb.append((char)b);
+ int end = keyOffset;
+ while(keyBytes[end] != 0) {
+ ++end;
}
- return sb.toString();
+ int len = end - keyOffset;
+ return new String(keyBytes, keyOffset, len, StandardCharsets.ISO_8859_1);
}
private String getKey16String(int keyOffset) {
if(keyOffset < localKeyLimit) {
diff --git a/android_icu4j/src/main/java/android/icu/impl/LocaleFallbackData.java b/android_icu4j/src/main/java/android/icu/impl/LocaleFallbackData.java
new file mode 100644
index 0000000..6ed1cad
--- /dev/null
+++ b/android_icu4j/src/main/java/android/icu/impl/LocaleFallbackData.java
@@ -0,0 +1,576 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+// © 2022 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html
+//
+// Internal static data tables used by ICUResourceBundle.java
+// WARNING: This file is mechanically generated by the CLDR-to-ICU tool
+// (see tools/cldr/cldr-to-icu/src/main/java/org/unicode/tool/cldrtoicu/generator/ResourcFallbackCodeGenerator.java).
+// DO NOT HAND EDIT!!!
+
+package android.icu.impl;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+class LocaleFallbackData {
+ //======================================================================
+ // Default script table
+ public static final Map<String, String> DEFAULT_SCRIPT_TABLE = buildDefaultScriptTable();
+
+ private static Map<String, String> buildDefaultScriptTable() {
+ Map<String, String> t = new HashMap<>();
+ t.put("ab", "Cyrl");
+ t.put("abq", "Cyrl");
+ t.put("adp", "Tibt");
+ t.put("ady", "Cyrl");
+ t.put("ae", "Avst");
+ t.put("aeb", "Arab");
+ t.put("aho", "Ahom");
+ t.put("ajt", "Arab");
+ t.put("akk", "Xsux");
+ t.put("alt", "Cyrl");
+ t.put("am", "Ethi");
+ t.put("apc", "Arab");
+ t.put("apd", "Arab");
+ t.put("ar", "Arab");
+ t.put("arc", "Armi");
+ t.put("arq", "Arab");
+ t.put("ars", "Arab");
+ t.put("ary", "Arab");
+ t.put("arz", "Arab");
+ t.put("as", "Beng");
+ t.put("ase", "Sgnw");
+ t.put("av", "Cyrl");
+ t.put("avl", "Arab");
+ t.put("awa", "Deva");
+ t.put("az_IQ", "Arab");
+ t.put("az_IR", "Arab");
+ t.put("az_RU", "Cyrl");
+ t.put("ba", "Cyrl");
+ t.put("bal", "Arab");
+ t.put("bap", "Deva");
+ t.put("bax", "Bamu");
+ t.put("bcq", "Ethi");
+ t.put("be", "Cyrl");
+ t.put("bej", "Arab");
+ t.put("bfq", "Taml");
+ t.put("bft", "Arab");
+ t.put("bfy", "Deva");
+ t.put("bg", "Cyrl");
+ t.put("bgc", "Deva");
+ t.put("bgn", "Arab");
+ t.put("bgx", "Grek");
+ t.put("bhb", "Deva");
+ t.put("bhi", "Deva");
+ t.put("bho", "Deva");
+ t.put("bji", "Ethi");
+ t.put("bjj", "Deva");
+ t.put("blt", "Tavt");
+ t.put("bn", "Beng");
+ t.put("bo", "Tibt");
+ t.put("bpy", "Beng");
+ t.put("bqi", "Arab");
+ t.put("bra", "Deva");
+ t.put("brh", "Arab");
+ t.put("brx", "Deva");
+ t.put("bsq", "Bass");
+ t.put("bst", "Ethi");
+ t.put("btv", "Deva");
+ t.put("bua", "Cyrl");
+ t.put("byn", "Ethi");
+ t.put("ccp", "Cakm");
+ t.put("ce", "Cyrl");
+ t.put("chm", "Cyrl");
+ t.put("chr", "Cher");
+ t.put("cja", "Arab");
+ t.put("cjm", "Cham");
+ t.put("ckb", "Arab");
+ t.put("cmg", "Soyo");
+ t.put("cop", "Copt");
+ t.put("cr", "Cans");
+ t.put("crh", "Cyrl");
+ t.put("crk", "Cans");
+ t.put("crl", "Cans");
+ t.put("csw", "Cans");
+ t.put("ctd", "Pauc");
+ t.put("cu", "Cyrl");
+ t.put("cv", "Cyrl");
+ t.put("dar", "Cyrl");
+ t.put("dcc", "Arab");
+ t.put("dgl", "Arab");
+ t.put("dmf", "Medf");
+ t.put("doi", "Deva");
+ t.put("drh", "Mong");
+ t.put("drs", "Ethi");
+ t.put("dty", "Deva");
+ t.put("dv", "Thaa");
+ t.put("dz", "Tibt");
+ t.put("egy", "Egyp");
+ t.put("eky", "Kali");
+ t.put("el", "Grek");
+ t.put("esg", "Gonm");
+ t.put("ett", "Ital");
+ t.put("fa", "Arab");
+ t.put("fia", "Arab");
+ t.put("fub", "Arab");
+ t.put("gan", "Hans");
+ t.put("gbm", "Deva");
+ t.put("gbz", "Arab");
+ t.put("gez", "Ethi");
+ t.put("ggn", "Deva");
+ t.put("gjk", "Arab");
+ t.put("gju", "Arab");
+ t.put("glk", "Arab");
+ t.put("gmv", "Ethi");
+ t.put("gof", "Ethi");
+ t.put("gom", "Deva");
+ t.put("gon", "Telu");
+ t.put("got", "Goth");
+ t.put("grc", "Cprt");
+ t.put("grt", "Beng");
+ t.put("gu", "Gujr");
+ t.put("gvr", "Deva");
+ t.put("gwc", "Arab");
+ t.put("gwt", "Arab");
+ t.put("ha_CM", "Arab");
+ t.put("ha_SD", "Arab");
+ t.put("hak", "Hans");
+ t.put("haz", "Arab");
+ t.put("hdy", "Ethi");
+ t.put("he", "Hebr");
+ t.put("hi", "Deva");
+ t.put("hlu", "Hluw");
+ t.put("hmd", "Plrd");
+ t.put("hnd", "Arab");
+ t.put("hne", "Deva");
+ t.put("hnj", "Hmnp");
+ t.put("hno", "Arab");
+ t.put("hoc", "Deva");
+ t.put("hoj", "Deva");
+ t.put("hsn", "Hans");
+ t.put("hy", "Armn");
+ t.put("ii", "Yiii");
+ t.put("inh", "Cyrl");
+ t.put("iu", "Cans");
+ t.put("iw", "Hebr");
+ t.put("ja", "Jpan");
+ t.put("ji", "Hebr");
+ t.put("jml", "Deva");
+ t.put("ka", "Geor");
+ t.put("kaa", "Cyrl");
+ t.put("kaw", "Kawi");
+ t.put("kbd", "Cyrl");
+ t.put("kby", "Arab");
+ t.put("kdt", "Thai");
+ t.put("kfr", "Deva");
+ t.put("kfy", "Deva");
+ t.put("khb", "Talu");
+ t.put("khn", "Deva");
+ t.put("kht", "Mymr");
+ t.put("khw", "Arab");
+ t.put("kjg", "Laoo");
+ t.put("kk", "Cyrl");
+ t.put("kk_AF", "Arab");
+ t.put("kk_CN", "Arab");
+ t.put("kk_IR", "Arab");
+ t.put("kk_MN", "Arab");
+ t.put("km", "Khmr");
+ t.put("kn", "Knda");
+ t.put("ko", "Kore");
+ t.put("koi", "Cyrl");
+ t.put("kok", "Deva");
+ t.put("kqy", "Ethi");
+ t.put("krc", "Cyrl");
+ t.put("kru", "Deva");
+ t.put("ks", "Arab");
+ t.put("ktb", "Ethi");
+ t.put("ku_LB", "Arab");
+ t.put("kum", "Cyrl");
+ t.put("kv", "Cyrl");
+ t.put("kvx", "Arab");
+ t.put("kxc", "Ethi");
+ t.put("kxl", "Deva");
+ t.put("kxm", "Thai");
+ t.put("kxp", "Arab");
+ t.put("ky", "Cyrl");
+ t.put("ky_CN", "Arab");
+ t.put("kzh", "Arab");
+ t.put("lab", "Lina");
+ t.put("lad", "Hebr");
+ t.put("lah", "Arab");
+ t.put("lbe", "Cyrl");
+ t.put("lcp", "Thai");
+ t.put("lep", "Lepc");
+ t.put("lez", "Cyrl");
+ t.put("lif", "Deva");
+ t.put("lis", "Lisu");
+ t.put("lki", "Arab");
+ t.put("lmn", "Telu");
+ t.put("lo", "Laoo");
+ t.put("lrc", "Arab");
+ t.put("luz", "Arab");
+ t.put("lwl", "Thai");
+ t.put("lzh", "Hans");
+ t.put("mag", "Deva");
+ t.put("mai", "Deva");
+ t.put("man_GN", "Nkoo");
+ t.put("mde", "Arab");
+ t.put("mdf", "Cyrl");
+ t.put("mdx", "Ethi");
+ t.put("mfa", "Arab");
+ t.put("mgp", "Deva");
+ t.put("mk", "Cyrl");
+ t.put("mki", "Arab");
+ t.put("ml", "Mlym");
+ t.put("mn", "Cyrl");
+ t.put("mn_CN", "Mong");
+ t.put("mni", "Beng");
+ t.put("mnw", "Mymr");
+ t.put("mr", "Deva");
+ t.put("mrd", "Deva");
+ t.put("mrj", "Cyrl");
+ t.put("mro", "Mroo");
+ t.put("ms_CC", "Arab");
+ t.put("mtr", "Deva");
+ t.put("mvy", "Arab");
+ t.put("mwr", "Deva");
+ t.put("mww", "Hmnp");
+ t.put("my", "Mymr");
+ t.put("mym", "Ethi");
+ t.put("myv", "Cyrl");
+ t.put("myz", "Mand");
+ t.put("mzn", "Arab");
+ t.put("nan", "Hans");
+ t.put("ne", "Deva");
+ t.put("new", "Deva");
+ t.put("nnp", "Wcho");
+ t.put("nod", "Lana");
+ t.put("noe", "Deva");
+ t.put("non", "Runr");
+ t.put("nqo", "Nkoo");
+ t.put("nsk", "Cans");
+ t.put("nst", "Tnsa");
+ t.put("oj", "Cans");
+ t.put("ojs", "Cans");
+ t.put("or", "Orya");
+ t.put("oru", "Arab");
+ t.put("os", "Cyrl");
+ t.put("osa", "Osge");
+ t.put("ota", "Arab");
+ t.put("otk", "Orkh");
+ t.put("oui", "Ougr");
+ t.put("pa", "Guru");
+ t.put("pa_PK", "Arab");
+ t.put("pal", "Phli");
+ t.put("peo", "Xpeo");
+ t.put("phl", "Arab");
+ t.put("phn", "Phnx");
+ t.put("pka", "Brah");
+ t.put("pnt", "Grek");
+ t.put("ppa", "Deva");
+ t.put("pra", "Khar");
+ t.put("prd", "Arab");
+ t.put("ps", "Arab");
+ t.put("raj", "Deva");
+ t.put("rhg", "Rohg");
+ t.put("rif", "Tfng");
+ t.put("rjs", "Deva");
+ t.put("rkt", "Beng");
+ t.put("rmt", "Arab");
+ t.put("ru", "Cyrl");
+ t.put("rue", "Cyrl");
+ t.put("ryu", "Kana");
+ t.put("sa", "Deva");
+ t.put("sah", "Cyrl");
+ t.put("sat", "Olck");
+ t.put("saz", "Saur");
+ t.put("sck", "Deva");
+ t.put("scl", "Arab");
+ t.put("sd", "Arab");
+ t.put("sd_IN", "Deva");
+ t.put("sdh", "Arab");
+ t.put("sga", "Ogam");
+ t.put("sgw", "Ethi");
+ t.put("shi", "Tfng");
+ t.put("shn", "Mymr");
+ t.put("shu", "Arab");
+ t.put("si", "Sinh");
+ t.put("skr", "Arab");
+ t.put("smp", "Samr");
+ t.put("sog", "Sogd");
+ t.put("sou", "Thai");
+ t.put("sr", "Cyrl");
+ t.put("srb", "Sora");
+ t.put("srx", "Deva");
+ t.put("swb", "Arab");
+ t.put("swv", "Deva");
+ t.put("syl", "Beng");
+ t.put("syr", "Syrc");
+ t.put("ta", "Taml");
+ t.put("taj", "Deva");
+ t.put("tcy", "Knda");
+ t.put("tdd", "Tale");
+ t.put("tdg", "Deva");
+ t.put("tdh", "Deva");
+ t.put("te", "Telu");
+ t.put("tg", "Cyrl");
+ t.put("tg_PK", "Arab");
+ t.put("th", "Thai");
+ t.put("thl", "Deva");
+ t.put("thq", "Deva");
+ t.put("thr", "Deva");
+ t.put("ti", "Ethi");
+ t.put("tig", "Ethi");
+ t.put("tkt", "Deva");
+ t.put("trw", "Arab");
+ t.put("tsd", "Grek");
+ t.put("tsf", "Deva");
+ t.put("tsj", "Tibt");
+ t.put("tt", "Cyrl");
+ t.put("tts", "Thai");
+ t.put("txg", "Tang");
+ t.put("txo", "Toto");
+ t.put("tyv", "Cyrl");
+ t.put("udi", "Aghb");
+ t.put("udm", "Cyrl");
+ t.put("ug", "Arab");
+ t.put("ug_KZ", "Cyrl");
+ t.put("ug_MN", "Cyrl");
+ t.put("uga", "Ugar");
+ t.put("uk", "Cyrl");
+ t.put("unr", "Beng");
+ t.put("unr_NP", "Deva");
+ t.put("unx", "Beng");
+ t.put("ur", "Arab");
+ t.put("uz_AF", "Arab");
+ t.put("uz_CN", "Cyrl");
+ t.put("vai", "Vaii");
+ t.put("wal", "Ethi");
+ t.put("wbq", "Telu");
+ t.put("wbr", "Deva");
+ t.put("wni", "Arab");
+ t.put("wsg", "Gong");
+ t.put("wtm", "Deva");
+ t.put("wuu", "Hans");
+ t.put("xco", "Chrs");
+ t.put("xcr", "Cari");
+ t.put("xlc", "Lyci");
+ t.put("xld", "Lydi");
+ t.put("xmf", "Geor");
+ t.put("xmn", "Mani");
+ t.put("xmr", "Merc");
+ t.put("xna", "Narb");
+ t.put("xnr", "Deva");
+ t.put("xpr", "Prti");
+ t.put("xsa", "Sarb");
+ t.put("xsr", "Deva");
+ t.put("yi", "Hebr");
+ t.put("yue", "Hant");
+ t.put("yue_CN", "Hans");
+ t.put("zdj", "Arab");
+ t.put("zgh", "Tfng");
+ t.put("zh", "Hans");
+ t.put("zh_AU", "Hant");
+ t.put("zh_BN", "Hant");
+ t.put("zh_GB", "Hant");
+ t.put("zh_GF", "Hant");
+ t.put("zh_HK", "Hant");
+ t.put("zh_ID", "Hant");
+ t.put("zh_MO", "Hant");
+ t.put("zh_PA", "Hant");
+ t.put("zh_PF", "Hant");
+ t.put("zh_PH", "Hant");
+ t.put("zh_SR", "Hant");
+ t.put("zh_TH", "Hant");
+ t.put("zh_TW", "Hant");
+ t.put("zh_US", "Hant");
+ t.put("zh_VN", "Hant");
+ t.put("zhx", "Nshu");
+ t.put("zkt", "Kits");
+ return Collections.unmodifiableMap(t);
+ }
+
+ //======================================================================
+ // Parent locale table
+ public static final Map<String, String> PARENT_LOCALE_TABLE = buildParentLocaleTable();
+
+ private static Map<String, String> buildParentLocaleTable() {
+ Map<String, String> t = new HashMap<>();
+ t.put("az_Arab", "root");
+ t.put("az_Cyrl", "root");
+ t.put("bal_Latn", "root");
+ t.put("blt_Latn", "root");
+ t.put("bm_Nkoo", "root");
+ t.put("bs_Cyrl", "root");
+ t.put("byn_Latn", "root");
+ t.put("cu_Glag", "root");
+ t.put("dje_Arab", "root");
+ t.put("dyo_Arab", "root");
+ t.put("en_150", "en_001");
+ t.put("en_AG", "en_001");
+ t.put("en_AI", "en_001");
+ t.put("en_AT", "en_150");
+ t.put("en_AU", "en_001");
+ t.put("en_BB", "en_001");
+ t.put("en_BE", "en_150");
+ t.put("en_BM", "en_001");
+ t.put("en_BS", "en_001");
+ t.put("en_BW", "en_001");
+ t.put("en_BZ", "en_001");
+ t.put("en_CC", "en_001");
+ t.put("en_CH", "en_150");
+ t.put("en_CK", "en_001");
+ t.put("en_CM", "en_001");
+ t.put("en_CX", "en_001");
+ t.put("en_CY", "en_001");
+ t.put("en_DE", "en_150");
+ t.put("en_DG", "en_001");
+ t.put("en_DK", "en_150");
+ t.put("en_DM", "en_001");
+ t.put("en_Dsrt", "root");
+ t.put("en_ER", "en_001");
+ t.put("en_FI", "en_150");
+ t.put("en_FJ", "en_001");
+ t.put("en_FK", "en_001");
+ t.put("en_FM", "en_001");
+ t.put("en_GB", "en_001");
+ t.put("en_GD", "en_001");
+ t.put("en_GG", "en_001");
+ t.put("en_GH", "en_001");
+ t.put("en_GI", "en_001");
+ t.put("en_GM", "en_001");
+ t.put("en_GY", "en_001");
+ t.put("en_HK", "en_001");
+ t.put("en_IE", "en_001");
+ t.put("en_IL", "en_001");
+ t.put("en_IM", "en_001");
+ t.put("en_IN", "en_001");
+ t.put("en_IO", "en_001");
+ t.put("en_JE", "en_001");
+ t.put("en_JM", "en_001");
+ t.put("en_KE", "en_001");
+ t.put("en_KI", "en_001");
+ t.put("en_KN", "en_001");
+ t.put("en_KY", "en_001");
+ t.put("en_LC", "en_001");
+ t.put("en_LR", "en_001");
+ t.put("en_LS", "en_001");
+ t.put("en_MG", "en_001");
+ t.put("en_MO", "en_001");
+ t.put("en_MS", "en_001");
+ t.put("en_MT", "en_001");
+ t.put("en_MU", "en_001");
+ t.put("en_MV", "en_001");
+ t.put("en_MW", "en_001");
+ t.put("en_MY", "en_001");
+ t.put("en_NA", "en_001");
+ t.put("en_NF", "en_001");
+ t.put("en_NG", "en_001");
+ t.put("en_NL", "en_150");
+ t.put("en_NR", "en_001");
+ t.put("en_NU", "en_001");
+ t.put("en_NZ", "en_001");
+ t.put("en_PG", "en_001");
+ t.put("en_PK", "en_001");
+ t.put("en_PN", "en_001");
+ t.put("en_PW", "en_001");
+ t.put("en_RW", "en_001");
+ t.put("en_SB", "en_001");
+ t.put("en_SC", "en_001");
+ t.put("en_SD", "en_001");
+ t.put("en_SE", "en_150");
+ t.put("en_SG", "en_001");
+ t.put("en_SH", "en_001");
+ t.put("en_SI", "en_150");
+ t.put("en_SL", "en_001");
+ t.put("en_SS", "en_001");
+ t.put("en_SX", "en_001");
+ t.put("en_SZ", "en_001");
+ t.put("en_Shaw", "root");
+ t.put("en_TC", "en_001");
+ t.put("en_TK", "en_001");
+ t.put("en_TO", "en_001");
+ t.put("en_TT", "en_001");
+ t.put("en_TV", "en_001");
+ t.put("en_TZ", "en_001");
+ t.put("en_UG", "en_001");
+ t.put("en_VC", "en_001");
+ t.put("en_VG", "en_001");
+ t.put("en_VU", "en_001");
+ t.put("en_WS", "en_001");
+ t.put("en_ZA", "en_001");
+ t.put("en_ZM", "en_001");
+ t.put("en_ZW", "en_001");
+ t.put("es_AR", "es_419");
+ t.put("es_BO", "es_419");
+ t.put("es_BR", "es_419");
+ t.put("es_BZ", "es_419");
+ t.put("es_CL", "es_419");
+ t.put("es_CO", "es_419");
+ t.put("es_CR", "es_419");
+ t.put("es_CU", "es_419");
+ t.put("es_DO", "es_419");
+ t.put("es_EC", "es_419");
+ t.put("es_GT", "es_419");
+ t.put("es_HN", "es_419");
+ t.put("es_MX", "es_419");
+ t.put("es_NI", "es_419");
+ t.put("es_PA", "es_419");
+ t.put("es_PE", "es_419");
+ t.put("es_PR", "es_419");
+ t.put("es_PY", "es_419");
+ t.put("es_SV", "es_419");
+ t.put("es_US", "es_419");
+ t.put("es_UY", "es_419");
+ t.put("es_VE", "es_419");
+ t.put("ff_Adlm", "root");
+ t.put("ff_Arab", "root");
+ t.put("ha_Arab", "root");
+ t.put("hi_Latn", "en_IN");
+ t.put("ht", "fr_HT");
+ t.put("iu_Latn", "root");
+ t.put("kk_Arab", "root");
+ t.put("ks_Deva", "root");
+ t.put("ku_Arab", "root");
+ t.put("ky_Arab", "root");
+ t.put("ky_Latn", "root");
+ t.put("ml_Arab", "root");
+ t.put("mn_Mong", "root");
+ t.put("mni_Mtei", "root");
+ t.put("ms_Arab", "root");
+ t.put("nb", "no");
+ t.put("nn", "no");
+ t.put("pa_Arab", "root");
+ t.put("pt_AO", "pt_PT");
+ t.put("pt_CH", "pt_PT");
+ t.put("pt_CV", "pt_PT");
+ t.put("pt_FR", "pt_PT");
+ t.put("pt_GQ", "pt_PT");
+ t.put("pt_GW", "pt_PT");
+ t.put("pt_LU", "pt_PT");
+ t.put("pt_MO", "pt_PT");
+ t.put("pt_MZ", "pt_PT");
+ t.put("pt_ST", "pt_PT");
+ t.put("pt_TL", "pt_PT");
+ t.put("sat_Deva", "root");
+ t.put("sd_Deva", "root");
+ t.put("sd_Khoj", "root");
+ t.put("sd_Sind", "root");
+ t.put("shi_Latn", "root");
+ t.put("so_Arab", "root");
+ t.put("sr_Latn", "root");
+ t.put("sw_Arab", "root");
+ t.put("tg_Arab", "root");
+ t.put("ug_Cyrl", "root");
+ t.put("uz_Arab", "root");
+ t.put("uz_Cyrl", "root");
+ t.put("vai_Latn", "root");
+ t.put("wo_Arab", "root");
+ t.put("yo_Arab", "root");
+ t.put("yue_Hans", "root");
+ t.put("zh_Hant", "root");
+ t.put("zh_Hant_MO", "zh_Hant_HK");
+ return Collections.unmodifiableMap(t);
+ }
+}
diff --git a/android_icu4j/src/main/java/android/icu/impl/LocaleIDs.java b/android_icu4j/src/main/java/android/icu/impl/LocaleIDs.java
index f1c0ac5..cef1174 100644
--- a/android_icu4j/src/main/java/android/icu/impl/LocaleIDs.java
+++ b/android_icu4j/src/main/java/android/icu/impl/LocaleIDs.java
@@ -134,6 +134,7 @@
*/
/* tables updated per http://lcweb.loc.gov/standards/iso639-2/
to include the revisions up to 2001/7/27 *CWB*/
+ /* Subsequent hand addition of selected languages */
/* The 3 character codes are the terminology codes like RFC 3066.
This is compatible with prior ICU codes */
/* "in" "iw" "ji" "jw" & "sh" have been withdrawn but are still in
@@ -145,183 +146,181 @@
/* This list MUST be in sorted order, and MUST contain the two-letter codes
if one exists otherwise use the three letter code */
private static final String[] _languages = {
- "aa", "ab", "ace", "ach", "ada", "ady", "ae", "af",
- "afa", "afh", "agq", "ain", "ak", "akk", "ale", "alg",
- "alt", "am", "an", "ang", "anp", "apa", "ar", "arc",
- "arn", "arp", "ars", "art", "arw", "as", "asa", "ast",
- "ath", "aus", "av", "awa", "ay", "az",
- "ba", "bad", "bai", "bal", "ban", "bas", "bat", "bax",
- "bbj", "be", "bej", "bem", "ber", "bez", "bfd", "bg",
- "bh", "bho", "bi", "bik", "bin", "bkm", "bla", "bm",
- "bn", "bnt", "bo", "br", "bra", "brx", "bs", "bss",
- "btk", "bua", "bug", "bum", "byn", "byv",
- "ca", "cad", "cai", "car", "cau", "cay", "cch", "ce",
- "ceb", "cel", "cgg", "ch", "chb", "chg", "chk", "chm",
- "chn", "cho", "chp", "chr", "chy", "ckb", "cmc", "co",
- "cop", "cpe", "cpf", "cpp", "cr", "crh", "crp", "cs",
- "csb", "cu", "cus", "cv", "cy",
- "da", "dak", "dar", "dav", "day", "de", "del", "den",
- "dgr", "din", "dje", "doi", "dra", "dsb", "dua", "dum",
- "dv", "dyo", "dyu", "dz", "dzg",
- "ebu", "ee", "efi", "egy", "eka", "el", "elx", "en",
- "enm", "eo", "es", "et", "eu", "ewo",
- "fa", "fan", "fat", "ff", "fi", "fil", "fiu", "fj",
- "fo", "fon", "fr", "frm", "fro", "frr", "frs", "fur",
- "fy",
- "ga", "gaa", "gay", "gba", "gd", "gem", "gez", "gil",
- "gl", "gmh", "gn", "goh", "gon", "gor", "got", "grb",
- "grc", "gsw", "gu", "guz", "gv", "gwi",
- "ha", "hai", "haw", "he", "hi", "hil", "him", "hit",
- "hmn", "ho", "hr", "hsb", "ht", "hu", "hup", "hy",
- "hz",
- "ia", "iba", "ibb", "id", "ie", "ig", "ii", "ijo",
- "ik", "ilo", "inc", "ine", "inh", "io", "ira", "iro",
- "is", "it", "iu",
- "ja", "jbo", "jgo", "jmc", "jpr", "jrb", "jv",
- "ka", "kaa", "kab", "kac", "kaj", "kam", "kar", "kaw",
- "kbd", "kbl", "kcg", "kde", "kea", "kfo", "kg", "kha",
- "khi", "kho", "khq", "ki", "kj", "kk", "kkj", "kl",
- "kln", "km", "kmb", "kn", "ko", "kok", "kos", "kpe",
- "kr", "krc", "krl", "kro", "kru", "ks", "ksb", "ksf",
- "ksh", "ku", "kum", "kut", "kv", "kw", "ky",
- "la", "lad", "lag", "lah", "lam", "lb", "lez", "lg",
- "li", "lkt", "ln", "lo", "lol", "loz", "lt", "lu",
- "lua", "lui", "lun", "luo", "lus", "luy", "lv",
- "mad", "maf", "mag", "mai", "mak", "man", "map", "mas",
- "mde", "mdf", "mdr", "men", "mer", "mfe", "mg", "mga",
- "mgh", "mgo", "mh", "mi", "mic", "min", "mis", "mk",
- "mkh", "ml", "mn", "mnc", "mni", "mno", "mo", "moh",
- "mos", "mr", "ms", "mt", "mua", "mul", "mun", "mus",
- "mwl", "mwr", "my", "mye", "myn", "myv",
- "na", "nah", "nai", "nap", "naq", "nb", "nd", "nds",
- "ne", "new", "ng", "nia", "nic", "niu", "nl", "nmg",
- "nn", "nnh", "no", "nog", "non", "nqo", "nr", "nso",
- "nub", "nus", "nv", "nwc", "ny", "nym", "nyn", "nyo",
- "nzi",
- "oc", "oj", "om", "or", "os", "osa", "ota", "oto",
- "pa", "paa", "pag", "pal", "pam", "pap", "pau", "peo",
- "phi", "phn", "pi", "pl", "pon", "pra", "pro", "ps",
- "pt",
- "qu",
- "raj", "rap", "rar", "rm", "rn", "ro", "roa", "rof",
- "rom", "ru", "rup", "rw", "rwk",
- "sa", "sad", "sah", "sai", "sal", "sam", "saq", "sas",
- "sat", "sba", "sbp", "sc", "scn", "sco", "sd", "se",
- "see", "seh", "sel", "sem", "ses", "sg", "sga", "sgn",
- "shi", "shn", "shu", "si", "sid", "sio", "sit",
- "sk", "sl", "sla", "sm", "sma", "smi", "smj", "smn",
- "sms", "sn", "snk", "so", "sog", "son", "sq", "sr",
- "srn", "srr", "ss", "ssa", "ssy", "st", "su", "suk",
- "sus", "sux", "sv", "sw", "swb", "swc", "syc", "syr",
- "ta", "tai", "te", "tem", "teo", "ter", "tet", "tg",
- "th", "ti", "tig", "tiv", "tk", "tkl", "tl", "tlh",
- "tli", "tmh", "tn", "to", "tog", "tpi", "tr", "trv",
- "ts", "tsi", "tt", "tum", "tup", "tut", "tvl", "tw",
- "twq", "ty", "tyv", "tzm",
- "udm", "ug", "uga", "uk", "umb", "und", "ur", "uz",
- "vai", "ve", "vi", "vo", "vot", "vun",
- "wa", "wae", "wak", "wal", "war", "was", "wen", "wo",
- "xal", "xh", "xog",
- "yao", "yap", "yav", "ybb", "yi", "yo", "ypk", "yue",
- "za", "zap", "zbl", "zen", "zh", "znd", "zu", "zun",
+ "aa", "ab", "ace", "ach", "ada", "ady", "ae", "aeb",
+ "af", "afh", "agq", "ain", "ak", "akk", "akz", "ale",
+ "aln", "alt", "am", "an", "ang", "anp", "ar", "arc",
+ "arn", "aro", "arp", "arq", "ars", "arw", "ary", "arz", "as",
+ "asa", "ase", "ast", "av", "avk", "awa", "ay", "az",
+ "ba", "bal", "ban", "bar", "bas", "bax", "bbc", "bbj",
+ "be", "bej", "bem", "bew", "bez", "bfd", "bfq", "bg",
+ "bgc", "bgn", "bho", "bi", "bik", "bin", "bjn", "bkm", "bla",
+ "bm", "bn", "bo", "bpy", "bqi", "br", "bra", "brh",
+ "brx", "bs", "bss", "bua", "bug", "bum", "byn", "byv",
+ "ca", "cad", "car", "cay", "cch", "ccp", "ce", "ceb", "cgg",
+ "ch", "chb", "chg", "chk", "chm", "chn", "cho", "chp",
+ "chr", "chy", "ckb", "co", "cop", "cps", "cr", "crh",
+ "cs", "csb", "cu", "cv", "cy",
+ "da", "dak", "dar", "dav", "day", "de", "del", "den",
+ "dgr", "din", "dje", "doi", "dra", "dsb", "dua", "dum",
+ "dv", "dyo", "dyu", "dz", "dzg",
+ "ebu", "ee", "efi", "egy", "eka", "el", "elx", "en",
+ "enm", "eo", "es", "et", "eu", "ewo",
+ "fa", "fan", "fat", "ff", "fi", "fil", "fiu", "fj",
+ "fo", "fon", "fr", "frm", "fro", "frr", "frs", "fur",
+ "fy",
+ "ga", "gaa", "gay", "gba", "gd", "gem", "gez", "gil",
+ "gl", "gmh", "gn", "goh", "gon", "gor", "got", "grb",
+ "grc", "gsw", "gu", "guz", "gv", "gwi",
+ "ha", "hai", "haw", "he", "hi", "hil", "him", "hit",
+ "hmn", "ho", "hr", "hsb", "ht", "hu", "hup", "hy",
+ "hz",
+ "ia", "iba", "ibb", "id", "ie", "ig", "ii", "ijo",
+ "ik", "ilo", "inc", "ine", "inh", "io", "ira", "iro",
+ "is", "it", "iu",
+ "ja", "jbo", "jgo", "jmc", "jpr", "jrb", "jv",
+ "ka", "kaa", "kab", "kac", "kaj", "kam", "kar", "kaw",
+ "kbd", "kbl", "kcg", "kde", "kea", "kfo", "kg", "kha",
+ "khi", "kho", "khq", "ki", "kj", "kk", "kkj", "kl",
+ "kln", "km", "kmb", "kn", "ko", "kok", "kos", "kpe",
+ "kr", "krc", "krl", "kro", "kru", "ks", "ksb", "ksf",
+ "ksh", "ku", "kum", "kut", "kv", "kw", "ky",
+ "la", "lad", "lag", "lah", "lam", "lb", "lez", "lg",
+ "li", "lkt", "ln", "lo", "lol", "loz", "lt", "lu",
+ "lua", "lui", "lun", "luo", "lus", "luy", "lv",
+ "mad", "maf", "mag", "mai", "mak", "man", "map", "mas",
+ "mde", "mdf", "mdr", "men", "mer", "mfe", "mg", "mga",
+ "mgh", "mgo", "mh", "mi", "mic", "min", "mis", "mk",
+ "mkh", "ml", "mn", "mnc", "mni", "mno", "moh",
+ "mos", "mr", "ms", "mt", "mua", "mul", "mun", "mus",
+ "mwl", "mwr", "my", "mye", "myn", "myv",
+ "na", "nah", "nai", "nap", "naq", "nb", "nd", "nds",
+ "ne", "new", "ng", "nia", "nic", "niu", "nl", "nmg",
+ "nn", "nnh", "no", "nog", "non", "nqo", "nr", "nso",
+ "nub", "nus", "nv", "nwc", "ny", "nym", "nyn", "nyo",
+ "nzi",
+ "oc", "oj", "om", "or", "os", "osa", "ota", "oto",
+ "pa", "paa", "pag", "pal", "pam", "pap", "pau", "peo",
+ "phi", "phn", "pi", "pl", "pon", "pra", "pro", "ps",
+ "pt",
+ "qu",
+ "raj", "rap", "rar", "rm", "rn", "ro", "roa", "rof",
+ "rom", "ru", "rup", "rw", "rwk",
+ "sa", "sad", "sah", "sai", "sal", "sam", "saq", "sas",
+ "sat", "sba", "sbp", "sc", "scn", "sco", "sd", "se",
+ "see", "seh", "sel", "sem", "ses", "sg", "sga", "sgn",
+ "shi", "shn", "shu", "si", "sid", "sio", "sit",
+ "sk", "sl", "sla", "sm", "sma", "smi", "smj", "smn",
+ "sms", "sn", "snk", "so", "sog", "son", "sq", "sr",
+ "srn", "srr", "ss", "ssa", "ssy", "st", "su", "suk",
+ "sus", "sux", "sv", "sw", "swb", "syc", "syr",
+ "ta", "tai", "te", "tem", "teo", "ter", "tet", "tg",
+ "th", "ti", "tig", "tiv", "tk", "tkl", "tlh",
+ "tli", "tmh", "tn", "to", "tog", "tpi", "tr", "trv",
+ "ts", "tsi", "tt", "tum", "tup", "tut", "tvl", "tw",
+ "twq", "ty", "tyv", "tzm",
+ "udm", "ug", "uga", "uk", "umb", "und", "ur", "uz",
+ "vai", "ve", "vi", "vo", "vot", "vun",
+ "wa", "wae", "wak", "wal", "war", "was", "wen", "wo",
+ "xal", "xh", "xog",
+ "yao", "yap", "yav", "ybb", "yi", "yo", "ypk", "yue",
+ "za", "zap", "zbl", "zen", "zh", "znd", "zu", "zun",
"zxx", "zza" };
-
+
private static final String[] _replacementLanguages = {
- "id", "he", "yi", "jv", "sr", /* replacement language codes */
+ "id", "he", "yi", "jv", "ro", "sr", /* replacement language codes */
};
-
+
private static final String[] _obsoleteLanguages = {
- "in", "iw", "ji", "jw", "sh", /* obsolete language codes */
+ "in", "iw", "ji", "jw", "mo", "sh", /* obsolete language codes */
};
-
+
/* This list MUST contain a three-letter code for every two-letter code in the
list above, and they MUST ne in the same order (i.e., the same language must
be in the same place in both lists)! */
private static final String[] _languages3 = {
- "aar", "abk", "ace", "ach", "ada", "ady", "ave", "afr",
- "afa", "afh", "agq", "ain", "aka", "akk", "ale", "alg",
- "alt", "amh", "arg", "ang", "anp", "apa", "ara", "arc",
- "arn", "arp", "ars", "art", "arw", "asm", "asa", "ast",
- "ath", "aus", "ava", "awa", "aym", "aze",
- "bak", "bad", "bai", "bal", "ban", "bas", "bat", "bax",
- "bbj", "bel", "bej", "bem", "ber", "bez", "bfd", "bul",
- "bih", "bho", "bis", "bik", "bin", "bkm", "bla", "bam",
- "ben", "bnt", "bod", "bre", "bra", "brx", "bos", "bss",
- "btk", "bua", "bug", "bum", "byn", "byv",
- "cat", "cad", "cai", "car", "cau", "cay", "cch", "che",
- "ceb", "cel", "cgg", "cha", "chb", "chg", "chk", "chm",
- "chn", "cho", "chp", "chr", "chy", "ckb", "cmc", "cos",
- "cop", "cpe", "cpf", "cpp", "cre", "crh", "crp", "ces",
- "csb", "chu", "cus", "chv", "cym",
- "dan", "dak", "dar", "dav", "day", "deu", "del", "den",
- "dgr", "din", "dje", "doi", "dra", "dsb", "dua", "dum",
- "div", "dyo", "dyu", "dzo", "dzg",
- "ebu", "ewe", "efi", "egy", "eka", "ell", "elx", "eng",
- "enm", "epo", "spa", "est", "eus", "ewo",
- "fas", "fan", "fat", "ful", "fin", "fil", "fiu", "fij",
- "fao", "fon", "fra", "frm", "fro", "frr", "frs", "fur",
- "fry",
- "gle", "gaa", "gay", "gba", "gla", "gem", "gez", "gil",
- "glg", "gmh", "grn", "goh", "gon", "gor", "got", "grb",
- "grc", "gsw", "guj", "guz", "glv", "gwi",
- "hau", "hai", "haw", "heb", "hin", "hil", "him", "hit",
- "hmn", "hmo", "hrv", "hsb", "hat", "hun", "hup", "hye",
- "her",
- "ina", "iba", "ibb", "ind", "ile", "ibo", "iii", "ijo",
- "ipk", "ilo", "inc", "ine", "inh", "ido", "ira", "iro",
- "isl", "ita", "iku",
- "jpn", "jbo", "jgo", "jmc", "jpr", "jrb", "jav",
- "kat", "kaa", "kab", "kac", "kaj", "kam", "kar", "kaw",
- "kbd", "kbl", "kcg", "kde", "kea", "kfo", "kon", "kha",
- "khi", "kho", "khq", "kik", "kua", "kaz", "kkj", "kal",
- "kln", "khm", "kmb", "kan", "kor", "kok", "kos", "kpe",
- "kau", "krc", "krl", "kro", "kru", "kas", "ksb", "ksf",
- "ksh", "kur", "kum", "kut", "kom", "cor", "kir",
- "lat", "lad", "lag", "lah", "lam", "ltz", "lez", "lug",
- "lim", "lkt", "lin", "lao", "lol", "loz", "lit", "lub",
- "lua", "lui", "lun", "luo", "lus", "luy", "lav",
- "mad", "maf", "mag", "mai", "mak", "man", "map", "mas",
- "mde", "mdf", "mdr", "men", "mer", "mfe", "mlg", "mga",
- "mgh", "mgo", "mah", "mri", "mic", "min", "mis", "mkd",
- "mkh", "mal", "mon", "mnc", "mni", "mno", "mol", "moh",
- "mos", "mar", "msa", "mlt", "mua", "mul", "mun", "mus",
- "mwl", "mwr", "mya", "mye", "myn", "myv",
- "nau", "nah", "nai", "nap", "naq", "nob", "nde", "nds",
- "nep", "new", "ndo", "nia", "nic", "niu", "nld", "nmg",
- "nno", "nnh", "nor", "nog", "non", "nqo", "nbl", "nso",
- "nub", "nus", "nav", "nwc", "nya", "nym", "nyn", "nyo",
- "nzi",
- "oci", "oji", "orm", "ori", "oss", "osa", "ota", "oto",
- "pan", "paa", "pag", "pal", "pam", "pap", "pau", "peo",
- "phi", "phn", "pli", "pol", "pon", "pra", "pro", "pus",
- "por",
- "que",
- "raj", "rap", "rar", "roh", "run", "ron", "roa", "rof",
- "rom", "rus", "rup", "kin", "rwk",
- "san", "sad", "sah", "sai", "sal", "sam", "saq", "sas",
- "sat", "sba", "sbp", "srd", "scn", "sco", "snd", "sme",
- "see", "seh", "sel", "sem", "ses", "sag", "sga", "sgn",
- "shi", "shn", "shu", "sin", "sid", "sio", "sit",
- "slk", "slv", "sla", "smo", "sma", "smi", "smj", "smn",
- "sms", "sna", "snk", "som", "sog", "son", "sqi", "srp",
- "srn", "srr", "ssw", "ssa", "ssy", "sot", "sun", "suk",
- "sus", "sux", "swe", "swa", "swb", "swc", "syc", "syr",
- "tam", "tai", "tel", "tem", "teo", "ter", "tet", "tgk",
- "tha", "tir", "tig", "tiv", "tuk", "tkl", "tgl", "tlh",
- "tli", "tmh", "tsn", "ton", "tog", "tpi", "tur", "trv",
- "tso", "tsi", "tat", "tum", "tup", "tut", "tvl", "twi",
- "twq", "tah", "tyv", "tzm",
- "udm", "uig", "uga", "ukr", "umb", "und", "urd", "uzb",
- "vai", "ven", "vie", "vol", "vot", "vun",
- "wln", "wae", "wak", "wal", "war", "was", "wen", "wol",
- "xal", "xho", "xog",
- "yao", "yap", "yav", "ybb", "yid", "yor", "ypk", "yue",
- "zha", "zap", "zbl", "zen", "zho", "znd", "zul", "zun",
+ "aar", "abk", "ace", "ach", "ada", "ady", "ave", "aeb",
+ "afr", "afh", "agq", "ain", "aka", "akk", "akz", "ale",
+ "aln", "alt", "amh", "arg", "ang", "anp", "ara", "arc",
+ "arn", "aro", "arp", "arq", "ars", "arw", "ary", "arz", "asm",
+ "asa", "ase", "ast", "ava", "avk", "awa", "aym", "aze",
+ "bak", "bal", "ban", "bar", "bas", "bax", "bbc", "bbj",
+ "bel", "bej", "bem", "bew", "bez", "bfd", "bfq", "bul",
+ "bgc", "bgn", "bho", "bis", "bik", "bin", "bjn", "bkm", "bla",
+ "bam", "ben", "bod", "bpy", "bqi", "bre", "bra", "brh",
+ "brx", "bos", "bss", "bua", "bug", "bum", "byn", "byv",
+ "cat", "cad", "car", "cay", "cch", "ccp", "che", "ceb", "cgg",
+ "cha", "chb", "chg", "chk", "chm", "chn", "cho", "chp",
+ "chr", "chy", "ckb", "cos", "cop", "cps", "cre", "crh",
+ "ces", "csb", "chu", "chv", "cym",
+ "dan", "dak", "dar", "dav", "day", "deu", "del", "den",
+ "dgr", "din", "dje", "doi", "dra", "dsb", "dua", "dum",
+ "div", "dyo", "dyu", "dzo", "dzg",
+ "ebu", "ewe", "efi", "egy", "eka", "ell", "elx", "eng",
+ "enm", "epo", "spa", "est", "eus", "ewo",
+ "fas", "fan", "fat", "ful", "fin", "fil", "fiu", "fij",
+ "fao", "fon", "fra", "frm", "fro", "frr", "frs", "fur",
+ "fry",
+ "gle", "gaa", "gay", "gba", "gla", "gem", "gez", "gil",
+ "glg", "gmh", "grn", "goh", "gon", "gor", "got", "grb",
+ "grc", "gsw", "guj", "guz", "glv", "gwi",
+ "hau", "hai", "haw", "heb", "hin", "hil", "him", "hit",
+ "hmn", "hmo", "hrv", "hsb", "hat", "hun", "hup", "hye",
+ "her",
+ "ina", "iba", "ibb", "ind", "ile", "ibo", "iii", "ijo",
+ "ipk", "ilo", "inc", "ine", "inh", "ido", "ira", "iro",
+ "isl", "ita", "iku",
+ "jpn", "jbo", "jgo", "jmc", "jpr", "jrb", "jav",
+ "kat", "kaa", "kab", "kac", "kaj", "kam", "kar", "kaw",
+ "kbd", "kbl", "kcg", "kde", "kea", "kfo", "kon", "kha",
+ "khi", "kho", "khq", "kik", "kua", "kaz", "kkj", "kal",
+ "kln", "khm", "kmb", "kan", "kor", "kok", "kos", "kpe",
+ "kau", "krc", "krl", "kro", "kru", "kas", "ksb", "ksf",
+ "ksh", "kur", "kum", "kut", "kom", "cor", "kir",
+ "lat", "lad", "lag", "lah", "lam", "ltz", "lez", "lug",
+ "lim", "lkt", "lin", "lao", "lol", "loz", "lit", "lub",
+ "lua", "lui", "lun", "luo", "lus", "luy", "lav",
+ "mad", "maf", "mag", "mai", "mak", "man", "map", "mas",
+ "mde", "mdf", "mdr", "men", "mer", "mfe", "mlg", "mga",
+ "mgh", "mgo", "mah", "mri", "mic", "min", "mis", "mkd",
+ "mkh", "mal", "mon", "mnc", "mni", "mno", "moh",
+ "mos", "mar", "msa", "mlt", "mua", "mul", "mun", "mus",
+ "mwl", "mwr", "mya", "mye", "myn", "myv",
+ "nau", "nah", "nai", "nap", "naq", "nob", "nde", "nds",
+ "nep", "new", "ndo", "nia", "nic", "niu", "nld", "nmg",
+ "nno", "nnh", "nor", "nog", "non", "nqo", "nbl", "nso",
+ "nub", "nus", "nav", "nwc", "nya", "nym", "nyn", "nyo",
+ "nzi",
+ "oci", "oji", "orm", "ori", "oss", "osa", "ota", "oto",
+ "pan", "paa", "pag", "pal", "pam", "pap", "pau", "peo",
+ "phi", "phn", "pli", "pol", "pon", "pra", "pro", "pus",
+ "por",
+ "que",
+ "raj", "rap", "rar", "roh", "run", "ron", "roa", "rof",
+ "rom", "rus", "rup", "kin", "rwk",
+ "san", "sad", "sah", "sai", "sal", "sam", "saq", "sas",
+ "sat", "sba", "sbp", "srd", "scn", "sco", "snd", "sme",
+ "see", "seh", "sel", "sem", "ses", "sag", "sga", "sgn",
+ "shi", "shn", "shu", "sin", "sid", "sio", "sit",
+ "slk", "slv", "sla", "smo", "sma", "smi", "smj", "smn",
+ "sms", "sna", "snk", "som", "sog", "son", "sqi", "srp",
+ "srn", "srr", "ssw", "ssa", "ssy", "sot", "sun", "suk",
+ "sus", "sux", "swe", "swa", "swb", "syc", "syr",
+ "tam", "tai", "tel", "tem", "teo", "ter", "tet", "tgk",
+ "tha", "tir", "tig", "tiv", "tuk", "tkl", "tlh",
+ "tli", "tmh", "tsn", "ton", "tog", "tpi", "tur", "trv",
+ "tso", "tsi", "tat", "tum", "tup", "tut", "tvl", "twi",
+ "twq", "tah", "tyv", "tzm",
+ "udm", "uig", "uga", "ukr", "umb", "und", "urd", "uzb",
+ "vai", "ven", "vie", "vol", "vot", "vun",
+ "wln", "wae", "wak", "wal", "war", "was", "wen", "wol",
+ "xal", "xho", "xog",
+ "yao", "yap", "yav", "ybb", "yid", "yor", "ypk", "yue",
+ "zha", "zap", "zbl", "zen", "zho", "znd", "zul", "zun",
"zxx", "zza" };
private static final String[] _obsoleteLanguages3 = {
- /* "in", "iw", "ji", "jw", "sh", */
- "ind", "heb", "yid", "jaw", "srp",
+ /* "in", "iw", "ji", "jw", "mo", "sh", */
+ "ind", "heb", "yid", "jaw", "ron", "srp"
};
/* ZR(ZAR) is now CD(COD) and FX(FXX) is PS(PSE) as per
@@ -340,13 +339,13 @@
"BJ", "BL", "BM", "BN", "BO", "BQ", "BR", "BS", "BT", "BV",
"BW", "BY", "BZ", "CA", "CC", "CD", "CF", "CG",
"CH", "CI", "CK", "CL", "CM", "CN", "CO", "CR",
- "CU", "CV", "CW", "CX", "CY", "CZ", "DE", "DJ", "DK",
- "DM", "DO", "DZ", "EC", "EE", "EG", "EH", "ER",
+ "CU", "CV", "CW", "CX", "CY", "CZ", "DE", "DG", "DJ", "DK",
+ "DM", "DO", "DZ", "EA", "EC", "EE", "EG", "EH", "ER",
"ES", "ET", "FI", "FJ", "FK", "FM", "FO", "FR",
"GA", "GB", "GD", "GE", "GF", "GG", "GH", "GI", "GL",
"GM", "GN", "GP", "GQ", "GR", "GS", "GT", "GU",
"GW", "GY", "HK", "HM", "HN", "HR", "HT", "HU",
- "ID", "IE", "IL", "IM", "IN", "IO", "IQ", "IR", "IS",
+ "IC", "ID", "IE", "IL", "IM", "IN", "IO", "IQ", "IR", "IS",
"IT", "JE", "JM", "JO", "JP", "KE", "KG", "KH", "KI",
"KM", "KN", "KP", "KR", "KW", "KY", "KZ", "LA",
"LB", "LC", "LI", "LK", "LR", "LS", "LT", "LU",
@@ -363,22 +362,22 @@
"TK", "TL", "TM", "TN", "TO", "TR", "TT", "TV",
"TW", "TZ", "UA", "UG", "UM", "US", "UY", "UZ",
"VA", "VC", "VE", "VG", "VI", "VN", "VU", "WF",
- "WS", "YE", "YT", "ZA", "ZM", "ZW" };
-
+ "WS", "XK", "YE", "YT", "ZA", "ZM", "ZW" };
+
private static final String[] _deprecatedCountries = {
"AN", "BU", "CS", "DD", "DY", "FX", "HV", "NH", "RH", "SU", "TP", "UK", "VD", "YD", "YU", "ZR" /* deprecated country list */
};
-
+
private static final String[] _replacementCountries = {
/* "AN", "BU", "CS", "DD", "DY", "FX", "HV", "NH", "RH", "SU", "TP", "UK", "VD", "YD", "YU", "ZR" */
- "CW", "MM", "RS", "DE", "BJ", "FR", "BF", "VU", "ZW", "RU", "TL", "GB", "VN", "YE", "RS", "CD" /* replacement country codes */
+ "CW", "MM", "RS", "DE", "BJ", "FR", "BF", "VU", "ZW", "RU", "TL", "GB", "VN", "YE", "RS", "CD" /* replacement country codes */
};
-
+
/* this table is used for three letter codes */
private static final String[] _obsoleteCountries = {
"AN", "BU", "CS", "FX", "RO", "SU", "TP", "YD", "YU", "ZR", /* obsolete country codes */
};
-
+
/* This list MUST contain a three-letter code for every two-letter code in
the above list, and they MUST be listed in the same order! */
private static final String[] _countries3 = {
@@ -394,10 +393,10 @@
"BWA", "BLR", "BLZ", "CAN", "CCK", "COD", "CAF", "COG",
/* "CH", "CI", "CK", "CL", "CM", "CN", "CO", "CR", */
"CHE", "CIV", "COK", "CHL", "CMR", "CHN", "COL", "CRI",
- /* "CU", "CV", "CW", "CX", "CY", "CZ", "DE", "DJ", "DK", */
- "CUB", "CPV", "CUW", "CXR", "CYP", "CZE", "DEU", "DJI", "DNK",
- /* "DM", "DO", "DZ", "EC", "EE", "EG", "EH", "ER", */
- "DMA", "DOM", "DZA", "ECU", "EST", "EGY", "ESH", "ERI",
+ /* "CU", "CV", "CW", "CX", "CY", "CZ", "DE", "DG", "DJ", "DK", */
+ "CUB", "CPV", "CUW", "CXR", "CYP", "CZE", "DEU", "DGA", "DJI", "DNK",
+ /* "DM", "DO", "DZ", "EA", "EC", "EE", "EG", "EH", "ER", */
+ "DMA", "DOM", "DZA", "XEA", "ECU", "EST", "EGY", "ESH", "ERI",
/* "ES", "ET", "FI", "FJ", "FK", "FM", "FO", "FR", */
"ESP", "ETH", "FIN", "FJI", "FLK", "FSM", "FRO", "FRA",
/* "GA", "GB", "GD", "GE", "GF", "GG", "GH", "GI", "GL", */
@@ -406,8 +405,8 @@
"GMB", "GIN", "GLP", "GNQ", "GRC", "SGS", "GTM", "GUM",
/* "GW", "GY", "HK", "HM", "HN", "HR", "HT", "HU", */
"GNB", "GUY", "HKG", "HMD", "HND", "HRV", "HTI", "HUN",
- /* "ID", "IE", "IL", "IM", "IN", "IO", "IQ", "IR", "IS" */
- "IDN", "IRL", "ISR", "IMN", "IND", "IOT", "IRQ", "IRN", "ISL",
+ /* "IC", "ID", "IE", "IL", "IM", "IN", "IO", "IQ", "IR", "IS" */
+ "XIC", "IDN", "IRL", "ISR", "IMN", "IND", "IOT", "IRQ", "IRN", "ISL",
/* "IT", "JE", "JM", "JO", "JP", "KE", "KG", "KH", "KI", */
"ITA", "JEY", "JAM", "JOR", "JPN", "KEN", "KGZ", "KHM", "KIR",
/* "KM", "KN", "KP", "KR", "KW", "KY", "KZ", "LA", */
@@ -440,9 +439,9 @@
"TWN", "TZA", "UKR", "UGA", "UMI", "USA", "URY", "UZB",
/* "VA", "VC", "VE", "VG", "VI", "VN", "VU", "WF", */
"VAT", "VCT", "VEN", "VGB", "VIR", "VNM", "VUT", "WLF",
- /* "WS", "YE", "YT", "ZA", "ZM", "ZW", */
- "WSM", "YEM", "MYT", "ZAF", "ZMB", "ZWE" };
-
+ /* "WS", "XK", "YE", "YT", "ZA", "ZM", "ZW", */
+ "WSM", "XKK", "YEM", "MYT", "ZAF", "ZMB", "ZWE" };
+
private static final String[] _obsoleteCountries3 = {
/* "AN", "BU", "CS", "FX", "RO", "SU", "TP", "YD", "YU", "ZR" */
"ANT", "BUR", "SCG", "FXX", "ROM", "SUN", "TMP", "YMD", "YUG", "ZAR",
diff --git a/android_icu4j/src/main/java/android/icu/impl/Normalizer2Impl.java b/android_icu4j/src/main/java/android/icu/impl/Normalizer2Impl.java
index 8cf25cc..af129db 100644
--- a/android_icu4j/src/main/java/android/icu/impl/Normalizer2Impl.java
+++ b/android_icu4j/src/main/java/android/icu/impl/Normalizer2Impl.java
@@ -915,8 +915,8 @@
public static final int MIN_YES_YES_WITH_CC=0xfe02;
public static final int JAMO_VT=0xfe00;
public static final int MIN_NORMAL_MAYBE_YES=0xfc00;
- public static final int JAMO_L=2; // offset=1 hasCompBoundaryAfter=FALSE
- public static final int INERT=1; // offset=0 hasCompBoundaryAfter=TRUE
+ public static final int JAMO_L=2; // offset=1 hasCompBoundaryAfter=false
+ public static final int INERT=1; // offset=0 hasCompBoundaryAfter=true
// norm16 bit 0 is comp-boundary-after.
public static final int HAS_COMP_BOUNDARY_AFTER=1;
diff --git a/android_icu4j/src/main/java/android/icu/impl/OlsonTimeZone.java b/android_icu4j/src/main/java/android/icu/impl/OlsonTimeZone.java
index 1d200a9..4673179 100644
--- a/android_icu4j/src/main/java/android/icu/impl/OlsonTimeZone.java
+++ b/android_icu4j/src/main/java/android/icu/impl/OlsonTimeZone.java
@@ -307,9 +307,9 @@
public boolean useDaylightTime() {
// If DST was observed in 1942 (for example) but has never been
// observed from 1943 to the present, most clients will expect
- // this method to return FALSE. This method determines whether
+ // this method to return false. This method determines whether
// DST is in use in the current year (at any point in the year)
- // and returns TRUE if so.
+ // and returns true if so.
long current = System.currentTimeMillis();
if (finalZone != null && current >= finalStartMillis) {
@@ -322,7 +322,7 @@
long start = Grego.fieldsToDay(fields[0], 0, 1) * SECONDS_PER_DAY;
long limit = Grego.fieldsToDay(fields[0] + 1, 0, 1) * SECONDS_PER_DAY;
- // Return TRUE if DST is observed at any time during the current
+ // Return true if DST is observed at any time during the current
// year.
for (int i = 0; i < transitionCount; ++i) {
if (transitionTimes64[i] >= limit) {
@@ -351,7 +351,7 @@
}
}
- // Return TRUE if DST is observed at any future time
+ // Return true if DST is observed at any future time
long currentSec = Grego.floorDivide(current, Grego.MILLIS_PER_SECOND);
int trsIdx = transitionCount - 1;
if (dstOffsetAt(trsIdx) != 0) {
diff --git a/android_icu4j/src/main/java/android/icu/impl/PatternProps.java b/android_icu4j/src/main/java/android/icu/impl/PatternProps.java
index 5bdb37c..a3f3941 100644
--- a/android_icu4j/src/main/java/android/icu/impl/PatternProps.java
+++ b/android_icu4j/src/main/java/android/icu/impl/PatternProps.java
@@ -21,7 +21,7 @@
* Pattern_Syntax includes some unassigned code points.
* <p>
* [:Pattern_White_Space:] =
- * [\u0009-\u000D\ \u0085\u200E\u200F\u2028\u2029]
+ * [\u0009-\u000D\ \u0020\u0085\u200E\u200F\u2028\u2029]
* <p>
* [:Pattern_Syntax:] =
* [!-/\:-@\[-\^`\{-~\u00A1-\u00A7\u00A9\u00AB\u00AC\u00AE
diff --git a/android_icu4j/src/main/java/android/icu/impl/Trie2.java b/android_icu4j/src/main/java/android/icu/impl/Trie2.java
index c3a07d5..4942126 100644
--- a/android_icu4j/src/main/java/android/icu/impl/Trie2.java
+++ b/android_icu4j/src/main/java/android/icu/impl/Trie2.java
@@ -196,8 +196,8 @@
* @param is an InputStream containing the serialized form
* of a UTrie, version 1 or 2. The stream must support mark() and reset().
* The position of the input stream will be left unchanged.
- * @param littleEndianOk If FALSE, only big-endian (Java native) serialized forms are recognized.
- * If TRUE, little-endian serialized forms are recognized as well.
+ * @param littleEndianOk If false, only big-endian (Java native) serialized forms are recognized.
+ * If true, little-endian serialized forms are recognized as well.
* @return the Trie version of the serialized form, or 0 if it is not
* recognized as a serialized UTrie
* @throws IOException on errors in reading from the input stream.
diff --git a/android_icu4j/src/main/java/android/icu/impl/Trie2Writable.java b/android_icu4j/src/main/java/android/icu/impl/Trie2Writable.java
index d7c7971..2bf5129 100644
--- a/android_icu4j/src/main/java/android/icu/impl/Trie2Writable.java
+++ b/android_icu4j/src/main/java/android/icu/impl/Trie2Writable.java
@@ -358,7 +358,7 @@
}
/**
- * initialValue is ignored if overwrite=TRUE
+ * initialValue is ignored if overwrite=true
* @hide draft / provisional / internal are hidden on Android
*/
private void fillBlock(int block, /*UChar32*/ int start, /*UChar32*/ int limit,
@@ -381,7 +381,7 @@
/**
* Set a value in a range of code points [start..end].
* All code points c with start<=c<=end will get the value if
- * overwrite is TRUE or if the old value is the initial value.
+ * overwrite is true or if the old value is the initial value.
*
* @param start the first code point to get the value
* @param end the last code point to get the value (inclusive)
@@ -514,7 +514,7 @@
* Set the values from a Trie2.Range.
*
* All code points within the range will get the value if
- * overwrite is TRUE or if the old value is the initial value.
+ * overwrite is true or if the old value is the initial value.
*
* Ranges with the lead surrogate flag set will set the alternate
* lead-surrogate values in the Trie, rather than the code point values.
@@ -731,7 +731,7 @@
*
* The compaction
* - removes blocks that are identical with earlier ones
- * - overlaps adjacent blocks as much as possible (if overlap==TRUE)
+ * - overlaps adjacent blocks as much as possible (if overlap==true)
* - moves blocks in steps of the data granularity
* - moves and overlaps blocks that overlap with multiple values in the overlap region
*
diff --git a/android_icu4j/src/main/java/android/icu/impl/UCharacterProperty.java b/android_icu4j/src/main/java/android/icu/impl/UCharacterProperty.java
index 7acb688..822f399 100644
--- a/android_icu4j/src/main/java/android/icu/impl/UCharacterProperty.java
+++ b/android_icu4j/src/main/java/android/icu/impl/UCharacterProperty.java
@@ -314,7 +314,7 @@
*/
private static final boolean isgraphPOSIX(int c) {
/* \p{space}\p{gc=Control} == \p{gc=Z}\p{Control} */
- /* comparing ==0 returns FALSE for the categories mentioned */
+ /* comparing ==0 returns false for the categories mentioned */
return (getMask(UCharacter.getType(c))&
(GC_CC_MASK|GC_CS_MASK|GC_CN_MASK|GC_Z_MASK))
==0;
@@ -824,7 +824,7 @@
public int getIntPropertyMaxValue(int which) {
if(which<UProperty.INT_START) {
if(UProperty.BINARY_START<=which && which<UProperty.BINARY_LIMIT) {
- return 1; // maximum TRUE for all binary properties
+ return 1; // maximum true for all binary properties
}
} else if(which<UProperty.INT_LIMIT) {
return intProps[which-UProperty.INT_START].getMaxValue(which);
diff --git a/android_icu4j/src/main/java/android/icu/impl/Utility.java b/android_icu4j/src/main/java/android/icu/impl/Utility.java
index 110663d..5445e30 100644
--- a/android_icu4j/src/main/java/android/icu/impl/Utility.java
+++ b/android_icu4j/src/main/java/android/icu/impl/Utility.java
@@ -1564,8 +1564,8 @@
* Escapes one unprintable code point using <backslash>uxxxx notation
* for U+0000 to U+FFFF and <backslash>Uxxxxxxxx for U+10000 and
* above. If the character is printable ASCII, then do nothing
- * and return FALSE. Otherwise, append the escaped notation and
- * return TRUE.
+ * and return false. Otherwise, append the escaped notation and
+ * return true.
*/
public static <T extends Appendable> boolean escapeUnprintable(T result, int c) {
if (isUnprintable(c)) {
diff --git a/android_icu4j/src/main/java/android/icu/impl/breakiter/CjkBreakEngine.java b/android_icu4j/src/main/java/android/icu/impl/breakiter/CjkBreakEngine.java
index 0a41f08..3a801ce 100644
--- a/android_icu4j/src/main/java/android/icu/impl/breakiter/CjkBreakEngine.java
+++ b/android_icu4j/src/main/java/android/icu/impl/breakiter/CjkBreakEngine.java
@@ -12,12 +12,14 @@
import static android.icu.impl.CharacterIteration.DONE32;
import static android.icu.impl.CharacterIteration.current32;
import static android.icu.impl.CharacterIteration.next32;
+import static android.icu.impl.CharacterIteration.previous32;
import java.io.IOException;
import java.text.CharacterIterator;
import java.util.HashSet;
import android.icu.impl.Assert;
+import android.icu.impl.ICUConfig;
import android.icu.impl.ICUData;
import android.icu.text.Normalizer;
import android.icu.text.UnicodeSet;
@@ -34,6 +36,8 @@
private UnicodeSet fClosePunctuationSet;
private DictionaryMatcher fDictionary = null;
private HashSet<String> fSkipSet;
+ private MlBreakEngine fMlBreakEngine;
+ private boolean isCj = false;
public CjkBreakEngine(boolean korean) throws IOException {
fHangulWordSet = new UnicodeSet("[\\uac00-\\ud7a3]");
@@ -50,9 +54,16 @@
if (korean) {
setCharacters(fHangulWordSet);
} else { //Chinese and Japanese
+ isCj = true;
UnicodeSet cjSet = new UnicodeSet("[[:Han:][:Hiragana:][:Katakana:]\\u30fc\\uff70\\uff9e\\uff9f]");
setCharacters(cjSet);
- initializeJapanesePhraseParamater();
+ if (Boolean.parseBoolean(
+ ICUConfig.get("android.icu.impl.breakiter.useMLPhraseBreaking", "false"))) {
+ fMlBreakEngine = new MlBreakEngine(fDigitOrOpenPunctuationOrAlphabetSet,
+ fClosePunctuationSet);
+ } else {
+ initializeJapanesePhraseParamater();
+ }
}
}
@@ -154,6 +165,15 @@
charPositions[numCodePts] = index;
}
}
+ // Use ML phrase breaking
+ if (Boolean.parseBoolean(
+ ICUConfig.get("android.icu.impl.breakiter.useMLPhraseBreaking", "false"))) {
+ // PhraseBreaking is supported in ja and ko; MlBreakEngine only supports ja.
+ if (isPhraseBreaking && isCj) {
+ return fMlBreakEngine.divideUpRange(inText, startPos, endPos, text,
+ numCodePts, charPositions, foundBreaks);
+ }
+ }
// From here on out, do the algorithm. Note that our indices
// refer to indices within the normalized string.
@@ -244,17 +264,18 @@
t_boundary[numBreaks] = numCodePts;
numBreaks++;
int prevIdx = numCodePts;
- int codeUnitIdx = 0, length = 0;
+ int codeUnitIdx = 0, prevCodeUnitIdx = 0, length = 0;
for (int i = prev[numCodePts]; i > 0; i = prev[i]) {
codeUnitIdx = prenormstr.offsetByCodePoints(0, i);
- length = prevIdx - i;
+ prevCodeUnitIdx = prenormstr.offsetByCodePoints(0, prevIdx);
+ length = prevCodeUnitIdx - codeUnitIdx;
prevIdx = i;
String pattern = getPatternFromText(text, s, codeUnitIdx, length);
// Keep the breakpoint if the pattern is not in the fSkipSet and continuous Katakana
// characters don't occur.
- text.setIndex(codeUnitIdx - 1);
+ text.setIndex(codeUnitIdx);
if (!fSkipSet.contains(pattern)
- && (!isKatakana(current32(text)) || !isKatakana(next32(text)))) {
+ && (!isKatakana(current32(text)) || !isKatakana(previous32(text)))) {
t_boundary[numBreaks] = i;
numBreaks++;
}
@@ -278,10 +299,11 @@
// In phrase breaking, there has to be a breakpoint between Cj character and close
// punctuation.
// E.g.[携帯電話]正しい選択 -> [携帯▁電話]▁正しい▁選択 -> breakpoint between ] and 正
+ inText.setIndex(pos);
if (pos > previous) {
if (pos != startPos
|| (isPhraseBreaking && pos > 0
- && fClosePunctuationSet.contains(inText.setIndex(pos - 1)))) {
+ && fClosePunctuationSet.contains(previous32(inText)))) {
foundBreaks.push(charPositions[t_boundary[i]] + startPos);
correctedNumBreaks++;
}
@@ -296,7 +318,9 @@
// E.g. 乗車率90%程度だろうか -> 乗車▁率▁90%▁程度だろうか -> breakpoint between 率 and 9
// E.g. しかもロゴがUnicode! -> しかも▁ロゴが▁Unicode!-> breakpoint between が and U
if (isPhraseBreaking) {
- if (!fDigitOrOpenPunctuationOrAlphabetSet.contains(inText.setIndex(endPos))) {
+ inText.setIndex(endPos);
+ int current = current32(inText);
+ if (current != DONE32 && !fDigitOrOpenPunctuationOrAlphabetSet.contains(current)) {
foundBreaks.pop();
correctedNumBreaks--;
}
@@ -313,11 +337,11 @@
private String getPatternFromText(CharacterIterator text, StringBuffer sb, int start,
int length) {
sb.setLength(0);
- if(length > 0) {
+ if (length > 0) {
text.setIndex(start);
- sb.appendCodePoint(current32(text));
- for (int j = 1; j < length; j++) {
- sb.appendCodePoint(next32(text));
+ sb.append(text.current());
+ for (int i = 1; i < length; i++) {
+ sb.append(text.next());
}
}
return sb.toString();
diff --git a/android_icu4j/src/main/java/android/icu/impl/breakiter/MlBreakEngine.java b/android_icu4j/src/main/java/android/icu/impl/breakiter/MlBreakEngine.java
new file mode 100644
index 0000000..957c8d3
--- /dev/null
+++ b/android_icu4j/src/main/java/android/icu/impl/breakiter/MlBreakEngine.java
@@ -0,0 +1,287 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+// © 2022 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html
+
+package android.icu.impl.breakiter;
+
+import static android.icu.impl.CharacterIteration.DONE32;
+import static android.icu.impl.CharacterIteration.current32;
+import static android.icu.impl.CharacterIteration.next32;
+import static android.icu.impl.CharacterIteration.previous32;
+
+import android.icu.impl.ICUData;
+import android.icu.text.UnicodeSet;
+import android.icu.util.UResourceBundle;
+import android.icu.util.UResourceBundleIterator;
+
+import java.text.CharacterIterator;
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.HashMap;
+
+enum ModelIndex {
+ kUWStart(0), kBWStart(6), kTWStart(9);
+ private final int value;
+
+ private ModelIndex(int value) {
+ this.value = value;
+ }
+
+ public int getValue() {
+ return value;
+ }
+}
+
+/**
+ * @hide Only a subset of ICU is exposed in Android
+ */
+public class MlBreakEngine {
+ // {UW1, UW2, ... UW6, BW1, ... BW3, TW1, TW2, ... TW4} 6+3+4= 13
+ private static final int MAX_FEATURE = 13;
+ private UnicodeSet fDigitOrOpenPunctuationOrAlphabetSet;
+ private UnicodeSet fClosePunctuationSet;
+ private List<HashMap<String, Integer>> fModel;
+ private int fNegativeSum;
+
+ /**
+ * Constructor for Chinese and Japanese phrase breaking.
+ *
+ * @param digitOrOpenPunctuationOrAlphabetSet An unicode set with the digit and open punctuation
+ * and alphabet.
+ * @param closePunctuationSet An unicode set with the close punctuation.
+ */
+ public MlBreakEngine(UnicodeSet digitOrOpenPunctuationOrAlphabetSet,
+ UnicodeSet closePunctuationSet) {
+ fDigitOrOpenPunctuationOrAlphabetSet = digitOrOpenPunctuationOrAlphabetSet;
+ fClosePunctuationSet = closePunctuationSet;
+ fModel = new ArrayList<HashMap<String, Integer>>(MAX_FEATURE);
+ for (int i = 0; i < MAX_FEATURE; i++) {
+ fModel.add(new HashMap<String, Integer>());
+ }
+ fNegativeSum = 0;
+ loadMLModel();
+ }
+
+ /**
+ * Divide up a range of characters handled by this break engine.
+ *
+ * @param inText An input text.
+ * @param startPos The start index of the input text.
+ * @param endPos The end index of the input text.
+ * @param inString A input string normalized from inText from startPos to endPos
+ * @param codePointLength The number of code points of inString
+ * @param charPositions A map that transforms inString's code point index to code unit index.
+ * @param foundBreaks A list to store the breakpoint.
+ * @return The number of breakpoints
+ */
+ public int divideUpRange(CharacterIterator inText, int startPos, int endPos,
+ CharacterIterator inString, int codePointLength, int[] charPositions,
+ DictionaryBreakEngine.DequeI foundBreaks) {
+ if (startPos >= endPos) {
+ return 0;
+ }
+ ArrayList<Integer> boundary = new ArrayList<Integer>(codePointLength);
+ String inputStr = transform(inString);
+ // The ML algorithm groups six char and evaluates whether the 4th char is a breakpoint.
+ // In each iteration, it evaluates the 4th char and then moves forward one char like
+ // sliding window. Initially, the first six values in the indexList are
+ // [-1, -1, 0, 1, 2, 3]. After moving forward, finally the last six values in the indexList
+ // are [length-4, length-3, length-2, length-1, -1, -1]. The "+4" here means four extra
+ // "-1".
+ int indexSize = codePointLength + 4;
+ int indexList[] = new int[indexSize];
+ int numCodeUnits = initIndexList(inString, indexList, codePointLength);
+
+ // Add a break for the start.
+ boundary.add(0, 0);
+
+ for (int idx = 0; idx + 1 < codePointLength; idx++) {
+ evaluateBreakpoint(inputStr, indexList, idx, numCodeUnits, boundary);
+ if (idx + 4 < codePointLength) {
+ indexList[idx + 6] = numCodeUnits;
+ numCodeUnits += Character.charCount(next32(inString));
+ }
+ }
+
+ // Add a break for the end if there is not one there already.
+ if (boundary.get(boundary.size() - 1) != codePointLength) {
+ boundary.add(codePointLength);
+ }
+
+ int correctedNumBreaks = 0;
+ int previous = -1;
+ int numBreaks = boundary.size();
+ for (int i = 0; i < numBreaks; i++) {
+ int pos = charPositions[boundary.get(i)] + startPos;
+ // In phrase breaking, there has to be a breakpoint between Cj character and close
+ // punctuation.
+ // E.g.[携帯電話]正しい選択 -> [携帯▁電話]▁正しい▁選択 -> breakpoint between ] and 正
+ inText.setIndex(pos);
+ if (pos > previous) {
+ if (pos != startPos
+ || (pos > 0
+ && fClosePunctuationSet.contains(previous32(inText)))) {
+ foundBreaks.push(pos);
+ correctedNumBreaks++;
+ }
+ }
+ previous = pos;
+ }
+
+ if (!foundBreaks.isEmpty() && foundBreaks.peek() == endPos) {
+ // In phrase breaking, there has to be a breakpoint between Cj character and
+ // the number/open punctuation.
+ // E.g. る文字「そうだ、京都」->る▁文字▁「そうだ、▁京都」-> breakpoint between 字 and「
+ // E.g. 乗車率90%程度だろうか -> 乗車▁率▁90%▁程度だろうか -> breakpoint between 率 and 9
+ // E.g. しかもロゴがUnicode! -> しかも▁ロゴが▁Unicode!-> breakpoint between が and U
+ inText.setIndex(endPos);
+ int current = current32(inText);
+ if (current != DONE32 && !fDigitOrOpenPunctuationOrAlphabetSet.contains(current)) {
+ foundBreaks.pop();
+ correctedNumBreaks--;
+ }
+
+ }
+ if (!foundBreaks.isEmpty()) {
+ inText.setIndex(foundBreaks.peek());
+ }
+ return correctedNumBreaks;
+ }
+
+ /**
+ * Transform a CharacterIterator into a String.
+ */
+ private String transform(CharacterIterator inString) {
+ StringBuilder sb = new StringBuilder();
+ inString.setIndex(0);
+ for (char c = inString.first(); c != CharacterIterator.DONE; c = inString.next()) {
+ sb.append(c);
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Evaluate whether the breakpointIdx is a potential breakpoint.
+ *
+ * @param inputStr An input string to be segmented.
+ * @param indexList A code unit index list of the inputStr.
+ * @param startIdx The start index of the indexList.
+ * @param numCodeUnits The current code unit boundary of the indexList.
+ * @param boundary A list including the index of the breakpoint.
+ */
+ private void evaluateBreakpoint(String inputStr, int[] indexList, int startIdx,
+ int numCodeUnits, ArrayList<Integer> boundary) {
+ int start = 0, end = 0;
+ int score = fNegativeSum;
+
+ for (int i = 0; i < 6; i++) {
+ // UW1 ~ UW6
+ start = startIdx + i;
+ if (indexList[start] != -1) {
+ end = (indexList[start + 1] != -1) ? indexList[start + 1] : numCodeUnits;
+ score += fModel.get(ModelIndex.kUWStart.getValue() + i).getOrDefault(
+ inputStr.substring(indexList[start], end), 0);
+ }
+ }
+ for (int i = 0; i < 3; i++) {
+ // BW1 ~ BW3
+ start = startIdx + i + 1;
+ if (indexList[start] != -1 && indexList[start + 1] != -1) {
+ end = (indexList[start + 2] != -1) ? indexList[start + 2] : numCodeUnits;
+ score += fModel.get(ModelIndex.kBWStart.getValue() + i).getOrDefault(
+ inputStr.substring(indexList[start], end), 0);
+ }
+ }
+ for (int i = 0; i < 4; i++) {
+ // TW1 ~ TW4
+ start = startIdx + i;
+ if (indexList[start] != -1
+ && indexList[start + 1] != -1
+ && indexList[start + 2] != -1) {
+ end = (indexList[start + 3] != -1) ? indexList[start + 3] : numCodeUnits;
+ score += fModel.get(ModelIndex.kTWStart.getValue() + i).getOrDefault(
+ inputStr.substring(indexList[start], end), 0);
+ }
+ }
+ if (score > 0) {
+ boundary.add(startIdx + 1);
+ }
+ }
+
+ /**
+ * Initialize the index list from the input string.
+ *
+ * @param inString An input string to be segmented.
+ * @param indexList A code unit index list of the inString.
+ * @param codePointLength The number of code points of the input string
+ * @return The number of the code units of the first six characters in inString.
+ */
+ private int initIndexList(CharacterIterator inString, int[] indexList, int codePointLength) {
+ int index = 0;
+ inString.setIndex(index);
+ Arrays.fill(indexList, -1);
+ if (codePointLength > 0) {
+ indexList[2] = 0;
+ index += Character.charCount(current32(inString));
+ if (codePointLength > 1) {
+ indexList[3] = index;
+ index += Character.charCount(next32(inString));
+ if (codePointLength > 2) {
+ indexList[4] = index;
+ index += Character.charCount(next32(inString));
+ if (codePointLength > 3) {
+ indexList[5] = index;
+ index += Character.charCount(next32(inString));
+ }
+ }
+ }
+ }
+ return index;
+ }
+
+ /**
+ * Load the machine learning's model file.
+ */
+ private void loadMLModel() {
+ int index = 0;
+ UResourceBundle rb = UResourceBundle.getBundleInstance(ICUData.ICU_BRKITR_BASE_NAME,
+ "jaml");
+ initKeyValue(rb, "UW1Keys", "UW1Values", fModel.get(index++));
+ initKeyValue(rb, "UW2Keys", "UW2Values", fModel.get(index++));
+ initKeyValue(rb, "UW3Keys", "UW3Values", fModel.get(index++));
+ initKeyValue(rb, "UW4Keys", "UW4Values", fModel.get(index++));
+ initKeyValue(rb, "UW5Keys", "UW5Values", fModel.get(index++));
+ initKeyValue(rb, "UW6Keys", "UW6Values", fModel.get(index++));
+ initKeyValue(rb, "BW1Keys", "BW1Values", fModel.get(index++));
+ initKeyValue(rb, "BW2Keys", "BW2Values", fModel.get(index++));
+ initKeyValue(rb, "BW3Keys", "BW3Values", fModel.get(index++));
+ initKeyValue(rb, "TW1Keys", "TW1Values", fModel.get(index++));
+ initKeyValue(rb, "TW2Keys", "TW2Values", fModel.get(index++));
+ initKeyValue(rb, "TW3Keys", "TW3Values", fModel.get(index++));
+ initKeyValue(rb, "TW4Keys", "TW4Values", fModel.get(index++));
+ fNegativeSum /= 2;
+ }
+
+ /**
+ * In the machine learning's model file, specify the name of the key and value to load the
+ * corresponding feature and its score.
+ *
+ * @param rb A RedouceBundle corresponding to the model file.
+ * @param keyName The kay name in the model file.
+ * @param valueName The value name in the model file.
+ * @param map A HashMap to store the pairs of the feature and its score.
+ */
+ private void initKeyValue(UResourceBundle rb, String keyName, String valueName,
+ HashMap<String, Integer> map) {
+ int idx = 0;
+ UResourceBundle keyBundle = rb.get(keyName);
+ UResourceBundle valueBundle = rb.get(valueName);
+ int[] value = valueBundle.getIntVector();
+ UResourceBundleIterator iterator = keyBundle.getIterator();
+ while (iterator.hasNext()) {
+ fNegativeSum -= value[idx];
+ map.put(iterator.nextString(), value[idx++]);
+ }
+ }
+}
diff --git a/android_icu4j/src/main/java/android/icu/impl/coll/CollationDataBuilder.java b/android_icu4j/src/main/java/android/icu/impl/coll/CollationDataBuilder.java
index 80a8487..ae1ac74 100644
--- a/android_icu4j/src/main/java/android/icu/impl/coll/CollationDataBuilder.java
+++ b/android_icu4j/src/main/java/android/icu/impl/coll/CollationDataBuilder.java
@@ -55,7 +55,7 @@
trie = null;
ce32s = new UVector32();
ce64s = new UVector64();
- conditionalCE32s = new ArrayList<ConditionalCE32>();
+ conditionalCE32s = new ArrayList<>();
modified = false;
fastLatinEnabled = false;
fastLatinBuilder = null;
@@ -386,13 +386,25 @@
* When fetching CEs from the builder, the contexts are built into their runtime form
* so that the normal collation implementation can process them.
* The result is cached in the list head. It is reset when the contexts are modified.
+ * All of these builtCE32 are invalidated by clearContexts(),
+ * via incrementing the contextsEra.
*/
int builtCE32;
/**
+ * The "era" of building intermediate contexts when the above builtCE32 was set.
+ * When the array of cached, temporary contexts overflows, then clearContexts()
+ * removes them all and invalidates the builtCE32 that used to point to built tries.
+ */
+ int era = -1;
+ /**
* Index of the next ConditionalCE32.
* Negative for the end of the list.
*/
int next;
+ // Note: We could create a separate class for all of the contextual mappings for
+ // a code point, with the builtCE32, the era, and a list of the actual mappings.
+ // The class that represents one mapping would then not need to
+ // store those fields in each element.
}
protected int getCE32FromOffsetCE32(boolean fromBase, int c, int ce32) {
@@ -416,7 +428,7 @@
for(int i = 0; i < length; ++i) {
if(ce32 == ce32s.elementAti(i)) { return i; }
}
- ce32s.addElement(ce32);
+ ce32s.addElement(ce32);
return length;
}
@@ -990,19 +1002,16 @@
protected void clearContexts() {
contexts.setLength(0);
- UnicodeSetIterator iter = new UnicodeSetIterator(contextChars);
- while(iter.next()) {
- assert(iter.codepoint != UnicodeSetIterator.IS_STRING);
- int ce32 = trie.get(iter.codepoint);
- assert(isBuilderContextCE32(ce32));
- getConditionalCE32ForCE32(ce32).builtCE32 = Collation.NO_CE32;
- }
+ // Incrementing the contexts build "era" invalidates all of the builtCE32
+ // from before this clearContexts() call.
+ // Simpler than finding and resetting all of those fields.
+ ++contextsEra;
}
protected void buildContexts() {
// Ignore abandoned lists and the cached builtCE32,
// and build all contexts from scratch.
- contexts.setLength(0);
+ clearContexts();
UnicodeSetIterator iter = new UnicodeSetIterator(contextChars);
while(iter.next()) {
assert(iter.codepoint != UnicodeSetIterator.IS_STRING);
@@ -1022,8 +1031,12 @@
assert(!head.hasContext());
// The list head must be followed by one or more nodes that all do have context.
assert(head.next >= 0);
- CharsTrieBuilder prefixBuilder = new CharsTrieBuilder();
- CharsTrieBuilder contractionBuilder = new CharsTrieBuilder();
+ CharsTrieBuilder prefixBuilder = null;
+ CharsTrieBuilder contractionBuilder = null;
+ // This outer loop goes from each prefix to the next.
+ // For each prefix it finds the one or more same-prefix entries (firstCond..lastCond).
+ // If there are multiple suffixes for the same prefix,
+ // then an inner loop builds a contraction trie for them.
for(ConditionalCE32 cond = head;; cond = getConditionalCE32(cond.next)) {
// After the list head, the prefix or suffix can be empty, but not both.
assert(cond == head || cond.hasContext());
@@ -1032,11 +1045,22 @@
String prefixString = prefix.toString();
// Collect all contraction suffixes for one prefix.
ConditionalCE32 firstCond = cond;
- ConditionalCE32 lastCond = cond;
- while(cond.next >= 0 &&
- (cond = getConditionalCE32(cond.next)).context.startsWith(prefixString)) {
+ ConditionalCE32 lastCond;
+ do {
lastCond = cond;
- }
+ // Clear the defaultCE32 fields as we go.
+ // They are left over from building a previous version of this list of contexts.
+ //
+ // One of the code paths below may copy a preceding defaultCE32
+ // into its emptySuffixCE32.
+ // If a new suffix has been inserted before what used to be
+ // the firstCond for its prefix, then that previous firstCond could still
+ // contain an outdated defaultCE32 from an earlier buildContext() and
+ // result in an incorrect emptySuffixCE32.
+ // So we reset all defaultCE32 before reading and setting new values.
+ cond.defaultCE32 = Collation.NO_CE32;
+ } while(cond.next >= 0 &&
+ (cond = getConditionalCE32(cond.next)).context.startsWith(prefixString));
int ce32;
int suffixStart = prefixLength + 1; // == prefix.length()
if(lastCond.context.length() == suffixStart) {
@@ -1046,7 +1070,11 @@
cond = lastCond;
} else {
// Build the contractions trie.
- contractionBuilder.clear();
+ if(contractionBuilder == null) {
+ contractionBuilder = new CharsTrieBuilder();
+ } else {
+ contractionBuilder.clear();
+ }
// Entry for an empty suffix, to be stored before the trie.
int emptySuffixCE32 = Collation.NO_CE32; // Will always be set to a real value.
int flags = 0;
@@ -1115,6 +1143,9 @@
} else {
prefix.delete(0, 1); // Remove the length unit.
prefix.reverse();
+ if(prefixBuilder == null) {
+ prefixBuilder = new CharsTrieBuilder();
+ }
prefixBuilder.add(prefix, ce32);
if(cond.next < 0) { break; }
}
@@ -1305,7 +1336,7 @@
return builder.trie.get(jamo);
} else {
ConditionalCE32 cond = builder.getConditionalCE32ForCE32(ce32);
- if(cond.builtCE32 == Collation.NO_CE32) {
+ if(cond.builtCE32 == Collation.NO_CE32 || cond.era != builder.contextsEra) {
// Build the context-sensitive mappings into their runtime form and cache the result.
try {
cond.builtCE32 = builder.buildContext(cond);
@@ -1313,6 +1344,7 @@
builder.clearContexts();
cond.builtCE32 = builder.buildContext(cond);
}
+ cond.era = builder.contextsEra;
builderData.contexts = builder.contexts.toString();
}
return cond.builtCE32;
@@ -1346,6 +1378,13 @@
protected UnicodeSet contextChars = new UnicodeSet();
// Serialized UCharsTrie structures for finalized contexts.
protected StringBuilder contexts = new StringBuilder();
+ /**
+ * The "era" of building intermediate contexts.
+ * When the array of cached, temporary contexts overflows, then clearContexts()
+ * removes them all and invalidates the builtCE32 that used to point to built tries.
+ * See {@link ConditionalCE32#era}.
+ */
+ private int contextsEra = 0;
protected UnicodeSet unsafeBackwardSet = new UnicodeSet();
protected boolean modified;
diff --git a/android_icu4j/src/main/java/android/icu/impl/coll/CollationFCD.java b/android_icu4j/src/main/java/android/icu/impl/coll/CollationFCD.java
index 533faee..e11b39d 100644
--- a/android_icu4j/src/main/java/android/icu/impl/coll/CollationFCD.java
+++ b/android_icu4j/src/main/java/android/icu/impl/coll/CollationFCD.java
@@ -256,7 +256,7 @@
0x200ff800,0xfbc00000,0x3eef,0xe000000,0xff000000,0xfffffc00,0xfffffffb,0x10000000,0x1e2000,0x2000,0x40000000,0x602000,0x18000000,0x400,0x7000000,0xf00,
0x3000000,0x2a00000,0x3c3e0000,0xdf,0x40,0x6800000,0xe0000000,0x300000,0x100000,0x20040000,0x200,0x1800000,0x9fe00001,0xbfff0000,0x7fff,0x10,
0xff800,0xc00,0xc0040,0x800000,0xfff70000,0x31021fd,0x1fff0000,0x1ffe2,0x38000,0x80000000,0xfc00,0x6000000,0x3ff08000,0xc0000000,0x30000,0x1000,
-0x3ffff,0x3800,0x80000,1,0xc19d0000,2,0x400000,0xc0000fd,0x5108000
+0x3ffff,0x3800,0x80000,1,0xc19d0000,2,0x400000,0xc0000fd,0x7108000
};
private static final byte[] tcccIndex={
@@ -398,7 +398,7 @@
0xe0000000,0x300000,0x100000,0x20040000,0x200,0x1800000,0x9fe00001,0xbfff0000,0x7fff,0x10,0xff800,0xc00,0xc0040,0x800000,0xfff70000,0x31021fd,
0xbffffff,0x3ffffff,0x3f3fffff,0xaaff3f3f,0x3fffffff,0x1fdfffff,0xefcfffde,0x1fdc7fff,0x1fff0000,0x1ffe2,0x800,0xc000000,0x4000,0xe000,0x1210,0x50,
0x292,0x333e005,0x333,0xf000,0x3c0f,0x38000,0x80000000,0xfc00,0x55555000,0x36db02a5,0x46100000,0x47900000,0x3ff08000,0xc0000000,0x30000,0x1000,
-0x3ffff,0x3800,0x80000,1,0xc19d0000,2,0x400000,0xc0000fd,0x5108000,0x5f7ffc00,0x7fdb
+0x3ffff,0x3800,0x80000,1,0xc19d0000,2,0x400000,0xc0000fd,0x7108000,0x5f7ffc00,0x7fdb
};
}
diff --git a/android_icu4j/src/main/java/android/icu/impl/number/AffixUtils.java b/android_icu4j/src/main/java/android/icu/impl/number/AffixUtils.java
index 4bacf6d..7f7ffca 100644
--- a/android_icu4j/src/main/java/android/icu/impl/number/AffixUtils.java
+++ b/android_icu4j/src/main/java/android/icu/impl/number/AffixUtils.java
@@ -279,8 +279,7 @@
case TYPE_PLUS_SIGN:
return NumberFormat.Field.SIGN;
case TYPE_APPROXIMATELY_SIGN:
- // TODO: Introduce a new field for the approximately sign?
- return NumberFormat.Field.SIGN;
+ return NumberFormat.Field.APPROXIMATELY_SIGN;
case TYPE_PERCENT:
return NumberFormat.Field.PERCENT;
case TYPE_PERMILLE:
diff --git a/android_icu4j/src/main/java/android/icu/impl/number/CompactData.java b/android_icu4j/src/main/java/android/icu/impl/number/CompactData.java
index 4b33b63..0143cb5 100644
--- a/android_icu4j/src/main/java/android/icu/impl/number/CompactData.java
+++ b/android_icu4j/src/main/java/android/icu/impl/number/CompactData.java
@@ -39,7 +39,7 @@
private byte largestMagnitude;
private boolean isEmpty;
- private static final int COMPACT_MAX_DIGITS = 15;
+ private static final int COMPACT_MAX_DIGITS = 20;
public CompactData() {
patterns = new String[(CompactData.COMPACT_MAX_DIGITS + 1) * StandardPlural.COUNT];
@@ -190,8 +190,10 @@
// Assumes that the keys are always of the form "10000" where the magnitude is the
// length of the key minus one. We expect magnitudes to be less than MAX_DIGITS.
byte magnitude = (byte) (key.length() - 1);
+ if (magnitude >= COMPACT_MAX_DIGITS) {
+ continue;
+ }
byte multiplier = data.multipliers[magnitude];
- assert magnitude < COMPACT_MAX_DIGITS;
// Iterate over the plural variants ("one", "other", etc)
UResource.Table pluralVariantsTable = value.getTable();
diff --git a/android_icu4j/src/main/java/android/icu/impl/number/DecimalQuantity.java b/android_icu4j/src/main/java/android/icu/impl/number/DecimalQuantity.java
index b73fc87..2fcb091 100644
--- a/android_icu4j/src/main/java/android/icu/impl/number/DecimalQuantity.java
+++ b/android_icu4j/src/main/java/android/icu/impl/number/DecimalQuantity.java
@@ -233,6 +233,12 @@
public String toPlainString();
/**
+ * Returns the string using ASCII digits and using exponential notation for non-zero
+ * exponents, following the UTS 35 specification for plural rule samples.
+ */
+ public String toExponentString();
+
+ /**
* Like clone, but without the restrictions of the Cloneable interface clone.
*
* @return A copy of this instance which can be mutated without affecting this instance.
diff --git a/android_icu4j/src/main/java/android/icu/impl/number/DecimalQuantity_AbstractBCD.java b/android_icu4j/src/main/java/android/icu/impl/number/DecimalQuantity_AbstractBCD.java
index 046024e..6e24f96 100644
--- a/android_icu4j/src/main/java/android/icu/impl/number/DecimalQuantity_AbstractBCD.java
+++ b/android_icu4j/src/main/java/android/icu/impl/number/DecimalQuantity_AbstractBCD.java
@@ -1124,6 +1124,48 @@
}
@Override
+ public String toExponentString() {
+ StringBuilder sb = new StringBuilder();
+ toExponentString(sb);
+ return sb.toString();
+ }
+
+ private void toExponentString(StringBuilder result) {
+ assert(!isApproximate);
+ if (isNegative()) {
+ result.append('-');
+ }
+
+ int upper = scale + precision - 1;
+ int lower = scale;
+ if (upper < lReqPos - 1) {
+ upper = lReqPos - 1;
+ }
+ if (lower > rReqPos) {
+ lower = rReqPos;
+ }
+
+ int p = upper;
+ if (p < 0) {
+ result.append('0');
+ }
+ for (; p >= 0; p--) {
+ result.append((char) ('0' + getDigitPos(p - scale)));
+ }
+ if (lower < 0) {
+ result.append('.');
+ }
+ for(; p >= lower; p--) {
+ result.append((char) ('0' + getDigitPos(p - scale)));
+ }
+
+ if (exponent != 0) {
+ result.append('c');
+ result.append(exponent);
+ }
+ }
+
+ @Override
public boolean equals(Object other) {
if (this == other) {
return true;
diff --git a/android_icu4j/src/main/java/android/icu/impl/number/DecimalQuantity_DualStorageBCD.java b/android_icu4j/src/main/java/android/icu/impl/number/DecimalQuantity_DualStorageBCD.java
index e224259..76087ca 100644
--- a/android_icu4j/src/main/java/android/icu/impl/number/DecimalQuantity_DualStorageBCD.java
+++ b/android_icu4j/src/main/java/android/icu/impl/number/DecimalQuantity_DualStorageBCD.java
@@ -90,6 +90,54 @@
return new DecimalQuantity_DualStorageBCD(this);
}
+ /**
+ * Returns a DecimalQuantity after parsing the input string.
+ *
+ * @param s The String to parse
+ */
+ public static DecimalQuantity fromExponentString(String num) {
+ if (num.contains("e") || num.contains("c")
+ || num.contains("E") || num.contains("C")) {
+ int ePos = num.lastIndexOf('e');
+ if (ePos < 0) {
+ ePos = num.lastIndexOf('c');
+ }
+ if (ePos < 0) {
+ ePos = num.lastIndexOf('E');
+ }
+ if (ePos < 0) {
+ ePos = num.lastIndexOf('C');
+ }
+ int expNumPos = ePos + 1;
+ String exponentStr = num.substring(expNumPos);
+ int exponent = Integer.parseInt(exponentStr);
+
+ String fractionStr = num.substring(0, ePos);
+ BigDecimal fraction = new BigDecimal(fractionStr);
+
+ DecimalQuantity_DualStorageBCD dq = new DecimalQuantity_DualStorageBCD(fraction);
+ int numFracDigit = getVisibleFractionCount(fractionStr);
+ dq.setMinFraction(numFracDigit);
+ dq.adjustExponent(exponent);
+
+ return dq;
+ } else {
+ int numFracDigit = getVisibleFractionCount(num);
+ DecimalQuantity_DualStorageBCD dq = new DecimalQuantity_DualStorageBCD(new BigDecimal(num));
+ dq.setMinFraction(numFracDigit);
+ return dq;
+ }
+ }
+
+ private static int getVisibleFractionCount(String value) {
+ int decimalPos = value.indexOf('.') + 1;
+ if (decimalPos == 0) {
+ return 0;
+ } else {
+ return value.length() - decimalPos;
+ }
+ }
+
@Override
protected byte getDigitPos(int position) {
if (usingBytes) {
diff --git a/android_icu4j/src/main/java/android/icu/impl/number/LongNameHandler.java b/android_icu4j/src/main/java/android/icu/impl/number/LongNameHandler.java
index 34f5595..39bb3e9 100644
--- a/android_icu4j/src/main/java/android/icu/impl/number/LongNameHandler.java
+++ b/android_icu4j/src/main/java/android/icu/impl/number/LongNameHandler.java
@@ -297,11 +297,6 @@
// Continue: fall back to short
}
- // TODO(ICU-13353): The ICU4J fallback mechanism works differently:
- // investigate? Without this code, unit tests do fail:
- key.setLength(0);
- key.append("unitsShort/");
- key.append(subKey);
unitsBundle.getAllItemsWithFallback(key.toString(), sink);
}
@@ -336,6 +331,21 @@
}
}
+ private static final class AliasSink extends UResource.Sink {
+ String replacement;
+
+ @Override
+ public void put(UResource.Key key, UResource.Value value, boolean noFallback) {
+ UResource.Table aliasTable = value.getTable();
+ for (int i = 0; aliasTable.getKeyAndValue(i, key, value); ++i) {
+ String keyString = key.toString();
+ if (keyString.equals("replacement")) {
+ this.replacement = value.toString();
+ }
+ }
+ }
+ }
+
// NOTE: outArray MUST have at least ARRAY_LENGTH entries. No bounds checking is performed.
static void getMeasureData(
ULocale locale,
@@ -353,12 +363,23 @@
subKey.append(unit.getType());
subKey.append("/");
+ // If the unit is an alias, replace it is identifier with the replacement.
+ String unitSubType = unit.getSubtype();
+ ICUResourceBundle metadataResource = (ICUResourceBundle) UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME, "metadata");
+ AliasSink aliasSink = new AliasSink();
+
+ metadataResource.getAllItemsWithFallbackNoFail("alias/unit/" + unitSubType, aliasSink);
+ if (aliasSink.replacement != null) {
+ unitSubType = aliasSink.replacement;
+ }
+
+
// Map duration-year-person, duration-week-person, etc. to duration-year, duration-week, ...
// TODO(ICU-20400): Get duration-*-person data properly with aliases.
- if (unit.getSubtype() != null && unit.getSubtype().endsWith("-person")) {
- subKey.append(unit.getSubtype(), 0, unit.getSubtype().length() - 7);
+ if (unitSubType != null && unitSubType.endsWith("-person")) {
+ subKey.append(unitSubType, 0, unitSubType.length() - 7);
} else {
- subKey.append(unit.getSubtype());
+ subKey.append(unitSubType);
}
if (width != UnitWidth.FULL_NAME) {
@@ -457,11 +478,6 @@
}
}
- // TODO(icu-units#28): this code is not exercised by unit tests yet.
- // Also, what's the fallback mechanism mentioned in ICU-13353?
- key.setLength(0);
- key.append("unitsShort/compound/");
- key.append(compoundKey);
try {
return resource.getStringWithFallback(key.toString());
} catch (MissingResourceException e) {
diff --git a/android_icu4j/src/main/java/android/icu/impl/number/UsagePrefsHandler.java b/android_icu4j/src/main/java/android/icu/impl/number/UsagePrefsHandler.java
index a9a299a..683eaa6 100644
--- a/android_icu4j/src/main/java/android/icu/impl/number/UsagePrefsHandler.java
+++ b/android_icu4j/src/main/java/android/icu/impl/number/UsagePrefsHandler.java
@@ -24,8 +24,7 @@
assert parent != null;
this.fParent = parent;
- this.fUnitsRouter =
- new UnitsRouter(MeasureUnitImpl.forIdentifier(inputUnit.getIdentifier()), locale.getCountry(), usage);
+ this.fUnitsRouter = new UnitsRouter(MeasureUnitImpl.forIdentifier(inputUnit.getIdentifier()), locale, usage);
}
/**
diff --git a/android_icu4j/src/main/java/android/icu/impl/personname/FieldModifierImpl.java b/android_icu4j/src/main/java/android/icu/impl/personname/FieldModifierImpl.java
new file mode 100644
index 0000000..6d0918c
--- /dev/null
+++ b/android_icu4j/src/main/java/android/icu/impl/personname/FieldModifierImpl.java
@@ -0,0 +1,157 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+// © 2022 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html
+package android.icu.impl.personname;
+
+import java.util.Locale;
+import java.util.StringTokenizer;
+
+import android.icu.lang.UCharacter;
+import android.icu.text.BreakIterator;
+import android.icu.text.CaseMap;
+import android.icu.text.PersonName;
+import android.icu.text.SimpleFormatter;
+
+/**
+ * Parent class for classes that implement field-modifier behavior.
+ */
+abstract class FieldModifierImpl {
+ public abstract String modifyField(String fieldValue);
+
+ public static FieldModifierImpl forName(PersonName.FieldModifier modifierID, PersonNameFormatterImpl formatterImpl) {
+ switch (modifierID) {
+ case INFORMAL:
+ return NOOP_MODIFIER;
+ case PREFIX:
+ return NULL_MODIFIER;
+ case CORE:
+ return NOOP_MODIFIER;
+ case ALL_CAPS:
+ return new AllCapsModifier(formatterImpl.getLocale());
+ case INITIAL_CAP:
+ return new InitialCapModifier(formatterImpl.getLocale());
+ case INITIAL:
+ return new InitialModifier(formatterImpl.getInitialPattern(), formatterImpl.getInitialSequencePattern());
+ case MONOGRAM:
+ return MONOGRAM_MODIFIER;
+ default:
+ throw new IllegalArgumentException("Invalid modifier ID " + modifierID);
+ }
+ }
+
+ /**
+ * A field modifier that just returns the field value unmodified. This is used to implement the default
+ * behavior of the "informal" and "core" modifiers ("real" informal or core variants have to be supplied or
+ * calculated by the PersonName object).
+ */
+ private static final FieldModifierImpl NOOP_MODIFIER = new FieldModifierImpl() {
+ @Override
+ public String modifyField(String fieldValue) {
+ return fieldValue;
+ }
+ };
+
+ /**
+ * A field modifier that just returns the empty string. This is used to implement the default behavior of the
+ * "prefix" modifier ("real" prefix variants have to be supplied to calculated by the PersonName object).
+ */
+ private static final FieldModifierImpl NULL_MODIFIER = new FieldModifierImpl() {
+ @Override
+ public String modifyField(String fieldValue) {
+ return "";
+ }
+ };
+
+ /**
+ * A field modifier that returns the field value converted to ALL CAPS. This is the default behavior
+ * for the "allCaps" modifier.
+ */
+ private static class AllCapsModifier extends FieldModifierImpl {
+ private final Locale locale;
+
+ public AllCapsModifier(Locale locale) {
+ this.locale = locale;
+ }
+
+ @Override
+ public String modifyField(String fieldValue) {
+ return UCharacter.toUpperCase(locale, fieldValue);
+ }
+ }
+
+ /**
+ * A field modifier that returns the field value with the first letter of each word capitalized. This is
+ * the default behavior of the "initialCap" modifier.
+ */
+ private static class InitialCapModifier extends FieldModifierImpl {
+ private final Locale locale;
+ private static final CaseMap.Title TO_TITLE_WHOLE_STRING_NO_LOWERCASE = CaseMap.toTitle().wholeString().noLowercase();
+
+ public InitialCapModifier(Locale locale) {
+ this.locale = locale;
+ }
+
+ @Override
+ public String modifyField(String fieldValue) {
+ return TO_TITLE_WHOLE_STRING_NO_LOWERCASE.apply(locale, null, fieldValue);
+ }
+ }
+
+ /**
+ * A field modifier that returns the field value converted into one or more initials. This is the first grapheme
+ * cluster of each word in the field value, modified using the initialPattern/initial resource value from the
+ * locale data, and strung together using the initialPattern/initialSequence resource value from the locale data.
+ * (In English, these patterns put periods after each initial and connect them with spaces.)
+ * This is default behavior of the "initial" modifier.
+ */
+ private static class InitialModifier extends FieldModifierImpl {
+ private final SimpleFormatter initialFormatter;
+ private final SimpleFormatter initialSequenceFormatter;
+
+ public InitialModifier(String initialPattern, String initialSequencePattern) {
+ this.initialFormatter = SimpleFormatter.compile(initialPattern);
+ this.initialSequenceFormatter = SimpleFormatter.compile(initialSequencePattern);
+ }
+
+ @Override
+ public String modifyField(String fieldValue) {
+ String result = null;
+ StringTokenizer tok = new StringTokenizer(fieldValue, " ");
+ while (tok.hasMoreTokens()) {
+ String curInitial = getFirstGrapheme(tok.nextToken());
+ if (result == null) {
+ result = initialFormatter.format(curInitial);
+ } else {
+ result = initialSequenceFormatter.format(result, initialFormatter.format(curInitial));
+ }
+ }
+ return result;
+ }
+ }
+
+ /**
+ * A field modifier that simply returns the first grapheme cluster in the field value.
+ * This is the default implementation of the "monogram" modifier.
+ */
+ private static final FieldModifierImpl MONOGRAM_MODIFIER = new FieldModifierImpl() {
+ @Override
+ public String modifyField(String fieldValue) {
+ return getFirstGrapheme(fieldValue);
+ }
+ };
+
+ /**
+ * A utility function that just returns the first grapheme cluster in the string.
+ */
+ private static String getFirstGrapheme(String s) {
+ // early out if the string is empty to avoid StringIndexOutOfBoundsException
+ if (s.isEmpty()) {
+ return "";
+ }
+
+ // (currently, no locale overrides the grapheme-break rules, so we just use "root" instead of passing in the locale)
+ BreakIterator bi = BreakIterator.getCharacterInstance(Locale.ROOT);
+ bi.setText(s);
+ return s.substring(0, bi.next());
+ }
+}
diff --git a/android_icu4j/src/main/java/android/icu/impl/personname/PersonNameFormatterImpl.java b/android_icu4j/src/main/java/android/icu/impl/personname/PersonNameFormatterImpl.java
new file mode 100644
index 0000000..98473da
--- /dev/null
+++ b/android_icu4j/src/main/java/android/icu/impl/personname/PersonNameFormatterImpl.java
@@ -0,0 +1,330 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+// © 2022 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html
+package android.icu.impl.personname;
+
+import static android.icu.util.UResourceBundle.ARRAY;
+import static android.icu.util.UResourceBundle.STRING;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Set;
+
+import android.icu.impl.ICUData;
+import android.icu.impl.ICUResourceBundle;
+import android.icu.lang.UScript;
+import android.icu.text.PersonName;
+import android.icu.text.PersonNameFormatter;
+import android.icu.util.ULocale;
+import android.icu.util.UResourceBundle;
+
+/**
+ * Actual implementation class for PersonNameFormatter.
+ * @hide Only a subset of ICU is exposed in Android
+ */
+public class PersonNameFormatterImpl {
+ private final Locale locale;
+ private final PersonNamePattern[] gnFirstPatterns;
+ private final PersonNamePattern[] snFirstPatterns;
+ private final Set<String> gnFirstLocales;
+ private final Set<String> snFirstLocales;
+ private final String initialPattern;
+ private final String initialSequencePattern;
+ private final boolean capitalizeSurname;
+ private final String foreignSpaceReplacement;
+ private final boolean formatterLocaleUsesSpaces;
+ private final PersonNameFormatter.Length length;
+ private final PersonNameFormatter.Usage usage;
+ private final PersonNameFormatter.Formality formality;
+ private final Set<PersonNameFormatter.Options> options;
+
+ public PersonNameFormatterImpl(Locale locale,
+ PersonNameFormatter.Length length,
+ PersonNameFormatter.Usage usage,
+ PersonNameFormatter.Formality formality,
+ Set<PersonNameFormatter.Options> options) {
+ // null for `options` is the same as the empty set
+ if (options == null) {
+ options = new HashSet<>();
+ }
+
+ // save off our creation parameters (these are only used if we have to create a second formatter)
+ this.length = length;
+ this.usage = usage;
+ this.formality = formality;
+ this.options = options;
+
+ // load simple property values from the resource bundle (or the options set)
+ ICUResourceBundle rb = (ICUResourceBundle)UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME, locale);
+ this.locale = locale;
+ this.initialPattern = rb.getStringWithFallback("personNames/initialPattern/initial");
+ this.initialSequencePattern = rb.getStringWithFallback("personNames/initialPattern/initialSequence");
+ this.capitalizeSurname = options.contains(PersonNameFormatter.Options.SURNAME_ALLCAPS);
+ this.foreignSpaceReplacement = rb.getStringWithFallback("personNames/foreignSpaceReplacement");
+ this.formatterLocaleUsesSpaces = !LOCALES_THAT_DONT_USE_SPACES.contains(locale.getLanguage());
+
+ // asjust for combinations of parameters that don't make sense in practice
+ if (usage == PersonNameFormatter.Usage.MONOGRAM) {
+ // we don't support SORTING in conjunction with MONOGRAM; if the caller passes in SORTING, remove it from
+ // the options list
+ options.remove(PersonNameFormatter.Options.SORTING);
+ } else if (options.contains(PersonNameFormatter.Options.SORTING)) {
+ // we only support SORTING in conjunction with REFERRING; if the caller passes in ADDRESSING, treat it
+ // the same as REFERRING
+ usage = PersonNameFormatter.Usage.REFERRING;
+ }
+
+ // load the actual formatting patterns-- since we don't know the name order until formatting time (it can be
+ // different for different names), load patterns for both given-first and surname-first names. (If the user has
+ // specified SORTING, we don't need to do this-- we just load the "sorting" patterns and ignore the name's order.)
+ final String RESOURCE_PATH_PREFIX = "personNames/namePattern/";
+ String resourceNameBody = length.toString().toLowerCase() + "-" + usage.toString().toLowerCase() + "-"
+ + formality.toString().toLowerCase();
+ if (!options.contains(PersonNameFormatter.Options.SORTING)) {
+ ICUResourceBundle gnFirstResource = rb.getWithFallback(RESOURCE_PATH_PREFIX + "givenFirst-" + resourceNameBody);
+ ICUResourceBundle snFirstResource = rb.getWithFallback(RESOURCE_PATH_PREFIX + "surnameFirst-" + resourceNameBody);
+
+ gnFirstPatterns = PersonNamePattern.makePatterns(asStringArray(gnFirstResource), this);
+ snFirstPatterns = PersonNamePattern.makePatterns(asStringArray(snFirstResource), this);
+
+ gnFirstLocales = new HashSet<>();
+ Collections.addAll(gnFirstLocales, asStringArray(rb.getWithFallback("personNames/nameOrderLocales/givenFirst")));
+ snFirstLocales = new HashSet<>();
+ Collections.addAll(snFirstLocales, asStringArray(rb.getWithFallback("personNames/nameOrderLocales/surnameFirst")));
+ } else {
+ ICUResourceBundle patternResource = rb.getWithFallback(RESOURCE_PATH_PREFIX + "sorting-" + resourceNameBody);
+
+ gnFirstPatterns = PersonNamePattern.makePatterns(asStringArray(patternResource), this);
+ snFirstPatterns = null;
+ gnFirstLocales = null;
+ snFirstLocales = null;
+ }
+ }
+
+ /**
+ * THIS IS A DUMMY CONSTRUCTOR JUST FOR THE USE OF THE UNIT TESTS TO CHECK SOME OF THE INTERNAL IMPLEMENTATION!
+ */
+ public PersonNameFormatterImpl(Locale locale, String[] patterns) {
+ // first, set dummy values for the other fields
+ snFirstPatterns = null;
+ gnFirstLocales = null;
+ snFirstLocales = null;
+ length = PersonNameFormatter.Length.MEDIUM;
+ usage = PersonNameFormatter.Usage.REFERRING;
+ formality = PersonNameFormatter.Formality.FORMAL;
+ options = Collections.emptySet();
+ initialPattern = "{0}.";
+ initialSequencePattern = "{0} {1}";
+ capitalizeSurname = false;
+ foreignSpaceReplacement = " ";
+ formatterLocaleUsesSpaces = true;
+
+ // then, set values for the fields we actually care about
+ this.locale = locale;
+ gnFirstPatterns = PersonNamePattern.makePatterns(patterns, this);
+
+ }
+
+ public String formatToString(PersonName name) {
+ // TODO: Should probably return a FormattedPersonName object
+
+ // if the formatter is for a language that doesn't use spaces between words and the name is from a language
+ // that does, create a formatter for the NAME'S locale and use THAT to format the name
+ Locale nameLocale = getNameLocale(name);
+ boolean nameLocaleUsesSpaces = !LOCALES_THAT_DONT_USE_SPACES.contains(nameLocale.getLanguage());
+ if (!formatterLocaleUsesSpaces && nameLocaleUsesSpaces) {
+ PersonNameFormatterImpl nativeFormatter = new PersonNameFormatterImpl(nameLocale, this.length,
+ this.usage, this.formality, this.options);
+ String result = nativeFormatter.formatToString(name);
+
+ // BUT, if the name is actually written in the formatter locale's script, replace any spaces in the name
+ // with the foreignSpaceReplacement character
+ if (!foreignSpaceReplacement.equals(" ") && scriptMatchesLocale(result, this.locale)) {
+ result = result.replace(" ", this.foreignSpaceReplacement);
+ }
+ return result;
+ }
+
+ // if we get down to here, we're just doing normal formatting-- if we have both given-first and surname-first
+ // rules, choose which one to use based on the name's locale and preferred field order
+ if (snFirstPatterns == null || nameIsGnFirst(name)) {
+ return getBestPattern(gnFirstPatterns, name).format(name);
+ } else {
+ return getBestPattern(snFirstPatterns, name).format(name);
+ }
+ }
+
+ public Locale getLocale() {
+ return locale;
+ }
+
+ public PersonNameFormatter.Length getLength() { return length; }
+
+ public PersonNameFormatter.Usage getUsage() { return usage; }
+
+ public PersonNameFormatter.Formality getFormality() { return formality; }
+
+ public Set<PersonNameFormatter.Options> getOptions() { return options; }
+
+ public String getInitialPattern() {
+ return initialPattern;
+ }
+
+ public String getInitialSequencePattern() {
+ return initialSequencePattern;
+ }
+
+ public boolean shouldCapitalizeSurname() {
+ return capitalizeSurname;
+ }
+
+ private final Set<String> LOCALES_THAT_DONT_USE_SPACES = new HashSet<>(Arrays.asList("ja", "zh", "th", "yue", "km", "lo"));
+
+ /**
+ * Returns the value of the resource, as a string array.
+ * @param resource An ICUResourceBundle of type STRING or ARRAY. If ARRAY, this function just returns it
+ * as a string array. If STRING, it returns a one-element array containing that string.
+ * @return The resource's value, as an array of Strings.
+ */
+ private String[] asStringArray(ICUResourceBundle resource) {
+ if (resource.getType() == STRING) {
+ return new String[] { resource.getString() };
+ } else if (resource.getType() == ARRAY){
+ return resource.getStringArray();
+ } else {
+ throw new IllegalStateException("Unsupported resource type " + resource.getType());
+ }
+ }
+
+ /**
+ * Returns the field order to use when formatting this name, taking into account the name's preferredOrder
+ * field, as well as the name and formatter's respective locales.
+ * @param name The name to be formatted.
+ * @return If true, use given-first order to format the name; if false, use surname-first order.
+ */
+ private boolean nameIsGnFirst(PersonName name) {
+ // the name can declare its order-- check that first (it overrides any locale-based calculation)
+ Set<PersonName.FieldModifier> modifiers = new HashSet<>();
+ String preferredOrder = name.getFieldValue(PersonName.NameField.PREFERRED_ORDER, modifiers);
+ if (preferredOrder != null) {
+ if (preferredOrder.equals("givenFirst")) {
+ return true;
+ } else if (preferredOrder.equals("surnameFirst")) {
+ return false;
+ } else {
+ throw new IllegalArgumentException("Illegal preferredOrder value " + preferredOrder);
+ }
+ }
+
+ String localeStr = getNameLocale(name).toString();
+ do {
+ if (gnFirstLocales.contains(localeStr)) {
+ return true;
+ } else if (snFirstLocales.contains(localeStr)) {
+ return false;
+ }
+
+ int lastUnderbarPos = localeStr.lastIndexOf("_");
+ if (lastUnderbarPos >= 0) {
+ localeStr = localeStr.substring(0, lastUnderbarPos);
+ } else {
+ localeStr = "root";
+ }
+ } while (!localeStr.equals("root"));
+
+ // should never get here-- "root" should always be in one of the locales
+ return true;
+ }
+
+ private PersonNamePattern getBestPattern(PersonNamePattern[] patterns, PersonName name) {
+ // early out if there's only one pattern
+ if (patterns.length == 1) {
+ return patterns[0];
+ } else {
+ // if there's more than one pattern, return the one that contains the greatest number of fields that
+ // actually have values in `name`. If there's a tie, return the pattern that contains the lowest number
+ // of fields that DON'T have values in `name`.
+ int maxPopulatedFields = 0;
+ int minEmptyFields = Integer.MAX_VALUE;
+ PersonNamePattern bestPattern = null;
+
+ for (PersonNamePattern pattern : patterns) {
+ int populatedFields = pattern.numPopulatedFields(name);
+ int emptyFields = pattern.numEmptyFields(name);
+ if (populatedFields > maxPopulatedFields) {
+ maxPopulatedFields = populatedFields;
+ minEmptyFields = emptyFields;
+ bestPattern = pattern;
+ } else if (populatedFields == maxPopulatedFields && emptyFields < minEmptyFields) {
+ minEmptyFields = emptyFields;
+ bestPattern = pattern;
+ }
+ }
+ return bestPattern;
+ }
+ }
+
+ /**
+ * Internal function to figure out the name's locale when the name doesn't specify it.
+ * (Note that this code assumes that if the locale is specified, it includes a language
+ * code.)
+ * @param name The name for which we need the locale
+ * @return The name's (real or guessed) locale.
+ */
+ private Locale getNameLocale(PersonName name) {
+ // if the name specifies its locale, we can just return it
+ Locale nameLocale = name.getNameLocale();
+ if (nameLocale == null) {
+ // if not, we look at the characters in the name. If their script matches the default script for the formatter's
+ // locale, we use the formatter's locale as the name's locale
+ int formatterScript = UScript.getCodeFromName(ULocale.addLikelySubtags(ULocale.forLocale(locale)).getScript());
+ String givenName = name.getFieldValue(PersonName.NameField.GIVEN, new HashSet<PersonName.FieldModifier>());
+ int nameScript = UScript.INVALID_CODE;
+ for (int i = 0; nameScript == UScript.INVALID_CODE && i < givenName.length(); i++) {
+ // the script of the name is the script of the first character in the name whose script isn't
+ // COMMON or INHERITED
+ int script = UScript.getScript(givenName.charAt(i));
+ if (script != UScript.COMMON && script != UScript.INHERITED) {
+ nameScript = script;
+ }
+ }
+ if (formatterScript == nameScript) {
+ nameLocale = this.locale;
+ } else {
+ // if the name's script is different from the formatter's script, we use addLikelySubtags() to find the
+ // default language for the name's script and use THAT as the name's locale
+ nameLocale = new Locale(ULocale.addLikelySubtags(new ULocale("und_" + UScript.getShortName(nameScript))).getLanguage());
+ }
+ // TODO: This algorithm has a few deficiencies: First, it assumes the script of the string is the script of the first
+ // character in the string that's not COMMON or INHERITED. This won't work well for some languages, such as Japanese,
+ // that use multiple scripts. Doing better would require adding a new getScript(String) method on UScript, which
+ // might be something we want. Second, we only look at the given-name field. This field should always be populated,
+ // but if it isn't, we're stuck. Looking at all the fields requires API on PersonName that we don't need anywhere
+ // else.
+ }
+ return nameLocale;
+ }
+
+ /**
+ * Returns true if the script of `s` is one of the default scripts for `locale`.
+ * This function only checks the script of the first character whose script isn't "common,"
+ * so it probably won't work right on mixed-script strings.
+ */
+ private boolean scriptMatchesLocale(String s, Locale locale) {
+ int[] localeScripts = UScript.getCode(locale);
+ int stringScript = UScript.COMMON;
+ for (int i = 0; stringScript == UScript.COMMON && i < s.length(); i++) {
+ char c = s.charAt(i);
+ stringScript = UScript.getScript(c);
+ }
+
+ for (int localeScript : localeScripts) {
+ if (localeScript == stringScript) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/android_icu4j/src/main/java/android/icu/impl/personname/PersonNamePattern.java b/android_icu4j/src/main/java/android/icu/impl/personname/PersonNamePattern.java
new file mode 100644
index 0000000..d612454
--- /dev/null
+++ b/android_icu4j/src/main/java/android/icu/impl/personname/PersonNamePattern.java
@@ -0,0 +1,276 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+// © 2022 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html
+package android.icu.impl.personname;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.StringTokenizer;
+
+import android.icu.text.PersonName;
+
+/**
+ * A single name formatting pattern, corresponding to a single namePattern element in CLDR.
+ */
+class PersonNamePattern {
+ private String patternText; // for debugging
+ private Element[] patternElements;
+
+ public static PersonNamePattern[] makePatterns(String[] patternText, PersonNameFormatterImpl formatterImpl) {
+ PersonNamePattern[] result = new PersonNamePattern[patternText.length];
+ for (int i = 0; i < patternText.length; i++) {
+ result[i] = new PersonNamePattern(patternText[i], formatterImpl);
+ }
+ return result;
+ }
+
+ private PersonNamePattern(String patternText, PersonNameFormatterImpl formatterImpl) {
+ this.patternText = patternText;
+
+ List<Element> elements = new ArrayList<>();
+ boolean inField = false;
+ boolean inEscape = false;
+ StringBuilder workingString = new StringBuilder();
+ for (int i = 0; i < patternText.length(); i++) {
+ char c = patternText.charAt(i);
+
+ if (inEscape) {
+ workingString.append(c);
+ inEscape = false;
+ } else {
+ switch (c) {
+ case '\\':
+ inEscape = true;
+ break;
+ case '{':
+ if (!inField) {
+ if (workingString.length() > 0) {
+ elements.add(new LiteralText(workingString.toString()));
+ workingString = new StringBuilder();
+ }
+ inField = true;
+ } else {
+ throw new IllegalArgumentException("Nested braces are not allowed in name patterns");
+ }
+ break;
+ case '}':
+ if (inField) {
+ if (workingString.length() > 0) {
+ elements.add(new NameFieldImpl(workingString.toString(), formatterImpl));
+ workingString = new StringBuilder();
+ } else {
+ throw new IllegalArgumentException("No field name inside braces");
+ }
+ inField = false;
+ } else {
+ throw new IllegalArgumentException("Unmatched closing brace in literal text");
+ }
+ break;
+ default:
+ workingString.append(c);
+ }
+ }
+ }
+ if (workingString.length() > 0) {
+ elements.add(new LiteralText(workingString.toString()));
+ }
+ this.patternElements = elements.toArray(new Element[0]);
+ }
+
+ public String format(PersonName name) {
+ StringBuilder result = new StringBuilder();
+ boolean seenLeadingField = false;
+ boolean seenEmptyLeadingField = false;
+ boolean seenEmptyField = false;
+ StringBuilder textBefore = new StringBuilder();
+ StringBuilder textAfter = new StringBuilder();
+
+ // the logic below attempts to implement the following algorithm:
+ // - If one or more fields at the beginning of the name are empty, also skip all literal text
+ // from the beginning of the name up to the first populated field.
+ // - If one or more fields at the end of the name are empty, also skip all literal text from
+ // the last populated field to the end of the name.
+ // - If one or more contiguous fields in the middle of the name are empty, skip the literal text
+ // between them, omit characters from the literal text on either side of the empty fields up to
+ // the first space on either side, and make sure that the resulting literal text doesn't end up
+ // with two spaces in a row.
+ for (Element element : patternElements) {
+ if (element.isLiteral()) {
+ if (seenEmptyLeadingField) {
+ // do nothing; throw away the literal text
+ } else if (seenEmptyField) {
+ textAfter.append(element.format(name));
+ } else {
+ textBefore.append(element.format(name));
+ }
+ } else {
+ String fieldText = element.format(name);
+ if (fieldText == null || fieldText.isEmpty()) {
+ if (!seenLeadingField) {
+ seenEmptyLeadingField = true;
+ textBefore.setLength(0);
+ } else {
+ seenEmptyField = true;
+ textAfter.setLength(0);
+ }
+ } else {
+ seenLeadingField = true;
+ seenEmptyLeadingField = false;
+ if (seenEmptyField) {
+ result.append(coalesce(textBefore, textAfter));
+ result.append(fieldText);
+ seenEmptyField = false;
+ } else {
+ result.append(textBefore);
+ textBefore.setLength(0);
+ result.append(element.format(name));
+ }
+ }
+ }
+ }
+ if (!seenEmptyField) {
+ result.append(textBefore);
+ }
+ return result.toString();
+ }
+
+ public int numPopulatedFields(PersonName name) {
+ int result = 0;
+ for (Element element : patternElements) {
+ result += element.isPopulated(name) ? 1 : 0;
+ }
+ return result;
+ }
+
+ public int numEmptyFields(PersonName name) {
+ int result = 0;
+ for (Element element : patternElements) {
+ result += element.isPopulated(name) ? 0 : 1;
+ }
+ return result;
+ }
+
+ /**
+ * Stitches together the literal text on either side of an omitted field by deleting any
+ * non-whitespace characters immediately neighboring the omitted field and coalescing any
+ * adjacent spaces at the join point down to one.
+ * @param s1 The literal text before the omitted field.
+ * @param s2 The literal text after the omitted field.
+ */
+ private String coalesce(StringBuilder s1, StringBuilder s2) {
+ // get the range of non-whitespace characters at the beginning of s1
+ int p1 = 0;
+ while (p1 < s1.length() && !Character.isWhitespace(s1.charAt(p1))) {
+ ++p1;
+ }
+
+ // get the range of non-whitespace characters at the end of s2
+ int p2 = s2.length() - 1;
+ while (p2 >= 0 && !Character.isWhitespace(s2.charAt(p2))) {
+ --p2;
+ }
+
+ // also include one whitespace character from s1 or, if there aren't
+ // any, one whitespace character from s2
+ if (p1 < s1.length()) {
+ ++p1;
+ } else if (p2 >= 0) {
+ --p2;
+ }
+
+ // concatenate those two ranges to get the coalesced literal text
+ String result = s1.substring(0, p1) + s2.substring(p2 + 1);
+
+ // clear out s1 and s2 (done here to improve readability in format() above))
+ s1.setLength(0);
+ s2.setLength(0);
+
+ return result;
+ }
+
+ /**
+ * A single element in a NamePattern. This is either a name field or a range of literal text.
+ */
+ private interface Element {
+ boolean isLiteral();
+ String format(PersonName name);
+ boolean isPopulated(PersonName name);
+ }
+
+ /**
+ * Literal text from a name pattern.
+ */
+ private static class LiteralText implements Element {
+ private String text;
+
+ public LiteralText(String text) {
+ this.text = text;
+ }
+
+ public boolean isLiteral() {
+ return true;
+ }
+
+ public String format(PersonName name) {
+ return text;
+ }
+
+ public boolean isPopulated(PersonName name) {
+ return false;
+ }
+ }
+
+ /**
+ * An actual name field in a NamePattern (i.e., the stuff represented in the pattern by text
+ * in braces). This class actually handles fetching the value for the field out of a
+ * PersonName object and applying any modifiers to it.
+ */
+ private static class NameFieldImpl implements Element {
+ private PersonName.NameField fieldID;
+ private Map<PersonName.FieldModifier, FieldModifierImpl> modifiers;
+
+ public NameFieldImpl(String fieldNameAndModifiers, PersonNameFormatterImpl formatterImpl) {
+ List<PersonName.FieldModifier> modifierIDs = new ArrayList<>();
+ StringTokenizer tok = new StringTokenizer(fieldNameAndModifiers, "-");
+
+ this.fieldID = PersonName.NameField.forString(tok.nextToken());
+ while (tok.hasMoreTokens()) {
+ modifierIDs.add(PersonName.FieldModifier.forString(tok.nextToken()));
+ }
+ if (this.fieldID == PersonName.NameField.SURNAME && formatterImpl.shouldCapitalizeSurname()) {
+ modifierIDs.add(PersonName.FieldModifier.ALL_CAPS);
+ }
+
+ this.modifiers = new HashMap<>();
+ for (PersonName.FieldModifier modifierID : modifierIDs) {
+ this.modifiers.put(modifierID, FieldModifierImpl.forName(modifierID, formatterImpl));
+ }
+ }
+
+ public boolean isLiteral() {
+ return false;
+ }
+
+ public String format(PersonName name) {
+ Set<PersonName.FieldModifier> modifierIDs = new HashSet<>(modifiers.keySet());
+ String result = name.getFieldValue(fieldID, modifierIDs);
+ if (result != null) {
+ for (PersonName.FieldModifier modifierID : modifierIDs) {
+ result = modifiers.get(modifierID).modifyField(result);
+ }
+ }
+ return result;
+ }
+
+ public boolean isPopulated(PersonName name) {
+ // just check whether the unmodified field contains a value
+ Set<PersonName.FieldModifier> modifierIDs = new HashSet<>();
+ String fieldValue = name.getFieldValue(fieldID, modifierIDs);
+ return fieldValue != null && !fieldValue.isEmpty();
+ }
+ }
+}
diff --git a/android_icu4j/src/main/java/android/icu/impl/units/ComplexUnitsConverter.java b/android_icu4j/src/main/java/android/icu/impl/units/ComplexUnitsConverter.java
index 2d8bcf1..4e614ac 100644
--- a/android_icu4j/src/main/java/android/icu/impl/units/ComplexUnitsConverter.java
+++ b/android_icu4j/src/main/java/android/icu/impl/units/ComplexUnitsConverter.java
@@ -26,12 +26,15 @@
public class ComplexUnitsConverter {
public static final BigDecimal EPSILON = BigDecimal.valueOf(Math.ulp(1.0));
public static final BigDecimal EPSILON_MULTIPLIER = BigDecimal.valueOf(1).add(EPSILON);
- private ArrayList<UnitsConverter> unitsConverters_;
+
+ // TODO(ICU-21937): Make it private after submitting the public units conversion API.
+ public ArrayList<UnitsConverter> unitsConverters_;
/**
* Individual units of mixed units, sorted big to small, with indices
* indicating the requested output mixed unit order.
*/
- private List<MeasureUnitImpl.MeasureUnitImplWithIndex> units_;
+ // TODO(ICU-21937): Make it private after submitting the public units conversion API.
+ public List<MeasureUnitImpl.MeasureUnitImplWithIndex> units_;
private MeasureUnitImpl inputUnit_;
/**
@@ -170,7 +173,7 @@
*/
public ComplexConverterResult convert(BigDecimal quantity, Precision rounder) {
BigInteger sign = BigInteger.ONE;
- if (quantity.compareTo(BigDecimal.ZERO) < 0) {
+ if (quantity.compareTo(BigDecimal.ZERO) < 0 && unitsConverters_.size() > 1) {
quantity = quantity.abs();
sign = sign.negate();
}
diff --git a/android_icu4j/src/main/java/android/icu/impl/units/MeasureUnitImpl.java b/android_icu4j/src/main/java/android/icu/impl/units/MeasureUnitImpl.java
index 824539e..1277b1b 100644
--- a/android_icu4j/src/main/java/android/icu/impl/units/MeasureUnitImpl.java
+++ b/android_icu4j/src/main/java/android/icu/impl/units/MeasureUnitImpl.java
@@ -434,7 +434,7 @@
private int fIndex = 0;
// Set to true when we've seen a "-per-" or a "per-", after which all units
// are in the denominator. Until we find an "-and-", at which point the
- // identifier is invalid pending TODO(CLDR-13700).
+ // identifier is invalid pending TODO(CLDR-13701).
private boolean fAfterPer = false;
// If an "-and-" was parsed prior to finding the "single
// * unit", sawAnd is set to true. If not, it is left as is.
@@ -568,7 +568,7 @@
* <p>
*
* @throws IllegalArgumentException if we parse both compound units and "-and-", since mixed
- * compound units are not yet supported - TODO(CLDR-13700).
+ * compound units are not yet supported - TODO(CLDR-13701).
*/
private SingleUnitImpl nextSingleUnit() {
SingleUnitImpl result = new SingleUnitImpl();
@@ -604,7 +604,7 @@
case PER:
if (fSawAnd) {
throw new IllegalArgumentException("Mixed compound units not yet supported");
- // TODO(CLDR-13700).
+ // TODO(CLDR-13701).
}
fAfterPer = true;
@@ -619,7 +619,7 @@
case AND:
if (fAfterPer) {
- // not yet supported, TODO(CLDR-13700).
+ // not yet supported, TODO(CLDR-13701).
throw new IllegalArgumentException("Can't start with \"-and-\", and mixed compound units");
}
fSawAnd = true;
diff --git a/android_icu4j/src/main/java/android/icu/impl/units/UnitPreferences.java b/android_icu4j/src/main/java/android/icu/impl/units/UnitPreferences.java
index b6ef278..fd15712 100644
--- a/android_icu4j/src/main/java/android/icu/impl/units/UnitPreferences.java
+++ b/android_icu4j/src/main/java/android/icu/impl/units/UnitPreferences.java
@@ -5,17 +5,31 @@
import java.math.BigDecimal;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
import android.icu.impl.ICUData;
import android.icu.impl.ICUResourceBundle;
import android.icu.impl.UResource;
+import android.icu.util.ULocale;
import android.icu.util.UResourceBundle;
/**
* @hide Only a subset of ICU is exposed in Android
*/
public class UnitPreferences {
+ private static final Map<String, String> measurementSystem;
+
+ static {
+ Map<String, String> tempMS = new HashMap<>();
+ tempMS.put("metric", "001");
+ tempMS.put("ussystem", "US");
+ tempMS.put("uksystem", "GB");
+ measurementSystem = Collections.unmodifiableMap(tempMS);
+ }
+
private HashMap<String, HashMap<String, UnitPreference[]>> mapToUnitPreferences = new HashMap<>();
@@ -60,7 +74,53 @@
return result.toArray(new String[0]);
}
- public UnitPreference[] getPreferencesFor(String category, String usage, String region) {
+ public UnitPreference[] getPreferencesFor(String category, String usage, ULocale locale, UnitsData data) {
+ // TODO: remove this condition when all the categories are allowed.
+ // WARNING: when this is removed please make sure to keep the "fahrenhe" => "fahrenheit" mapping
+ if ("temperature".equals(category)) {
+ String localeUnit = locale.getKeywordValue("mu");
+ // The value for -u-mu- is `fahrenhe`, but CLDR and everything else uses `fahrenheit`
+ if ("fahrenhe".equals(localeUnit)) {
+ localeUnit = "fahrenheit";
+ }
+ String localeUnitCategory;
+ try {
+ localeUnitCategory = localeUnit == null ? null : data.getCategory(MeasureUnitImpl.forIdentifier(localeUnit));
+ } catch (Exception e) {
+ localeUnitCategory = null;
+ }
+
+ if (localeUnitCategory != null && category.equals(localeUnitCategory)) {
+ UnitPreference[] preferences = {new UnitPreference(localeUnit, null, null)};
+ return preferences;
+ }
+ }
+
+ String region = locale.getCountry();
+
+ // Check the locale system tag, e.g `ms=metric`.
+ String localeSystem = locale.getKeywordValue("measure");
+ boolean isLocaleSystem = false;
+ if (measurementSystem.containsKey(localeSystem)) {
+ isLocaleSystem = true;
+ region = measurementSystem.get(localeSystem);
+ }
+
+ // Check the region tag, e.g. `rg=uszzz`.
+ if (!isLocaleSystem) {
+ String localeRegion = locale.getKeywordValue("rg");
+ if (localeRegion != null && localeRegion.length() >= 3) {
+ if (localeRegion.equals("default")) {
+ region = localeRegion;
+ } else if (Character.isDigit(localeRegion.charAt(0))) {
+ region = localeRegion.substring(0, 3); // e.g. 001
+ } else {
+ // Capitalize the first two character of the region, e.g. ukzzzz or usca
+ region = localeRegion.substring(0, 2).toUpperCase(Locale.ROOT);
+ }
+ }
+ }
+
String[] subUsages = getAllUsages(usage);
UnitPreference[] result = null;
for (String subUsage :
@@ -68,6 +128,7 @@
result = getUnitPreferences(category, subUsage, region);
if (result != null) break;
}
+
// TODO: if a category is missing, we get an assertion failure, or we
// return null, causing a NullPointerException. In C++, we return an
// U_MISSING_RESOURCE_ERROR error.
@@ -108,8 +169,8 @@
public UnitPreference(String unit, String geq, String skeleton) {
this.unit = unit;
- this.geq = new BigDecimal(geq);
- this.skeleton = skeleton;
+ this.geq = geq == null ? BigDecimal.valueOf( Double.MIN_VALUE) /* -inf */ : new BigDecimal(geq);
+ this.skeleton = skeleton == null? "" : skeleton;
}
public String getUnit() {
diff --git a/android_icu4j/src/main/java/android/icu/impl/units/UnitsConverter.java b/android_icu4j/src/main/java/android/icu/impl/units/UnitsConverter.java
index 91c3968..8aea577 100644
--- a/android_icu4j/src/main/java/android/icu/impl/units/UnitsConverter.java
+++ b/android_icu4j/src/main/java/android/icu/impl/units/UnitsConverter.java
@@ -119,10 +119,8 @@
if (this.reciprocal) {
// We should see no offsets for reciprocal conversions - they don't make sense:
assert offset == BigDecimal.ZERO;
- if (result == BigDecimal.ZERO) {
- // TODO: demonstrate the resulting behaviour in tests... and
- // figure out desired behaviour. (Theoretical result should be
- // infinity, not 0, but BigDecimal does not support infinity.)
+ if (result.compareTo(BigDecimal.ZERO) == 0) {
+ // TODO(ICU-21988): determine desirable behaviour
return BigDecimal.ZERO;
}
result = BigDecimal.ONE.divide(result, DECIMAL128);
@@ -135,10 +133,8 @@
if (this.reciprocal) {
// We should see no offsets for reciprocal conversions - they don't make sense:
assert offset == BigDecimal.ZERO;
- if (result == BigDecimal.ZERO) {
- // TODO: demonstrate the resulting behaviour in tests... and
- // figure out desired behaviour. (Theoretical result should be
- // infinity, not 0, but BigDecimal does not support infinity.)
+ if (result.compareTo(BigDecimal.ZERO) == 0) {
+ // TODO(ICU-21988): determine desirable behaviour
return BigDecimal.ZERO;
}
result = BigDecimal.ONE.divide(result, DECIMAL128);
@@ -201,6 +197,12 @@
private int exponentGlucoseMolarMass = 0;
/** Exponent for the item per mole conversion rate constant */
private int exponentItemPerMole = 0;
+ /** Exponent for the meters per AU conversion rate constant */
+ private int exponentMetersPerAU = 0;
+ /** Exponent for the sec per julian year conversion rate constant */
+ private int exponentSecPerJulianYear = 0;
+ /** Exponent for the speed of light meters per second" conversion rate constant */
+ private int exponentSpeedOfLightMetersPerSecond = 0;
/**
* Creates Empty Factor
@@ -254,6 +256,9 @@
result.exponentLbToKg = this.exponentLbToKg;
result.exponentGlucoseMolarMass = this.exponentGlucoseMolarMass;
result.exponentItemPerMole = this.exponentItemPerMole;
+ result.exponentMetersPerAU = this.exponentMetersPerAU;
+ result.exponentSecPerJulianYear = this.exponentSecPerJulianYear;
+ result.exponentSpeedOfLightMetersPerSecond = this.exponentSpeedOfLightMetersPerSecond;
return result;
}
@@ -281,6 +286,9 @@
resultCollector.multiply(new BigDecimal("0.45359237"), this.exponentLbToKg);
resultCollector.multiply(new BigDecimal("180.1557"), this.exponentGlucoseMolarMass);
resultCollector.multiply(new BigDecimal("6.02214076E+23"), this.exponentItemPerMole);
+ resultCollector.multiply(new BigDecimal("149597870700"), this.exponentMetersPerAU);
+ resultCollector.multiply(new BigDecimal("31557600"), this.exponentSecPerJulianYear);
+ resultCollector.multiply(new BigDecimal("299792458"), this.exponentSpeedOfLightMetersPerSecond);
return resultCollector.factorNum.divide(resultCollector.factorDen, DECIMAL128);
}
@@ -337,6 +345,10 @@
result.exponentLbToKg = this.exponentLbToKg * power;
result.exponentGlucoseMolarMass = this.exponentGlucoseMolarMass * power;
result.exponentItemPerMole = this.exponentItemPerMole * power;
+ result.exponentMetersPerAU = this.exponentMetersPerAU * power;
+ result.exponentSecPerJulianYear = this.exponentSecPerJulianYear * power;
+ result.exponentSpeedOfLightMetersPerSecond =
+ this.exponentSpeedOfLightMetersPerSecond * power;
return result;
}
@@ -355,6 +367,10 @@
result.exponentGlucoseMolarMass =
this.exponentGlucoseMolarMass - other.exponentGlucoseMolarMass;
result.exponentItemPerMole = this.exponentItemPerMole - other.exponentItemPerMole;
+ result.exponentMetersPerAU = this.exponentMetersPerAU - other.exponentMetersPerAU;
+ result.exponentSecPerJulianYear = this.exponentSecPerJulianYear - other.exponentSecPerJulianYear;
+ result.exponentSpeedOfLightMetersPerSecond =
+ this.exponentSpeedOfLightMetersPerSecond - other.exponentSpeedOfLightMetersPerSecond;
return result;
}
@@ -373,6 +389,10 @@
result.exponentGlucoseMolarMass =
this.exponentGlucoseMolarMass + other.exponentGlucoseMolarMass;
result.exponentItemPerMole = this.exponentItemPerMole + other.exponentItemPerMole;
+ result.exponentMetersPerAU = this.exponentMetersPerAU + other.exponentMetersPerAU;
+ result.exponentSecPerJulianYear = this.exponentSecPerJulianYear + other.exponentSecPerJulianYear;
+ result.exponentSpeedOfLightMetersPerSecond =
+ this.exponentSpeedOfLightMetersPerSecond + other.exponentSpeedOfLightMetersPerSecond;
return result;
}
@@ -416,9 +436,15 @@
this.exponentGlucoseMolarMass += power;
} else if ("item_per_mole".equals(entity)) {
this.exponentItemPerMole += power;
+ } else if ("meters_per_AU".equals(entity)) {
+ this.exponentMetersPerAU += power;
} else if ("PI".equals(entity)) {
this.exponentPi += power;
- } else {
+ } else if ("sec_per_julian_year".equals(entity)) {
+ this.exponentSecPerJulianYear += power;
+ } else if ("speed_of_light_meters_per_second".equals(entity)) {
+ this.exponentSpeedOfLightMetersPerSecond += power;
+ } else {
BigDecimal decimalEntity = new BigDecimal(entity).pow(power, DECIMAL128);
this.factorNum = this.factorNum.multiply(decimalEntity);
}
diff --git a/android_icu4j/src/main/java/android/icu/impl/units/UnitsData.java b/android_icu4j/src/main/java/android/icu/impl/units/UnitsData.java
index 373be1c..a57bc55 100644
--- a/android_icu4j/src/main/java/android/icu/impl/units/UnitsData.java
+++ b/android_icu4j/src/main/java/android/icu/impl/units/UnitsData.java
@@ -12,6 +12,7 @@
import android.icu.impl.ICUResourceBundle;
import android.icu.impl.IllegalIcuArgumentException;
import android.icu.impl.UResource;
+import android.icu.util.ULocale;
import android.icu.util.UResourceBundle;
/**
@@ -111,8 +112,8 @@
return Categories.indexToCategory[index];
}
- public UnitPreferences.UnitPreference[] getPreferencesFor(String category, String usage, String region) {
- return this.unitPreferences.getPreferencesFor(category, usage, region);
+ public UnitPreferences.UnitPreference[] getPreferencesFor(String category, String usage, ULocale locale) {
+ return this.unitPreferences.getPreferencesFor(category, usage, locale, this);
}
/**
diff --git a/android_icu4j/src/main/java/android/icu/impl/units/UnitsRouter.java b/android_icu4j/src/main/java/android/icu/impl/units/UnitsRouter.java
index d1909fa..383642e 100644
--- a/android_icu4j/src/main/java/android/icu/impl/units/UnitsRouter.java
+++ b/android_icu4j/src/main/java/android/icu/impl/units/UnitsRouter.java
@@ -11,6 +11,7 @@
import android.icu.impl.number.MicroProps;
import android.icu.number.Precision;
import android.icu.util.MeasureUnit;
+import android.icu.util.ULocale;
/**
* `UnitsRouter` responsible for converting from a single unit (such as `meter` or `meter-per-second`) to
@@ -23,7 +24,7 @@
* `foot+inch`, otherwise, the output will be in `inch`.
* <p>
* NOTE:
- * the output units and the their limits MUST BE in order, for example, if the output units, from the
+ * the output units and their limits MUST BE in order, for example, if the output units, from the
* previous example, are the following:
* {`inch` , limit: no value (-inf)}
* {`foot+inch`, limit: 3.0}
@@ -48,17 +49,17 @@
private ArrayList<MeasureUnit> outputUnits_ = new ArrayList<>();
private ArrayList<ConverterPreference> converterPreferences_ = new ArrayList<>();
- public UnitsRouter(String inputUnitIdentifier, String region, String usage) {
- this(MeasureUnitImpl.forIdentifier(inputUnitIdentifier), region, usage);
+ public UnitsRouter(String inputUnitIdentifier, ULocale locale, String usage) {
+ this(MeasureUnitImpl.forIdentifier(inputUnitIdentifier), locale, usage);
}
- public UnitsRouter(MeasureUnitImpl inputUnit, String region, String usage) {
+ public UnitsRouter(MeasureUnitImpl inputUnit, ULocale locale, String usage) {
// TODO: do we want to pass in ConversionRates and UnitPreferences instead?
// of loading in each UnitsRouter instance? (Or make global?)
UnitsData data = new UnitsData();
String category = data.getCategory(inputUnit);
- UnitPreferences.UnitPreference[] unitPreferences = data.getPreferencesFor(category, usage, region);
+ UnitPreferences.UnitPreference[] unitPreferences = data.getPreferencesFor(category, usage, locale);
for (int i = 0; i < unitPreferences.length; ++i) {
UnitPreferences.UnitPreference preference = unitPreferences[i];
diff --git a/android_icu4j/src/main/java/android/icu/lang/UCharacter.java b/android_icu4j/src/main/java/android/icu/lang/UCharacter.java
index da7dda7..f2da2c6 100644
--- a/android_icu4j/src/main/java/android/icu/lang/UCharacter.java
+++ b/android_icu4j/src/main/java/android/icu/lang/UCharacter.java
@@ -1101,6 +1101,23 @@
/***/
public static final int ZNAMENNY_MUSICAL_NOTATION_ID = 320; /*[1CF00]*/
+ // New blocks in Unicode 15.0
+
+ /***/
+ public static final int ARABIC_EXTENDED_C_ID = 321; /*[10EC0]*/
+ /***/
+ public static final int CJK_UNIFIED_IDEOGRAPHS_EXTENSION_H_ID = 322; /*[31350]*/
+ /***/
+ public static final int CYRILLIC_EXTENDED_D_ID = 323; /*[1E030]*/
+ /***/
+ public static final int DEVANAGARI_EXTENDED_A_ID = 324; /*[11B00]*/
+ /***/
+ public static final int KAKTOVIK_NUMERALS_ID = 325; /*[1D2C0]*/
+ /***/
+ public static final int KAWI_ID = 326; /*[11F00]*/
+ /***/
+ public static final int NAG_MUNDARI_ID = 327; /*[1E4D0]*/
+
/**
* One more than the highest normal UnicodeBlock value.
* The highest value is available via UCharacter.getIntPropertyMaxValue(UProperty.BLOCK).
@@ -1109,7 +1126,7 @@
* @hide unsupported on Android
*/
@Deprecated
- public static final int COUNT = 321;
+ public static final int COUNT = 328;
// blocks objects ---------------------------------------------------
@@ -2342,6 +2359,30 @@
new UnicodeBlock("ZNAMENNY_MUSICAL_NOTATION",
ZNAMENNY_MUSICAL_NOTATION_ID); /*[1CF00]*/
+ // New blocks in Unicode 15.0
+
+ /***/
+ public static final UnicodeBlock ARABIC_EXTENDED_C =
+ new UnicodeBlock("ARABIC_EXTENDED_C", ARABIC_EXTENDED_C_ID); /*[10EC0]*/
+ /***/
+ public static final UnicodeBlock CJK_UNIFIED_IDEOGRAPHS_EXTENSION_H =
+ new UnicodeBlock("CJK_UNIFIED_IDEOGRAPHS_EXTENSION_H",
+ CJK_UNIFIED_IDEOGRAPHS_EXTENSION_H_ID); /*[31350]*/
+ /***/
+ public static final UnicodeBlock CYRILLIC_EXTENDED_D =
+ new UnicodeBlock("CYRILLIC_EXTENDED_D", CYRILLIC_EXTENDED_D_ID); /*[1E030]*/
+ /***/
+ public static final UnicodeBlock DEVANAGARI_EXTENDED_A =
+ new UnicodeBlock("DEVANAGARI_EXTENDED_A", DEVANAGARI_EXTENDED_A_ID); /*[11B00]*/
+ /***/
+ public static final UnicodeBlock KAKTOVIK_NUMERALS =
+ new UnicodeBlock("KAKTOVIK_NUMERALS", KAKTOVIK_NUMERALS_ID); /*[1D2C0]*/
+ /***/
+ public static final UnicodeBlock KAWI = new UnicodeBlock("KAWI", KAWI_ID); /*[11F00]*/
+ /***/
+ public static final UnicodeBlock NAG_MUNDARI =
+ new UnicodeBlock("NAG_MUNDARI", NAG_MUNDARI_ID); /*[1E4D0]*/
+
/**
*/
public static final UnicodeBlock INVALID_CODE
@@ -5329,8 +5370,6 @@
* does not have data for the property at all.
*
* @see android.icu.lang.UProperty
- * @see CharacterProperties#getBinaryPropertySet(int)
- * @hide draft / provisional / internal are hidden on Android
*/
public static boolean hasBinaryProperty(CharSequence s, int property) {
int length = s.length();
diff --git a/android_icu4j/src/main/java/android/icu/lang/UProperty.java b/android_icu4j/src/main/java/android/icu/lang/UProperty.java
index 504d8d6..f0057c3 100644
--- a/android_icu4j/src/main/java/android/icu/lang/UProperty.java
+++ b/android_icu4j/src/main/java/android/icu/lang/UProperty.java
@@ -495,50 +495,36 @@
/**
* Binary property of strings Basic_Emoji.
* See https://www.unicode.org/reports/tr51/#Emoji_Sets
- *
- * @hide draft / provisional / internal are hidden on Android
*/
public static final int BASIC_EMOJI=65;
/**
* Binary property of strings Emoji_Keycap_Sequence.
* See https://www.unicode.org/reports/tr51/#Emoji_Sets
- *
- * @hide draft / provisional / internal are hidden on Android
*/
public static final int EMOJI_KEYCAP_SEQUENCE=66;
/**
* Binary property of strings RGI_Emoji_Modifier_Sequence.
* See https://www.unicode.org/reports/tr51/#Emoji_Sets
- *
- * @hide draft / provisional / internal are hidden on Android
*/
public static final int RGI_EMOJI_MODIFIER_SEQUENCE=67;
/**
* Binary property of strings RGI_Emoji_Flag_Sequence.
* See https://www.unicode.org/reports/tr51/#Emoji_Sets
- *
- * @hide draft / provisional / internal are hidden on Android
*/
public static final int RGI_EMOJI_FLAG_SEQUENCE=68;
/**
* Binary property of strings RGI_Emoji_Tag_Sequence.
* See https://www.unicode.org/reports/tr51/#Emoji_Sets
- *
- * @hide draft / provisional / internal are hidden on Android
*/
public static final int RGI_EMOJI_TAG_SEQUENCE=69;
/**
* Binary property of strings RGI_Emoji_ZWJ_Sequence.
* See https://www.unicode.org/reports/tr51/#Emoji_Sets
- *
- * @hide draft / provisional / internal are hidden on Android
*/
public static final int RGI_EMOJI_ZWJ_SEQUENCE=70;
/**
* Binary property of strings RGI_Emoji.
* See https://www.unicode.org/reports/tr51/#Emoji_Sets
- *
- * @hide draft / provisional / internal are hidden on Android
*/
public static final int RGI_EMOJI=71;
diff --git a/android_icu4j/src/main/java/android/icu/lang/UScript.java b/android_icu4j/src/main/java/android/icu/lang/UScript.java
index 2d95fab..1a98ba2 100644
--- a/android_icu4j/src/main/java/android/icu/lang/UScript.java
+++ b/android_icu4j/src/main/java/android/icu/lang/UScript.java
@@ -875,6 +875,11 @@
/***/
public static final int VITHKUQI = 197; /* Vith */
+ /***/
+ public static final int KAWI = 198; /* Kawi */
+ /***/
+ public static final int NAG_MUNDARI = 199; /* Nagm */
+
/**
* One more than the highest normal UScript code.
* The highest value is available via UCharacter.getIntPropertyMaxValue(UProperty.SCRIPT).
@@ -883,7 +888,7 @@
* @hide unsupported on Android
*/
@Deprecated
- public static final int CODE_LIMIT = 198;
+ public static final int CODE_LIMIT = 200;
private static int[] getCodesFromLocale(ULocale locale) {
// Multi-script languages, equivalent to the LocaleScript data
@@ -1354,6 +1359,8 @@
0x16ABC | EXCLUSION, // Tnsa
0x1E290 | EXCLUSION, // Toto
0x10582 | EXCLUSION | CASED, // Vith
+ 0x11F1B | EXCLUSION | LB_LETTERS, // Kawi
+ 0x1E4E6 | EXCLUSION, // Nagm
// End copy-paste from parsescriptmetadata.py
};
diff --git a/android_icu4j/src/main/java/android/icu/message2/DateTimeFormatterFactory.java b/android_icu4j/src/main/java/android/icu/message2/DateTimeFormatterFactory.java
new file mode 100644
index 0000000..e01f72c
--- /dev/null
+++ b/android_icu4j/src/main/java/android/icu/message2/DateTimeFormatterFactory.java
@@ -0,0 +1,108 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+// © 2022 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html
+
+package android.icu.message2;
+
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+
+import android.icu.impl.locale.AsciiUtil;
+import android.icu.text.DateFormat;
+import android.icu.text.SimpleDateFormat;
+
+/**
+ * Creates a {@link Formatter} doing formatting of date / time, similar to
+ * <code>{exp, date}</code> and <code>{exp, time}</code> in {@link android.icu.text.MessageFormat}.
+ */
+class DateTimeFormatterFactory implements FormatterFactory {
+
+ private static int stringToStyle(String option) {
+ switch (AsciiUtil.toUpperString(option)) {
+ case "FULL": return DateFormat.FULL;
+ case "LONG": return DateFormat.LONG;
+ case "MEDIUM": return DateFormat.MEDIUM;
+ case "SHORT": return DateFormat.SHORT;
+ case "": // intentional fall-through
+ case "DEFAULT": return DateFormat.DEFAULT;
+ default: throw new IllegalArgumentException("Invalid datetime style: " + option);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @throws IllegalArgumentException when something goes wrong
+ * (for example conflicting options, invalid option values, etc.)
+ */
+ @Override
+ public Formatter createFormatter(Locale locale, Map<String, Object> fixedOptions) {
+ DateFormat df;
+
+ // TODO: how to handle conflicts. What if we have both skeleton and style, or pattern?
+ Object opt = fixedOptions.get("skeleton");
+ if (opt != null) {
+ String skeleton = Objects.toString(opt);
+ df = DateFormat.getInstanceForSkeleton(skeleton, locale);
+ return new DateTimeFormatter(df);
+ }
+
+ opt = fixedOptions.get("pattern");
+ if (opt != null) {
+ String pattern = Objects.toString(opt);
+ SimpleDateFormat sf = new SimpleDateFormat(pattern, locale);
+ return new DateTimeFormatter(sf);
+ }
+
+ int dateStyle = DateFormat.NONE;
+ opt = fixedOptions.get("datestyle");
+ if (opt != null) {
+ dateStyle = stringToStyle(Objects.toString(opt, ""));
+ }
+
+ int timeStyle = DateFormat.NONE;
+ opt = fixedOptions.get("timestyle");
+ if (opt != null) {
+ timeStyle = stringToStyle(Objects.toString(opt, ""));
+ }
+
+ if (dateStyle == DateFormat.NONE && timeStyle == DateFormat.NONE) {
+ // Match the MessageFormat behavior
+ dateStyle = DateFormat.SHORT;
+ timeStyle = DateFormat.SHORT;
+ }
+ df = DateFormat.getDateTimeInstance(dateStyle, timeStyle, locale);
+
+ return new DateTimeFormatter(df);
+ }
+
+ private static class DateTimeFormatter implements Formatter {
+ private final DateFormat icuFormatter;
+
+ private DateTimeFormatter(DateFormat df) {
+ this.icuFormatter = df;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public FormattedPlaceholder format(Object toFormat, Map<String, Object> variableOptions) {
+ // TODO: use a special type to indicate function without input argument.
+ if (toFormat == null) {
+ throw new IllegalArgumentException("The date to format can't be null");
+ }
+ String result = icuFormatter.format(toFormat);
+ return new FormattedPlaceholder(toFormat, new PlainStringFormattedValue(result));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String formatToString(Object toFormat, Map<String, Object> variableOptions) {
+ return format(toFormat, variableOptions).toString();
+ }
+ }
+}
diff --git a/android_icu4j/src/main/java/android/icu/message2/FormattedMessage.java b/android_icu4j/src/main/java/android/icu/message2/FormattedMessage.java
new file mode 100644
index 0000000..960723a
--- /dev/null
+++ b/android_icu4j/src/main/java/android/icu/message2/FormattedMessage.java
@@ -0,0 +1,122 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+// © 2022 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html
+
+package android.icu.message2;
+
+import java.text.AttributedCharacterIterator;
+
+import android.icu.text.ConstrainedFieldPosition;
+import android.icu.text.FormattedValue;
+
+/**
+ * Not yet implemented: The result of a message formatting operation.
+ *
+ * <p>This contains information about where the various fields and placeholders
+ * ended up in the final result.</p>
+ * <p>This class allows the result to be exported in several data types,
+ * including a {@link String}, {@link AttributedCharacterIterator}, more (TBD).</p>
+ *
+ * @deprecated This API is for ICU internal use only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+@Deprecated
+public class FormattedMessage implements FormattedValue {
+
+ /**
+ * Not yet implemented.
+ *
+ * @deprecated This API is for ICU internal use only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public FormattedMessage() {
+ throw new RuntimeException("Not yet implemented.");
+ }
+
+ /**
+ * Not yet implemented.
+ *
+ * {@inheritDoc}
+ *
+ * @deprecated This API is for ICU internal use only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ @Override
+ public int length() {
+ throw new RuntimeException("Not yet implemented.");
+ }
+
+ /**
+ * Not yet implemented.
+ *
+ * {@inheritDoc}
+ *
+ * @deprecated This API is for ICU internal use only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ @Override
+ public char charAt(int index) {
+ throw new RuntimeException("Not yet implemented.");
+ }
+
+ /**
+ * Not yet implemented.
+ *
+ * {@inheritDoc}
+ *
+ * @deprecated This API is for ICU internal use only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ @Override
+ public CharSequence subSequence(int start, int end) {
+ throw new RuntimeException("Not yet implemented.");
+ }
+
+ /**
+ * Not yet implemented.
+ *
+ * {@inheritDoc}
+ *
+ * @deprecated This API is for ICU internal use only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ @Override
+ public <A extends Appendable> A appendTo(A appendable) {
+ throw new RuntimeException("Not yet implemented.");
+ }
+
+ /**
+ * Not yet implemented.
+ *
+ * {@inheritDoc}
+ *
+ * @deprecated This API is for ICU internal use only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ @Override
+ public boolean nextPosition(ConstrainedFieldPosition cfpos) {
+ throw new RuntimeException("Not yet implemented.");
+ }
+
+ /**
+ * Not yet implemented.
+ *
+ * {@inheritDoc}
+ *
+ * @deprecated This API is for ICU internal use only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ @Override
+ public AttributedCharacterIterator toCharacterIterator() {
+ throw new RuntimeException("Not yet implemented.");
+ }
+
+}
diff --git a/android_icu4j/src/main/java/android/icu/message2/FormattedPlaceholder.java b/android_icu4j/src/main/java/android/icu/message2/FormattedPlaceholder.java
new file mode 100644
index 0000000..16bd52d
--- /dev/null
+++ b/android_icu4j/src/main/java/android/icu/message2/FormattedPlaceholder.java
@@ -0,0 +1,81 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+// © 2022 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html
+
+package android.icu.message2;
+
+import android.icu.text.FormattedValue;
+
+/**
+ * An immutable, richer formatting result, encapsulating a {@link FormattedValue},
+ * the original value to format, and we are considering adding some more info.
+ * Very preliminary.
+ *
+ * @deprecated This API is for ICU internal use only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+@Deprecated
+public class FormattedPlaceholder {
+ private final FormattedValue formattedValue;
+ private final Object inputValue;
+
+ /**
+ * Constructor creating the {@code FormattedPlaceholder}.
+ *
+ * @param inputValue the original value to be formatted.
+ * @param formattedValue the result of formatting the placeholder.
+ *
+ * @deprecated This API is for ICU internal use only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public FormattedPlaceholder(Object inputValue, FormattedValue formattedValue) {
+ if (formattedValue == null) {
+ throw new IllegalAccessError("Should not try to wrap a null formatted value");
+ }
+ this.inputValue = inputValue;
+ this.formattedValue = formattedValue;
+ }
+
+ /**
+ * Retrieve the original input value that was formatted.
+ *
+ * @return the original value to be formatted.
+ *
+ * @deprecated This API is for ICU internal use only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Object getInput() {
+ return inputValue;
+ }
+
+ /**
+ * Retrieve the formatted value.
+ *
+ * @return the result of formatting the placeholder.
+ *
+ * @deprecated This API is for ICU internal use only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public FormattedValue getFormattedValue() {
+ return formattedValue;
+ }
+
+ /**
+ * Returns a string representation of the object.
+ * It can be null, which is unusual, and we plan to change that.
+ *
+ * @return a string representation of the object.
+ *
+ * @deprecated This API is for ICU internal use only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ @Override
+ public String toString() {
+ return formattedValue.toString();
+ }
+}
diff --git a/android_icu4j/src/main/java/android/icu/message2/Formatter.java b/android_icu4j/src/main/java/android/icu/message2/Formatter.java
new file mode 100644
index 0000000..0eeac56
--- /dev/null
+++ b/android_icu4j/src/main/java/android/icu/message2/Formatter.java
@@ -0,0 +1,46 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+// © 2022 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html
+
+package android.icu.message2;
+
+import java.util.Map;
+
+/**
+ * The interface that must be implemented by all formatters
+ * that can be used from {@link MessageFormatter}.
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+@Deprecated
+public interface Formatter {
+ /**
+ * A method that takes the object to format and returns
+ * the i18n-aware string representation.
+ *
+ * @param toFormat the object to format.
+ * @param variableOptions options that are not know at build time.
+ * @return the formatted string.
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ String formatToString(Object toFormat, Map<String, Object> variableOptions);
+
+ /**
+ * A method that takes the object to format and returns
+ * the i18n-aware formatted placeholder.
+ *
+ * @param toFormat the object to format.
+ * @param variableOptions options that are not know at build time.
+ * @return the formatted placeholder.
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ FormattedPlaceholder format(Object toFormat, Map<String, Object> variableOptions);
+}
diff --git a/android_icu4j/src/main/java/android/icu/message2/FormatterFactory.java b/android_icu4j/src/main/java/android/icu/message2/FormatterFactory.java
new file mode 100644
index 0000000..6b89c31
--- /dev/null
+++ b/android_icu4j/src/main/java/android/icu/message2/FormatterFactory.java
@@ -0,0 +1,35 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+// © 2022 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html
+
+package android.icu.message2;
+
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * The interface that must be implemented for each formatting function name
+ * that can be used from {@link MessageFormatter}.
+ *
+ * <p>We use it to create and cache various formatters with various options.</p>
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+@Deprecated
+public interface FormatterFactory {
+ /**
+ * The method that is called to create a formatter.
+ *
+ * @param locale the locale to use for formatting.
+ * @param fixedOptions the options to use for formatting. The keys and values are function dependent.
+ * @return the formatter.
+ * @throws IllegalArgumentException
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ Formatter createFormatter(Locale locale, Map<String, Object> fixedOptions);
+}
diff --git a/android_icu4j/src/main/java/android/icu/message2/IdentityFormatterFactory.java b/android_icu4j/src/main/java/android/icu/message2/IdentityFormatterFactory.java
new file mode 100644
index 0000000..6798b98
--- /dev/null
+++ b/android_icu4j/src/main/java/android/icu/message2/IdentityFormatterFactory.java
@@ -0,0 +1,40 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+// © 2022 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html
+
+package android.icu.message2;
+
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Creates a {@link Formatter} that simply returns the String non-i18n aware representation of an object.
+ */
+class IdentityFormatterFactory implements FormatterFactory {
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Formatter createFormatter(Locale locale, Map<String, Object> fixedOptions) {
+ return new IdentityFormatterImpl();
+ }
+
+ private static class IdentityFormatterImpl implements Formatter {
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public FormattedPlaceholder format(Object toFormat, Map<String, Object> variableOptions) {
+ return new FormattedPlaceholder(toFormat, new PlainStringFormattedValue(Objects.toString(toFormat)));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String formatToString(Object toFormat, Map<String, Object> variableOptions) {
+ return format(toFormat, variableOptions).toString();
+ }
+ }
+}
diff --git a/android_icu4j/src/main/java/android/icu/message2/MessageFormatter.java b/android_icu4j/src/main/java/android/icu/message2/MessageFormatter.java
new file mode 100644
index 0000000..e8e187a
--- /dev/null
+++ b/android_icu4j/src/main/java/android/icu/message2/MessageFormatter.java
@@ -0,0 +1,341 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+// © 2022 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html
+
+package android.icu.message2;
+
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * {@code MessageFormatter} is the next iteration of {@link android.icu.text.MessageFormat}.
+ *
+ * <p>This new version builds on what we learned from using {@code MessageFormat} for 20 years
+ * in various environments, either exposed "as is" or as a base for other public APIs.</p>
+ *
+ * <p>It is more modular, easier to backport, and provides extension points to add new
+ * formatters and selectors without having to modify the specification.</p>
+ *
+ * <p>We will be able to add formatters for intervals, relative times, lists, measurement units,
+ * people names, and more, and support custom formatters implemented by developers
+ * outside of ICU itself, for company or even product specific needs.</p>
+ *
+ * <p>MessageFormat 2 will support more complex grammatical features, such as gender, inflections,
+ * and tagging parts of the message for style changes or speech.</p>
+ *
+ * <p>The reasoning for this effort is shared in the
+ * <a target="github" href="https://github.com/unicode-org/message-format-wg/blob/main/docs/why_mf_next.md">“Why
+ * MessageFormat needs a successor”</a> document.</p>
+ *
+ * <p>The “MessageFormat 2” project, which develops the new data model, semantics, and syntax,
+ * is hosted on <a target="github" href="https://github.com/unicode-org/message-format-wg">GitHub</a>.</p>
+ *
+ * <p>The current specification for the syntax and data model can be found
+ * <a target="github" href="https://github.com/unicode-org/message-format-wg/blob/main/spec/syntax.md">here</a>.</p>
+ *
+ * <p>This tech preview implements enough of the {@code MessageFormat} functions to be useful,
+ * but the final set of functions and the parameters accepted by those functions is not yet finalized.</p>
+ *
+ * <p>These are the functions interpreted right now:</p>
+ *
+ * <table border="1">
+ * <tr>
+ * <td rowspan="4">{@code datetime}</td>
+ * <td>Similar to the ICU {@code "date"} and {@code "time"}.</td>
+ * </tr>
+ *
+ * <tr><td>{@code datestyle} and {@code timestyle}<br>
+ * Similar to {@code argStyle : short | medium | long | full}.<br>
+ * Same values are accepted, but we can use both in one placeholder,
+ * for example <code>{$due :datetime datestyle=full timestyle=long}</code>.
+ * </td></tr>
+ *
+ * <tr><td>{@code pattern}<br>
+ * Similar to {@code argStyle = argStyleText}.<br>
+ * This is bad i18n practice, and will probably be dropped.<br>
+ * This is included just to support migration to MessageFormat 2.
+ * </td></tr>
+ *
+ * <tr><td>{@code skeleton}<br>
+ * Same as {@code argStyle = argSkeletonText}.<br>
+ * These are the date/time skeletons as supported by {@link android.icu.text.SimpleDateFormat}.
+ * </td></tr>
+ *
+ * <tr>
+ * <td rowspan="4">{@code number}</td>
+ * <td>Similar to the ICU "number".</td>
+ * </tr>
+ *
+ * <tr><td>{@code skeleton}<br>
+ * These are the number skeletons as supported by {@link android.icu.number.NumberFormatter}.</td></tr>
+ *
+ * <tr><td>{@code minimumFractionDigits}<br>
+ * Only implemented to be able to pass the unit tests from the ECMA tech preview implementation,
+ * which prefers options bags to skeletons.<br>
+ * TBD if the final {@number} function will support skeletons, option backs, or both.</td></tr>
+ *
+ * <tr><td>{@code offset}<br>
+ * Used to support plural with an offset.</td></tr>
+ *
+ * <tr><td >{@code identity}</td><td>Returns the direct string value of the argument (calling {@code toString()}).</td></tr>
+ *
+ * <tr>
+ * <td rowspan="3">{@code plural}</td>
+ * <td>Similar to the ICU {@code "plural"}.</td>
+ * </tr>
+ *
+ * <tr><td>{@code skeleton}<br>
+ * These are the number skeletons as supported by {@link android.icu.number.NumberFormatter}.<br>
+ * Can also be indirect, from a local variable of type {@code number} (recommended).</td></tr>
+ *
+ * <tr><td>{@code offset}<br>
+ * Used to support plural with an offset.<br>
+ * Can also be indirect, from a local variable of type {@code number} (recommended).</td></tr>
+ *
+ * <tr>
+ * <td>{@code selectordinal}</td>
+ * <td>Similar to the ICU {@code "selectordinal"}.<br>
+ * For now it accepts the same parameters as "plural", although there is no use case for them.<br>
+ * TBD if this will be merged into "plural" (with some {@code kind} option) or not.</td></tr>
+ *
+ * <tr><td>{@code select}</td><td>Literal match, same as the ICU4 {@code "select"}.</td></tr>
+ * </table>
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+@Deprecated
+public class MessageFormatter {
+ private final Locale locale;
+ private final String pattern;
+ private final Mf2FunctionRegistry functionRegistry;
+ private final Mf2DataModel dataModel;
+ private final Mf2DataModelFormatter modelFormatter;
+
+ private MessageFormatter(Builder builder) {
+ this.locale = builder.locale;
+ this.functionRegistry = builder.functionRegistry;
+ if ((builder.pattern == null && builder.dataModel == null)
+ || (builder.pattern != null && builder.dataModel != null)) {
+ throw new IllegalArgumentException("You need to set either a pattern, or a dataModel, but not both.");
+ }
+
+ if (builder.dataModel != null) {
+ this.dataModel = builder.dataModel;
+ this.pattern = Mf2Serializer.dataModelToString(this.dataModel);
+ } else {
+ this.pattern = builder.pattern;
+ Mf2Serializer tree = new Mf2Serializer();
+ Mf2Parser parser = new Mf2Parser(pattern, tree);
+ try {
+ parser.parse_Message();
+ dataModel = tree.build();
+ } catch (Mf2Parser.ParseException pe) {
+ throw new IllegalArgumentException(
+ "Parse error:\n"
+ + "Message: <<" + pattern + ">>\n"
+ + "Error:" + parser.getErrorMessage(pe) + "\n");
+ }
+ }
+ modelFormatter = new Mf2DataModelFormatter(dataModel, locale, functionRegistry);
+ }
+
+ /**
+ * Creates a builder.
+ *
+ * @return the Builder.
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Get the locale to use for all the formatting and selections in
+ * the current {@code MessageFormatter}.
+ *
+ * @return the locale.
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Locale getLocale() {
+ return locale;
+ }
+
+ /**
+ * Get the pattern (the serialized message in MessageFormat 2 syntax) of
+ * the current {@code MessageFormatter}.
+ *
+ * <p>If the {@code MessageFormatter} was created from an {@link Mf2DataModel}
+ * the this string is generated from that model.</p>
+ *
+ * @return the pattern.
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public String getPattern() {
+ return pattern;
+ }
+
+ /**
+ * Give public access to the message data model.
+ *
+ * <p>This data model is similar to the functionality we have today
+ * in {@link android.icu.text.MessagePatternUtil} maybe even a bit more higher level.</p>
+ *
+ * <p>We can also imagine a model where one parses the string syntax, takes the data model,
+ * modifies it, and then uses that modified model to create a {@code MessageFormatter}.</p>
+ *
+ * @return the data model.
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Mf2DataModel getDataModel() {
+ return dataModel;
+ }
+
+ /**
+ * Formats a map of objects by iterating over the MessageFormat's pattern,
+ * with the plain text “as is” and the arguments replaced by the formatted objects.
+ *
+ * @param arguments a map of objects to be formatted and substituted.
+ * @return the string representing the message with parameters replaced.
+ *
+ * @throws IllegalArgumentException when something goes wrong
+ * (for example wrong argument type, or null arguments, etc.)
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public String formatToString(Map<String, Object> arguments) {
+ return modelFormatter.format(arguments);
+ }
+
+ /**
+ * Not yet implemented: formats a map of objects by iterating over the MessageFormat's
+ * pattern, with the plain text “as is” and the arguments replaced by the formatted objects.
+ *
+ * @param arguments a map of objects to be formatted and substituted.
+ * @return the {@link FormattedMessage} class representing the message with parameters replaced.
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public FormattedMessage format(Map<String, Object> arguments) {
+ throw new RuntimeException("Not yet implemented.");
+ }
+
+ /**
+ * A {@code Builder} used to build instances of {@link MessageFormatter}.
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public static class Builder {
+ private Locale locale = Locale.getDefault(Locale.Category.FORMAT);
+ private String pattern = null;
+ private Mf2FunctionRegistry functionRegistry = Mf2FunctionRegistry.builder().build();
+ private Mf2DataModel dataModel = null;
+
+ // Prevent direct creation
+ private Builder() {
+ }
+
+ /**
+ * Sets the locale to use for all formatting and selection operations.
+ *
+ * @param locale the locale to set.
+ * @return the builder, for fluent use.
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Builder setLocale(Locale locale) {
+ this.locale = locale;
+ return this;
+ }
+
+ /**
+ * Sets the pattern (in MessageFormat 2 syntax) used to create the message.<br>
+ * It conflicts with the data model, so it will reset it (the last call on setter wins).
+ *
+ * @param pattern the pattern to set.
+ * @return the builder, for fluent use.
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Builder setPattern(String pattern) {
+ this.pattern = pattern;
+ this.dataModel = null;
+ return this;
+ }
+
+ /**
+ * Sets an instance of {@link Mf2FunctionRegistry} that should register any
+ * custom functions used by the message.
+ *
+ * <p>There is no need to do this in order to use standard functions
+ * (for example date / time / number formatting, plural / ordinal / literal selection).<br>
+ * The exact set of standard functions, with the types they format and the options
+ * they accept is still TBD.</p>
+ *
+ * @param functionRegistry the function registry to set.
+ * @return the builder, for fluent use.
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Builder setFunctionRegistry(Mf2FunctionRegistry functionRegistry) {
+ this.functionRegistry = functionRegistry;
+ return this;
+ }
+
+ /**
+ * Sets the data model used to create the message.<br>
+ * It conflicts with the pattern, so it will reset it (the last call on setter wins).
+ *
+ * @param dataModel the pattern to set.
+ * @return the builder, for fluent use.
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Builder setDataModel(Mf2DataModel dataModel) {
+ this.dataModel = dataModel;
+ this.pattern = null;
+ return this;
+ }
+
+ /**
+ * Builds an instance of {@link MessageFormatter}.
+ *
+ * @return the {@link MessageFormatter} created.
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public MessageFormatter build() {
+ return new MessageFormatter(this);
+ }
+ }
+}
diff --git a/android_icu4j/src/main/java/android/icu/message2/Mf2DataModel.java b/android_icu4j/src/main/java/android/icu/message2/Mf2DataModel.java
new file mode 100644
index 0000000..0dd5e4b
--- /dev/null
+++ b/android_icu4j/src/main/java/android/icu/message2/Mf2DataModel.java
@@ -0,0 +1,884 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+// © 2022 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html
+
+package android.icu.message2;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.StringJoiner;
+
+/**
+ * This maps closely to the official specification.
+ * Since it is not final, we will not add javadoc everywhere.
+ *
+ * <p>See <a target="github" href="https://github.com/unicode-org/message-format-wg/blob/main/spec/syntax.md">the
+ * description of the syntax with examples and use cases</a> and the corresponding
+ * <a target="github" href="https://github.com/unicode-org/message-format-wg/blob/main/spec/message.ebnf">EBNF</a>.</p>
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+@Deprecated
+@SuppressWarnings("javadoc")
+public class Mf2DataModel {
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public static class SelectorKeys {
+ private final List<String> keys;
+
+ private SelectorKeys(Builder builder) {
+ keys = new ArrayList<>();
+ keys.addAll(builder.keys);
+ }
+
+ /**
+ * Creates a builder.
+ *
+ * @return the Builder.
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public List<String> getKeys() {
+ return Collections.unmodifiableList(keys);
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ @Override
+ public String toString() {
+ StringJoiner result = new StringJoiner(" ");
+ for (String key : keys) {
+ result.add(key);
+ }
+ return result.toString();
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public static class Builder {
+ private final List<String> keys = new ArrayList<>();
+
+ // Prevent direct creation
+ private Builder() {
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Builder add(String key) {
+ keys.add(key);
+ return this;
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Builder addAll(Collection<String> otherKeys) {
+ this.keys.addAll(otherKeys);
+ return this;
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public SelectorKeys build() {
+ return new SelectorKeys(this);
+ }
+ }
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public static class Pattern {
+ private final List<Part> parts;
+
+ private Pattern(Builder builder) {
+ parts = new ArrayList<>();
+ parts.addAll(builder.parts);
+ }
+
+ /**
+ * Creates a builder.
+ *
+ * @return the Builder.
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public List<Part> getParts() {
+ return parts;
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ @Override
+ public String toString() {
+ StringBuilder result = new StringBuilder();
+ result.append("{");
+ for (Part part : parts) {
+ result.append(part);
+ }
+ result.append("}");
+ return result.toString();
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public static class Builder {
+ private final List<Part> parts = new ArrayList<>();
+
+ // Prevent direct creation
+ private Builder() {
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Builder add(Part part) {
+ parts.add(part);
+ return this;
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Builder addAll(Collection<Part> otherParts) {
+ parts.addAll(otherParts);
+ return this;
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Pattern build() {
+ return new Pattern(this);
+ }
+
+ }
+ }
+
+ /**
+ * No functional role, this is only to be able to say that a message is a sequence of Part(s),
+ * and that plain text {@link Text} and {@link Expression} are Part(s).
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public interface Part {
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public static class Text implements Part {
+ private final String value;
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ private Text(Builder builder) {
+ this(builder.value);
+ }
+
+ /**
+ * Creates a builder.
+ *
+ * @return the Builder.
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Text(String value) {
+ this.value = value;
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public String getValue() {
+ return value;
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ @Override
+ public String toString() {
+ return value;
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public static class Builder {
+ private String value;
+
+ // Prevent direct creation
+ private Builder() {
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Builder setValue(String value) {
+ this.value = value;
+ return this;
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Text build() {
+ return new Text(this);
+ }
+ }
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public static class Expression implements Part {
+ private final Value operand; // Literal | Variable
+ private final String functionName;
+ private final Map<String, Value> options;
+ Formatter formatter = null;
+
+ private Expression(Builder builder) {
+ this.operand = builder.operand;
+ this.functionName = builder.functionName;
+ this.options = builder.options;
+ }
+
+ /**
+ * Creates a builder.
+ *
+ * @return the Builder.
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Value getOperand() {
+ return operand;
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public String getFunctionName() {
+ return functionName;
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Map<String, Value> getOptions() {
+ return options;
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ @Override
+ public String toString() {
+ StringBuilder result = new StringBuilder();
+ result.append("{");
+ if (operand != null) {
+ result.append(operand);
+ }
+ if (functionName != null) {
+ result.append(" :").append(functionName);
+ }
+ for (Entry<String, Value> option : options.entrySet()) {
+ result.append(" ").append(option.getKey()).append("=").append(option.getValue());
+ }
+ result.append("}");
+ return result.toString();
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public static class Builder {
+ private Value operand = null;
+ private String functionName = null;
+ private final OrderedMap<String, Value> options = new OrderedMap<>();
+
+ // Prevent direct creation
+ private Builder() {
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Builder setOperand(Value operand) {
+ this.operand = operand;
+ return this;
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Builder setFunctionName(String functionName) {
+ this.functionName = functionName;
+ return this;
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Builder addOption(String key, Value value) {
+ options.put(key, value);
+ return this;
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Builder addOptions(Map<String, Value> otherOptions) {
+ options.putAll(otherOptions);
+ return this;
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Expression build() {
+ return new Expression(this);
+ }
+ }
+ }
+
+// public static class Placeholder extends Expression implements Part {
+// public Placeholder(Builder builder) {
+// super(builder);
+// }
+// }
+
+ /**
+ * A Value can be either a Literal, or a Variable, but not both.
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public static class Value {
+ private final String literal;
+ private final String variableName;
+
+ private Value(Builder builder) {
+ this.literal = builder.literal;
+ this.variableName = builder.variableName;
+// this(builder.literal, builder.variableName);
+ }
+
+ /**
+ * Creates a builder.
+ *
+ * @return the Builder.
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public String getLiteral() {
+ return literal;
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public String getVariableName() {
+ return variableName;
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public boolean isLiteral() {
+ return literal != null;
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public boolean isVariable() {
+ return variableName != null;
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ @Override
+ public String toString() {
+ return isLiteral() ? "(" + literal + ")" : "$" + variableName;
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public static class Builder {
+ private String literal;
+ private String variableName;
+
+ // Prevent direct creation
+ private Builder() {
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Builder setLiteral(String literal) {
+ this.literal = literal;
+ this.variableName = null;
+ return this;
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Builder setVariableName(String variableName) {
+ this.variableName = variableName;
+ this.literal = null;
+ return this;
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Value build() {
+ return new Value(this);
+ }
+ }
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public static class Variable {
+ private final String name;
+
+ private Variable(Builder builder) {
+ this.name = builder.name;
+ }
+
+ /**
+ * Creates a builder.
+ *
+ * @return the Builder.
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public static class Builder {
+ private String name;
+
+ // Prevent direct creation
+ private Builder() {
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Builder setName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Variable build() {
+ return new Variable(this);
+ }
+ }
+ }
+
+ /**
+ * This is only to not force LinkedHashMap on the public API.
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public static class OrderedMap<K, V> extends LinkedHashMap<K, V> {
+ private static final long serialVersionUID = -7049361727790825496L;
+
+ /**
+ * {@inheritDoc}
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public OrderedMap() {
+ super();
+ }
+ }
+
+ private final OrderedMap<String, Expression> localVariables;
+ private final List<Expression> selectors;
+ private final OrderedMap<SelectorKeys, Pattern> variants;
+ private final Pattern pattern;
+
+ private Mf2DataModel(Builder builder) {
+ this.localVariables = builder.localVariables;
+ this.selectors = builder.selectors;
+ this.variants = builder.variants;
+ this.pattern = builder.pattern;
+ }
+
+ /**
+ * Creates a builder.
+ *
+ * @return the Builder.
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public OrderedMap<String, Expression> getLocalVariables() {
+ return localVariables;
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public List<Expression> getSelectors() {
+ return selectors;
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public OrderedMap<SelectorKeys, Pattern> getVariants() {
+ return variants;
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Pattern getPattern() {
+ return pattern;
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ @Override
+ public String toString() {
+ StringBuilder result = new StringBuilder();
+ for (Entry<String, Expression> lv : localVariables.entrySet()) {
+ result.append("let $").append(lv.getKey());
+ result.append(" = ");
+ result.append(lv.getValue());
+ result.append("\n");
+ }
+ if (!selectors.isEmpty()) {
+ result.append("match");
+ for (Expression e : this.selectors) {
+ result.append(" ").append(e);
+ }
+ result.append("\n");
+ for (Entry<SelectorKeys, Pattern> variant : variants.entrySet()) {
+ result.append(" when ").append(variant.getKey());
+ result.append(" ");
+ result.append(variant.getValue());
+ result.append("\n");
+ }
+ } else {
+ result.append(pattern);
+ }
+ return result.toString();
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public static class Builder {
+ private final OrderedMap<String, Expression> localVariables = new OrderedMap<>(); // declaration*
+ private final List<Expression> selectors = new ArrayList<>();
+ private final OrderedMap<SelectorKeys, Pattern> variants = new OrderedMap<>();
+ private Pattern pattern = Pattern.builder().build();
+
+ // Prevent direct creation
+ private Builder() {
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Builder addLocalVariable(String variableName, Expression expression) {
+ this.localVariables.put(variableName, expression);
+ return this;
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Builder addLocalVariables(OrderedMap<String, Expression> otherLocalVariables) {
+ this.localVariables.putAll(otherLocalVariables);
+ return this;
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Builder addSelector(Expression otherSelector) {
+ this.selectors.add(otherSelector);
+ return this;
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Builder addSelectors(List<Expression> otherSelectors) {
+ this.selectors.addAll(otherSelectors);
+ return this;
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Builder addVariant(SelectorKeys keys, Pattern newPattern) {
+ this.variants.put(keys, newPattern);
+ return this;
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Builder addVariants(OrderedMap<SelectorKeys, Pattern> otherVariants) {
+ this.variants.putAll(otherVariants);
+ return this;
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Builder setPattern(Pattern pattern) {
+ this.pattern = pattern;
+ return this;
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Mf2DataModel build() {
+ return new Mf2DataModel(this);
+ }
+ }
+}
diff --git a/android_icu4j/src/main/java/android/icu/message2/Mf2DataModelFormatter.java b/android_icu4j/src/main/java/android/icu/message2/Mf2DataModelFormatter.java
new file mode 100644
index 0000000..6ff0f1e
--- /dev/null
+++ b/android_icu4j/src/main/java/android/icu/message2/Mf2DataModelFormatter.java
@@ -0,0 +1,281 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+// © 2022 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html
+
+package android.icu.message2;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import android.icu.message2.Mf2DataModel.Expression;
+import android.icu.message2.Mf2DataModel.Part;
+import android.icu.message2.Mf2DataModel.Pattern;
+import android.icu.message2.Mf2DataModel.SelectorKeys;
+import android.icu.message2.Mf2DataModel.Text;
+import android.icu.message2.Mf2DataModel.Value;
+import android.icu.util.Calendar;
+import android.icu.util.CurrencyAmount;
+
+/**
+ * Takes an {@link Mf2DataModel} and formats it to a {@link String}
+ * (and later on we will also implement formatting to a {@code FormattedMessage}).
+ */
+// TODO: move this in the MessageFormatter
+class Mf2DataModelFormatter {
+ private final Locale locale;
+ private final Mf2DataModel dm;
+
+ final Mf2FunctionRegistry standardFunctions;
+ final Mf2FunctionRegistry customFunctions;
+ private static final Mf2FunctionRegistry EMPTY_REGISTY = Mf2FunctionRegistry.builder().build();
+
+ Mf2DataModelFormatter(Mf2DataModel dm, Locale locale, Mf2FunctionRegistry customFunctionRegistry) {
+ this.locale = locale;
+ this.dm = dm;
+ this.customFunctions = customFunctionRegistry == null ? EMPTY_REGISTY : customFunctionRegistry;
+
+ standardFunctions = Mf2FunctionRegistry.builder()
+ // Date/time formatting
+ .setFormatter("datetime", new DateTimeFormatterFactory())
+ .setDefaultFormatterNameForType(Date.class, "datetime")
+ .setDefaultFormatterNameForType(Calendar.class, "datetime")
+
+ // Number formatting
+ .setFormatter("number", new NumberFormatterFactory())
+ .setDefaultFormatterNameForType(Integer.class, "number")
+ .setDefaultFormatterNameForType(Double.class, "number")
+ .setDefaultFormatterNameForType(Number.class, "number")
+ .setDefaultFormatterNameForType(CurrencyAmount.class, "number")
+
+ // Format that returns "to string"
+ .setFormatter("identity", new IdentityFormatterFactory())
+ .setDefaultFormatterNameForType(String.class, "identity")
+ .setDefaultFormatterNameForType(CharSequence.class, "identity")
+
+ // Register the standard selectors
+ .setSelector("plural", new PluralSelectorFactory("cardinal"))
+ .setSelector("selectordinal", new PluralSelectorFactory("ordinal"))
+ .setSelector("select", new TextSelectorFactory())
+ .setSelector("gender", new TextSelectorFactory())
+
+ .build();
+ }
+
+ private static Map<String, Object> mf2OptToFixedOptions(Map<String, Value> options) {
+ Map<String, Object> result = new HashMap<>();
+ for (Entry<String, Value> option : options.entrySet()) {
+ Value value = option.getValue();
+ if (value.isLiteral()) {
+ result.put(option.getKey(), value.getLiteral());
+ }
+ }
+ return result;
+ }
+
+ private Map<String, Object> mf2OptToVariableOptions(Map<String, Value> options, Map<String, Object> arguments) {
+ Map<String, Object> result = new HashMap<>();
+ for (Entry<String, Value> option : options.entrySet()) {
+ Value value = option.getValue();
+ if (value.isVariable()) {
+ result.put(option.getKey(), variableToObjectEx(value, arguments));
+ }
+ }
+ return result;
+ }
+
+ FormatterFactory getFormattingFunctionFactoryByName(Object toFormat, String functionName) {
+ // Get a function name from the type of the object to format
+ if (functionName == null || functionName.isEmpty()) {
+ if (toFormat == null) {
+ // The object to format is null, and no function provided.
+ return null;
+ }
+ Class<?> clazz = toFormat.getClass();
+ functionName = standardFunctions.getDefaultFormatterNameForType(clazz);
+ if (functionName == null) {
+ functionName = customFunctions.getDefaultFormatterNameForType(clazz);
+ }
+ if (functionName == null) {
+ throw new IllegalArgumentException("Object to format without a function, and unknown type: "
+ + toFormat.getClass().getName());
+ }
+ }
+
+ FormatterFactory func = standardFunctions.getFormatter(functionName);
+ if (func == null) {
+ func = customFunctions.getFormatter(functionName);
+ if (func == null) {
+ throw new IllegalArgumentException("Can't find an implementation for function: '"
+ + functionName + "'");
+ }
+ }
+ return func;
+ }
+
+ String format(Map<String, Object> arguments) {
+ List<Expression> selectors = dm.getSelectors();
+ Pattern patternToRender = selectors.isEmpty()
+ ? dm.getPattern()
+ : findBestMatchingPattern(selectors, arguments);
+
+ StringBuilder result = new StringBuilder();
+ for (Part part : patternToRender.getParts()) {
+ if (part instanceof Text) {
+ result.append(part);
+ } else if (part instanceof Expression) { // Placeholder is an Expression
+ FormattedPlaceholder fp = formatPlaceholder((Expression) part, arguments, false);
+ result.append(fp.toString());
+ } else {
+ throw new IllegalArgumentException("Unknown part type: " + part);
+ }
+ }
+ return result.toString();
+ }
+
+ private Pattern findBestMatchingPattern(List<Expression> selectors, Map<String, Object> arguments) {
+ Pattern patternToRender = null;
+
+ // Collect all the selector functions in an array, to reuse
+ List<Selector> selectorFunctions = new ArrayList<>(selectors.size());
+ for (Expression selector : selectors) {
+ String functionName = selector.getFunctionName();
+ SelectorFactory funcFactory = standardFunctions.getSelector(functionName);
+ if (funcFactory == null) {
+ funcFactory = customFunctions.getSelector(functionName);
+ }
+ if (funcFactory != null) {
+ Map<String, Object> opt = mf2OptToFixedOptions(selector.getOptions());
+ selectorFunctions.add(funcFactory.createSelector(locale, opt));
+ } else {
+ throw new IllegalArgumentException("Unknown selector type: " + functionName);
+ }
+ }
+ // This should not be possible, we added one function for each selector, or we have thrown an exception.
+ // But just in case someone removes the throw above?
+ if (selectorFunctions.size() != selectors.size()) {
+ throw new IllegalArgumentException("Something went wrong, not enough selector functions, "
+ + selectorFunctions.size() + " vs. " + selectors.size());
+ }
+
+ // Iterate "vertically", through all variants
+ for (Entry<SelectorKeys, Pattern> variant : dm.getVariants().entrySet()) {
+ int maxCount = selectors.size();
+ List<String> keysToCheck = variant.getKey().getKeys();
+ if (selectors.size() != keysToCheck.size()) {
+ throw new IllegalArgumentException("Mismatch between the number of selectors and the number of keys: "
+ + selectors.size() + " vs. " + keysToCheck.size());
+ }
+ boolean matches = true;
+ // Iterate "horizontally", through all matching functions and keys
+ for (int i = 0; i < maxCount; i++) {
+ Expression selector = selectors.get(i);
+ String valToCheck = keysToCheck.get(i);
+ Selector func = selectorFunctions.get(i);
+ Map<String, Object> options = mf2OptToVariableOptions(selector.getOptions(), arguments);
+ if (!func.matches(variableToObjectEx(selector.getOperand(), arguments), valToCheck, options)) {
+ matches = false;
+ break;
+ }
+ }
+ if (matches) {
+ patternToRender = variant.getValue();
+ break;
+ }
+ }
+
+ // TODO: check that there was an entry with all the keys set to `*`
+ // And should do that only once, when building the data model.
+ if (patternToRender == null) {
+ // If there was a case with all entries in the keys `*` this should not happen
+ throw new IllegalArgumentException("The selection went wrong, cannot select any option.");
+ }
+
+ return patternToRender;
+ }
+
+ /*
+ * Pass a level to prevent local variables calling each-other recursively:
+ *
+ * <code><pre>
+ * let $l1 = {$l4 :number}
+ * let $l2 = {$l1 :number}
+ * let $l3 = {$l2 :number}
+ * let $l4 = {$l3 :number}
+ * </pre></code>
+ *
+ * We can keep track of the calls (complicated and expensive).
+ * Or we can forbid the use of variables before they are declared, but that is not in the spec (yet?).
+ */
+ private Object variableToObjectEx(Value value, Map<String, Object> arguments) {
+ if (value == null) { // function only
+ return null;
+ }
+ // We have an operand. Can be literal, local var, or argument.
+ if (value.isLiteral()) {
+ return value.getLiteral();
+ } else if (value.isVariable()) {
+ String varName = value.getVariableName();
+ Expression localPh = dm.getLocalVariables().get(varName);
+ if (localPh != null) {
+ return formatPlaceholder(localPh, arguments, false);
+ }
+ return arguments.get(varName);
+ } else {
+ throw new IllegalArgumentException("Invalid operand type " + value);
+ }
+ }
+
+ private FormattedPlaceholder formatPlaceholder(Expression ph, Map<String, Object> arguments, boolean localExpression) {
+ Object toFormat;
+ Value operand = ph.getOperand();
+ if (operand == null) { // function only, "...{:currentOs option=value}..."
+ toFormat = null;
+ } else {
+ // We have an operand. Can be literal, local var, or argument.
+ if (operand.isLiteral()) { // "...{(1234.56) :number}..."
+ // If it is a literal, return the string itself
+ toFormat = operand.getLiteral();
+ } else if (operand.isVariable()) {
+ String varName = operand.getVariableName();
+ if (!localExpression) {
+ Expression localPh = dm.getLocalVariables().get(varName);
+ if (localPh != null) {
+ // If it is a local variable, we need to format that (recursive)
+ // TODO: See if there is any danger to eval the local variables only once
+ // (on demand in case the local var is not used, for example in a select)
+ return formatPlaceholder(localPh, arguments, true);
+ }
+ }
+ // Return the object in the argument bag.
+ toFormat = arguments.get(varName);
+ // toFormat might still be null here.
+ } else {
+ throw new IllegalArgumentException("Invalid operand type " + ph.getOperand());
+ }
+ }
+
+ if (ph.formatter == null) {
+ FormatterFactory funcFactory = getFormattingFunctionFactoryByName(toFormat, ph.getFunctionName());
+ if (funcFactory != null) {
+ Map<String, Object> fixedOptions = mf2OptToFixedOptions(ph.getOptions());
+ Formatter ff = funcFactory.createFormatter(locale, fixedOptions);
+ ph.formatter = ff;
+ }
+ }
+ if (ph.formatter != null) {
+ Map<String, Object> variableOptions = mf2OptToVariableOptions(ph.getOptions(), arguments);
+ try {
+ return ph.formatter.format(toFormat, variableOptions);
+ } catch (IllegalArgumentException e) {
+ // Fall-through to the name of the placeholder without replacement.
+ }
+ }
+
+ return new FormattedPlaceholder(toFormat, new PlainStringFormattedValue("{" + ph.getOperand() + "}"));
+ }
+}
diff --git a/android_icu4j/src/main/java/android/icu/message2/Mf2FunctionRegistry.java b/android_icu4j/src/main/java/android/icu/message2/Mf2FunctionRegistry.java
new file mode 100644
index 0000000..702b545
--- /dev/null
+++ b/android_icu4j/src/main/java/android/icu/message2/Mf2FunctionRegistry.java
@@ -0,0 +1,350 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+// © 2022 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html
+
+package android.icu.message2;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * This class is used to register mappings between various function
+ * names and the factories that can create those functions.
+ *
+ * <p>For example to add formatting for a {@code Person} object one would need to:</p>
+ * <ul>
+ * <li>write a function (class, lambda, etc.) that does the formatting proper
+ * (implementing {@link Formatter})</li>
+ * <li>write a factory that creates such a function
+ * (implementing {@link FormatterFactory})</li>
+ * <li>add a mapping from the function name as used in the syntax
+ * (for example {@code "person"}) to the factory</li>
+ * <li>optionally add a mapping from the class to format ({@code ...Person.class}) to
+ * the formatter name ({@code "person"}), so that one can use a placeholder in the message
+ * without specifying a function (for example {@code "... {$me} ..."} instead of
+ * {@code "... {$me :person} ..."}, if the class of {@code $me} is an {@code instanceof Person}).
+ * </li>
+ * </ul>
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+@Deprecated
+public class Mf2FunctionRegistry {
+ private final Map<String, FormatterFactory> formattersMap;
+ private final Map<String, SelectorFactory> selectorsMap;
+ private final Map<Class<?>, String> classToFormatter;
+
+ private Mf2FunctionRegistry(Builder builder) {
+ this.formattersMap = new HashMap<>(builder.formattersMap);
+ this.selectorsMap = new HashMap<>(builder.selectorsMap);
+ this.classToFormatter = new HashMap<>(builder.classToFormatter);
+ }
+
+ /**
+ * Creates a builder.
+ *
+ * @return the Builder.
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Returns the formatter factory used to create the formatter for function
+ * named {@code name}.
+ *
+ * <p>Note: function name here means the name used to refer to the function in the
+ * MessageFormat 2 syntax, for example {@code "... {$exp :datetime} ..."}<br>
+ * The function name here is {@code "datetime"}, and does not have to correspond to the
+ * name of the methods / classes used to implement the functionality.</p>
+ *
+ * <p>For example one might write a {@code PersonFormatterFactory} returning a {@code PersonFormatter},
+ * and map that to the MessageFormat function named {@code "person"}.<br>
+ * The only name visible to the users of MessageFormat syntax will be {@code "person"}.</p>
+ *
+ * @param formatterName the function name.
+ * @return the factory creating formatters for {@code name}. Returns {@code null} if none is registered.
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public FormatterFactory getFormatter(String formatterName) {
+ return formattersMap.get(formatterName);
+ }
+
+ /**
+ * Get all know names that have a mappings from name to {@link FormatterFactory}.
+ *
+ * @return a set of all the known formatter names.
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Set<String> getFormatterNames() {
+ return formattersMap.keySet();
+ }
+
+ /**
+ * Returns the name of the formatter used to format an object of type {@code clazz}.
+ *
+ * @param clazz the class of the object to format.
+ * @return the name of the formatter class, if registered. Returns {@code null} otherwise.
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public String getDefaultFormatterNameForType(Class<?> clazz) {
+ // Search for the class "as is", to save time.
+ // If we don't find it then we iterate the registered classes and check
+ // if the class is an instanceof the ones registered.
+ // For example a BuddhistCalendar when we only registered Calendar
+ String result = classToFormatter.get(clazz);
+ if (result != null) {
+ return result;
+ }
+ // We didn't find the class registered explicitly "as is"
+ for (Map.Entry<Class<?>, String> e : classToFormatter.entrySet()) {
+ if (e.getKey().isAssignableFrom(clazz)) {
+ return e.getValue();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Get all know classes that have a mappings from class to function name.
+ *
+ * @return a set of all the known classes that have mapping to function names.
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Set<Class<?>> getDefaultFormatterTypes() {
+ return classToFormatter.keySet();
+ }
+
+ /**
+ * Returns the selector factory used to create the selector for function
+ * named {@code name}.
+ *
+ * <p>Note: the same comments about naming as the ones on {@code getFormatter} apply.</p>
+ *
+ * @param selectorName the selector name.
+ * @return the factory creating selectors for {@code name}. Returns {@code null} if none is registered.
+ * @see #getFormatter(String)
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public SelectorFactory getSelector(String selectorName) {
+ return selectorsMap.get(selectorName);
+ }
+
+ /**
+ * Get all know names that have a mappings from name to {@link SelectorFactory}.
+ *
+ * @return a set of all the known selector names.
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Set<String> getSelectorNames() {
+ return selectorsMap.keySet();
+ }
+
+ /**
+ * A {@code Builder} used to build instances of {@link Mf2FunctionRegistry}.
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public static class Builder {
+ private final Map<String, FormatterFactory> formattersMap = new HashMap<>();
+ private final Map<String, SelectorFactory> selectorsMap = new HashMap<>();
+ private final Map<Class<?>, String> classToFormatter = new HashMap<>();
+
+ // Prevent direct creation
+ private Builder() {
+ }
+
+ /**
+ * Adds all the mapping from another registry to this one.
+ *
+ * @param functionRegistry the registry to copy from.
+ * @return the builder, for fluent use.
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Builder addAll(Mf2FunctionRegistry functionRegistry) {
+ formattersMap.putAll(functionRegistry.formattersMap);
+ selectorsMap.putAll(functionRegistry.selectorsMap);
+ classToFormatter.putAll(functionRegistry.classToFormatter);
+ return this;
+ }
+
+ /**
+ * Adds a mapping from a formatter name to a {@link FormatterFactory}
+ *
+ * @param formatterName the function name (as used in the MessageFormat 2 syntax).
+ * @param formatterFactory the factory that handles the name.
+ * @return the builder, for fluent use.
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Builder setFormatter(String formatterName, FormatterFactory formatterFactory) {
+ formattersMap.put(formatterName, formatterFactory);
+ return this;
+ }
+
+ /**
+ * Remove the formatter associated with the name.
+ *
+ * @param formatterName the name of the formatter to remove.
+ * @return the builder, for fluent use.
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Builder removeFormatter(String formatterName) {
+ formattersMap.remove(formatterName);
+ return this;
+ }
+
+ /**
+ * Remove all the formatter mappings.
+ *
+ * @return the builder, for fluent use.
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Builder clearFormatters() {
+ formattersMap.clear();
+ return this;
+ }
+
+ /**
+ * Adds a mapping from a type to format to a {@link FormatterFactory} formatter name.
+ *
+ * @param clazz the class of the type to format.
+ * @param formatterName the formatter name (as used in the MessageFormat 2 syntax).
+ * @return the builder, for fluent use.
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Builder setDefaultFormatterNameForType(Class<?> clazz, String formatterName) {
+ classToFormatter.put(clazz, formatterName);
+ return this;
+ }
+
+ /**
+ * Remove the function name associated with the class.
+ *
+ * @param clazz the class to remove the mapping for.
+ * @return the builder, for fluent use.
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Builder removeDefaultFormatterNameForType(Class<?> clazz) {
+ classToFormatter.remove(clazz);
+ return this;
+ }
+
+ /**
+ * Remove all the class to formatter-names mappings.
+ *
+ * @return the builder, for fluent use.
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Builder clearDefaultFormatterNames() {
+ classToFormatter.clear();
+ return this;
+ }
+
+ /**
+ * Adds a mapping from a selector name to a {@link SelectorFactory}
+ *
+ * @param selectorName the function name (as used in the MessageFormat 2 syntax).
+ * @param selectorFactory the factory that handles the name.
+ * @return the builder, for fluent use.
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Builder setSelector(String selectorName, SelectorFactory selectorFactory) {
+ selectorsMap.put(selectorName, selectorFactory);
+ return this;
+ }
+
+ /**
+ * Remove the selector associated with the name.
+ *
+ * @param selectorName the name of the selector to remove.
+ * @return the builder, for fluent use.
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Builder removeSelector(String selectorName) {
+ selectorsMap.remove(selectorName);
+ return this;
+ }
+
+ /**
+ * Remove all the selector mappings.
+ *
+ * @return the builder, for fluent use.
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Builder clearSelectors() {
+ selectorsMap.clear();
+ return this;
+ }
+
+ /**
+ * Builds an instance of {@link Mf2FunctionRegistry}.
+ *
+ * @return the function registry created.
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Mf2FunctionRegistry build() {
+ return new Mf2FunctionRegistry(this);
+ }
+ }
+}
diff --git a/android_icu4j/src/main/java/android/icu/message2/Mf2Parser.java b/android_icu4j/src/main/java/android/icu/message2/Mf2Parser.java
new file mode 100644
index 0000000..5445212
--- /dev/null
+++ b/android_icu4j/src/main/java/android/icu/message2/Mf2Parser.java
@@ -0,0 +1,770 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+// © 2022 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html
+
+package android.icu.message2;
+
+import java.util.Arrays;
+
+/**
+ * Class generated from EBNF.
+ */
+@SuppressWarnings("all") // Disable all warnings in the generated file
+class Mf2Parser
+{
+ static class ParseException extends RuntimeException
+ {
+ private static final long serialVersionUID = 1L;
+ private int begin, end, offending, expected, state;
+
+ public ParseException(int b, int e, int s, int o, int x)
+ {
+ begin = b;
+ end = e;
+ state = s;
+ offending = o;
+ expected = x;
+ }
+
+ @Override
+ public String getMessage()
+ {
+ return offending < 0
+ ? "lexical analysis failed"
+ : "syntax error";
+ }
+
+ public void serialize(EventHandler eventHandler)
+ {
+ }
+
+ public int getBegin() {return begin;}
+ public int getEnd() {return end;}
+ public int getState() {return state;}
+ public int getOffending() {return offending;}
+ public int getExpected() {return expected;}
+ public boolean isAmbiguousInput() {return false;}
+ }
+
+ /**
+ * @hide Only a subset of ICU is exposed in Android
+ */
+public interface EventHandler
+ {
+ public void reset(CharSequence string);
+ public void startNonterminal(String name, int begin);
+ public void endNonterminal(String name, int end);
+ public void terminal(String name, int begin, int end);
+ public void whitespace(int begin, int end);
+ }
+
+ /**
+ * @hide Only a subset of ICU is exposed in Android
+ */
+public static class TopDownTreeBuilder implements EventHandler
+ {
+ private CharSequence input = null;
+ public Nonterminal[] stack = new Nonterminal[64];
+ private int top = -1;
+
+ @Override
+ public void reset(CharSequence input)
+ {
+ this.input = input;
+ top = -1;
+ }
+
+ @Override
+ public void startNonterminal(String name, int begin)
+ {
+ Nonterminal nonterminal = new Nonterminal(name, begin, begin, new Symbol[0]);
+ if (top >= 0) addChild(nonterminal);
+ if (++top >= stack.length) stack = Arrays.copyOf(stack, stack.length << 1);
+ stack[top] = nonterminal;
+ }
+
+ @Override
+ public void endNonterminal(String name, int end)
+ {
+ stack[top].end = end;
+ if (top > 0) --top;
+ }
+
+ @Override
+ public void terminal(String name, int begin, int end)
+ {
+ addChild(new Terminal(name, begin, end));
+ }
+
+ @Override
+ public void whitespace(int begin, int end)
+ {
+ }
+
+ private void addChild(Symbol s)
+ {
+ Nonterminal current = stack[top];
+ current.children = Arrays.copyOf(current.children, current.children.length + 1);
+ current.children[current.children.length - 1] = s;
+ }
+
+ public void serialize(EventHandler e)
+ {
+ e.reset(input);
+ stack[0].send(e);
+ }
+ }
+
+ /**
+ * @hide Only a subset of ICU is exposed in Android
+ */
+public static abstract class Symbol
+ {
+ public String name;
+ public int begin;
+ public int end;
+
+ protected Symbol(String name, int begin, int end)
+ {
+ this.name = name;
+ this.begin = begin;
+ this.end = end;
+ }
+
+ public abstract void send(EventHandler e);
+ }
+
+ /**
+ * @hide Only a subset of ICU is exposed in Android
+ */
+public static class Terminal extends Symbol
+ {
+ public Terminal(String name, int begin, int end)
+ {
+ super(name, begin, end);
+ }
+
+ @Override
+ public void send(EventHandler e)
+ {
+ e.terminal(name, begin, end);
+ }
+ }
+
+ /**
+ * @hide Only a subset of ICU is exposed in Android
+ */
+public static class Nonterminal extends Symbol
+ {
+ public Symbol[] children;
+
+ public Nonterminal(String name, int begin, int end, Symbol[] children)
+ {
+ super(name, begin, end);
+ this.children = children;
+ }
+
+ @Override
+ public void send(EventHandler e)
+ {
+ e.startNonterminal(name, begin);
+ int pos = begin;
+ for (Symbol c : children)
+ {
+ if (pos < c.begin) e.whitespace(pos, c.begin);
+ c.send(e);
+ pos = c.end;
+ }
+ if (pos < end) e.whitespace(pos, end);
+ e.endNonterminal(name, end);
+ }
+ }
+
+ public Mf2Parser(CharSequence string, EventHandler t)
+ {
+ initialize(string, t);
+ }
+
+ public void initialize(CharSequence source, EventHandler parsingEventHandler)
+ {
+ eventHandler = parsingEventHandler;
+ input = source;
+ size = source.length();
+ reset(0, 0, 0);
+ }
+
+ public CharSequence getInput()
+ {
+ return input;
+ }
+
+ public int getTokenOffset()
+ {
+ return b0;
+ }
+
+ public int getTokenEnd()
+ {
+ return e0;
+ }
+
+ public final void reset(int l, int b, int e)
+ {
+ b0 = b; e0 = b;
+ l1 = l; b1 = b; e1 = e;
+ end = e;
+ eventHandler.reset(input);
+ }
+
+ public void reset()
+ {
+ reset(0, 0, 0);
+ }
+
+ public static String getOffendingToken(ParseException e)
+ {
+ return e.getOffending() < 0 ? null : TOKEN[e.getOffending()];
+ }
+
+ public static String[] getExpectedTokenSet(ParseException e)
+ {
+ String[] expected;
+ if (e.getExpected() >= 0)
+ {
+ expected = new String[]{TOKEN[e.getExpected()]};
+ }
+ else
+ {
+ expected = getTokenSet(- e.getState());
+ }
+ return expected;
+ }
+
+ public String getErrorMessage(ParseException e)
+ {
+ String message = e.getMessage();
+ String[] tokenSet = getExpectedTokenSet(e);
+ String found = getOffendingToken(e);
+ int size = e.getEnd() - e.getBegin();
+ message += (found == null ? "" : ", found " + found)
+ + "\nwhile expecting "
+ + (tokenSet.length == 1 ? tokenSet[0] : java.util.Arrays.toString(tokenSet))
+ + "\n"
+ + (size == 0 || found != null ? "" : "after successfully scanning " + size + " characters beginning ");
+ String prefix = input.subSequence(0, e.getBegin()).toString();
+ int line = prefix.replaceAll("[^\n]", "").length() + 1;
+ int column = prefix.length() - prefix.lastIndexOf('\n');
+ return message
+ + "at line " + line + ", column " + column + ":\n..."
+ + input.subSequence(e.getBegin(), Math.min(input.length(), e.getBegin() + 64))
+ + "...";
+ }
+
+ public void parse_Message()
+ {
+ eventHandler.startNonterminal("Message", e0);
+ for (;;)
+ {
+ lookahead1W(12); // WhiteSpace | 'let' | 'match' | '{'
+ if (l1 != 13) // 'let'
+ {
+ break;
+ }
+ whitespace();
+ parse_Declaration();
+ }
+ switch (l1)
+ {
+ case 16: // '{'
+ whitespace();
+ parse_Pattern();
+ break;
+ default:
+ whitespace();
+ parse_Selector();
+ for (;;)
+ {
+ whitespace();
+ parse_Variant();
+ lookahead1W(4); // END | WhiteSpace | 'when'
+ if (l1 != 15) // 'when'
+ {
+ break;
+ }
+ }
+ }
+ eventHandler.endNonterminal("Message", e0);
+ }
+
+ private void parse_Declaration()
+ {
+ eventHandler.startNonterminal("Declaration", e0);
+ consume(13); // 'let'
+ lookahead1W(0); // WhiteSpace | Variable
+ consume(4); // Variable
+ lookahead1W(1); // WhiteSpace | '='
+ consume(12); // '='
+ lookahead1W(2); // WhiteSpace | '{'
+ consume(16); // '{'
+ lookahead1W(9); // WhiteSpace | Variable | Function | Literal
+ whitespace();
+ parse_Expression();
+ consume(17); // '}'
+ eventHandler.endNonterminal("Declaration", e0);
+ }
+
+ private void parse_Selector()
+ {
+ eventHandler.startNonterminal("Selector", e0);
+ consume(14); // 'match'
+ for (;;)
+ {
+ lookahead1W(2); // WhiteSpace | '{'
+ consume(16); // '{'
+ lookahead1W(9); // WhiteSpace | Variable | Function | Literal
+ whitespace();
+ parse_Expression();
+ consume(17); // '}'
+ lookahead1W(7); // WhiteSpace | 'when' | '{'
+ if (l1 != 16) // '{'
+ {
+ break;
+ }
+ }
+ eventHandler.endNonterminal("Selector", e0);
+ }
+
+ private void parse_Variant()
+ {
+ eventHandler.startNonterminal("Variant", e0);
+ consume(15); // 'when'
+ for (;;)
+ {
+ lookahead1W(11); // WhiteSpace | Nmtoken | Literal | '*'
+ whitespace();
+ parse_VariantKey();
+ lookahead1W(13); // WhiteSpace | Nmtoken | Literal | '*' | '{'
+ if (l1 == 16) // '{'
+ {
+ break;
+ }
+ }
+ whitespace();
+ parse_Pattern();
+ eventHandler.endNonterminal("Variant", e0);
+ }
+
+ private void parse_VariantKey()
+ {
+ eventHandler.startNonterminal("VariantKey", e0);
+ switch (l1)
+ {
+ case 10: // Literal
+ consume(10); // Literal
+ break;
+ case 9: // Nmtoken
+ consume(9); // Nmtoken
+ break;
+ default:
+ consume(11); // '*'
+ }
+ eventHandler.endNonterminal("VariantKey", e0);
+ }
+
+ private void parse_Pattern()
+ {
+ eventHandler.startNonterminal("Pattern", e0);
+ consume(16); // '{'
+ for (;;)
+ {
+ lookahead1(8); // Text | '{' | '}'
+ if (l1 == 17) // '}'
+ {
+ break;
+ }
+ switch (l1)
+ {
+ case 3: // Text
+ consume(3); // Text
+ break;
+ default:
+ parse_Placeholder();
+ }
+ }
+ consume(17); // '}'
+ eventHandler.endNonterminal("Pattern", e0);
+ }
+
+ private void parse_Placeholder()
+ {
+ eventHandler.startNonterminal("Placeholder", e0);
+ consume(16); // '{'
+ lookahead1W(14); // WhiteSpace | Variable | Function | MarkupStart | MarkupEnd | Literal | '}'
+ if (l1 != 17) // '}'
+ {
+ switch (l1)
+ {
+ case 6: // MarkupStart
+ whitespace();
+ parse_Markup();
+ break;
+ case 7: // MarkupEnd
+ consume(7); // MarkupEnd
+ break;
+ default:
+ whitespace();
+ parse_Expression();
+ }
+ }
+ lookahead1W(3); // WhiteSpace | '}'
+ consume(17); // '}'
+ eventHandler.endNonterminal("Placeholder", e0);
+ }
+
+ private void parse_Expression()
+ {
+ eventHandler.startNonterminal("Expression", e0);
+ switch (l1)
+ {
+ case 5: // Function
+ parse_Annotation();
+ break;
+ default:
+ parse_Operand();
+ lookahead1W(5); // WhiteSpace | Function | '}'
+ if (l1 == 5) // Function
+ {
+ whitespace();
+ parse_Annotation();
+ }
+ }
+ eventHandler.endNonterminal("Expression", e0);
+ }
+
+ private void parse_Operand()
+ {
+ eventHandler.startNonterminal("Operand", e0);
+ switch (l1)
+ {
+ case 10: // Literal
+ consume(10); // Literal
+ break;
+ default:
+ consume(4); // Variable
+ }
+ eventHandler.endNonterminal("Operand", e0);
+ }
+
+ private void parse_Annotation()
+ {
+ eventHandler.startNonterminal("Annotation", e0);
+ consume(5); // Function
+ for (;;)
+ {
+ lookahead1W(6); // WhiteSpace | Name | '}'
+ if (l1 != 8) // Name
+ {
+ break;
+ }
+ whitespace();
+ parse_Option();
+ }
+ eventHandler.endNonterminal("Annotation", e0);
+ }
+
+ private void parse_Option()
+ {
+ eventHandler.startNonterminal("Option", e0);
+ consume(8); // Name
+ lookahead1W(1); // WhiteSpace | '='
+ consume(12); // '='
+ lookahead1W(10); // WhiteSpace | Variable | Nmtoken | Literal
+ switch (l1)
+ {
+ case 10: // Literal
+ consume(10); // Literal
+ break;
+ case 9: // Nmtoken
+ consume(9); // Nmtoken
+ break;
+ default:
+ consume(4); // Variable
+ }
+ eventHandler.endNonterminal("Option", e0);
+ }
+
+ private void parse_Markup()
+ {
+ eventHandler.startNonterminal("Markup", e0);
+ consume(6); // MarkupStart
+ for (;;)
+ {
+ lookahead1W(6); // WhiteSpace | Name | '}'
+ if (l1 != 8) // Name
+ {
+ break;
+ }
+ whitespace();
+ parse_Option();
+ }
+ eventHandler.endNonterminal("Markup", e0);
+ }
+
+ private void consume(int t)
+ {
+ if (l1 == t)
+ {
+ whitespace();
+ eventHandler.terminal(TOKEN[l1], b1, e1);
+ b0 = b1; e0 = e1; l1 = 0;
+ }
+ else
+ {
+ error(b1, e1, 0, l1, t);
+ }
+ }
+
+ private void whitespace()
+ {
+ if (e0 != b1)
+ {
+ eventHandler.whitespace(e0, b1);
+ e0 = b1;
+ }
+ }
+
+ private int matchW(int tokenSetId)
+ {
+ int code;
+ for (;;)
+ {
+ code = match(tokenSetId);
+ if (code != 2) // WhiteSpace
+ {
+ break;
+ }
+ }
+ return code;
+ }
+
+ private void lookahead1W(int tokenSetId)
+ {
+ if (l1 == 0)
+ {
+ l1 = matchW(tokenSetId);
+ b1 = begin;
+ e1 = end;
+ }
+ }
+
+ private void lookahead1(int tokenSetId)
+ {
+ if (l1 == 0)
+ {
+ l1 = match(tokenSetId);
+ b1 = begin;
+ e1 = end;
+ }
+ }
+
+ private int error(int b, int e, int s, int l, int t)
+ {
+ throw new ParseException(b, e, s, l, t);
+ }
+
+ private int b0, e0;
+ private int l1, b1, e1;
+ private EventHandler eventHandler = null;
+ private CharSequence input = null;
+ private int size = 0;
+ private int begin = 0;
+ private int end = 0;
+
+ private int match(int tokenSetId)
+ {
+ begin = end;
+ int current = end;
+ int result = INITIAL[tokenSetId];
+ int state = 0;
+
+ for (int code = result & 63; code != 0; )
+ {
+ int charclass;
+ int c0 = current < size ? input.charAt(current) : 0;
+ ++current;
+ if (c0 < 0x80)
+ {
+ charclass = MAP0[c0];
+ }
+ else if (c0 < 0xd800)
+ {
+ int c1 = c0 >> 4;
+ charclass = MAP1[(c0 & 15) + MAP1[(c1 & 31) + MAP1[c1 >> 5]]];
+ }
+ else
+ {
+ if (c0 < 0xdc00)
+ {
+ int c1 = current < size ? input.charAt(current) : 0;
+ if (c1 >= 0xdc00 && c1 < 0xe000)
+ {
+ ++current;
+ c0 = ((c0 & 0x3ff) << 10) + (c1 & 0x3ff) + 0x10000;
+ }
+ }
+
+ int lo = 0, hi = 6;
+ for (int m = 3; ; m = (hi + lo) >> 1)
+ {
+ if (MAP2[m] > c0) {hi = m - 1;}
+ else if (MAP2[7 + m] < c0) {lo = m + 1;}
+ else {charclass = MAP2[14 + m]; break;}
+ if (lo > hi) {charclass = 0; break;}
+ }
+ }
+
+ state = code;
+ int i0 = (charclass << 6) + code - 1;
+ code = TRANSITION[(i0 & 7) + TRANSITION[i0 >> 3]];
+
+ if (code > 63)
+ {
+ result = code;
+ code &= 63;
+ end = current;
+ }
+ }
+
+ result >>= 6;
+ if (result == 0)
+ {
+ end = current - 1;
+ int c1 = end < size ? input.charAt(end) : 0;
+ if (c1 >= 0xdc00 && c1 < 0xe000)
+ {
+ --end;
+ }
+ return error(begin, end, state, -1, -1);
+ }
+
+ if (end > size) end = size;
+ return (result & 31) - 1;
+ }
+
+ private static String[] getTokenSet(int tokenSetId)
+ {
+ java.util.ArrayList<String> expected = new java.util.ArrayList<>();
+ int s = tokenSetId < 0 ? - tokenSetId : INITIAL[tokenSetId] & 63;
+ for (int i = 0; i < 18; i += 32)
+ {
+ int j = i;
+ int i0 = (i >> 5) * 38 + s - 1;
+ int f = EXPECTED[i0];
+ for ( ; f != 0; f >>>= 1, ++j)
+ {
+ if ((f & 1) != 0)
+ {
+ expected.add(TOKEN[j]);
+ }
+ }
+ }
+ return expected.toArray(new String[]{});
+ }
+
+ private static final int[] MAP0 =
+ {
+ /* 0 */ 24, 24, 24, 24, 24, 24, 24, 24, 24, 1, 1, 24, 24, 1, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+ /* 27 */ 24, 24, 24, 24, 24, 1, 24, 24, 24, 2, 24, 24, 24, 3, 4, 5, 6, 24, 7, 8, 24, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ /* 58 */ 9, 24, 24, 10, 24, 24, 24, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
+ /* 85 */ 11, 11, 11, 11, 11, 11, 24, 12, 24, 24, 11, 24, 13, 11, 14, 11, 15, 11, 11, 16, 11, 11, 11, 17, 18, 19,
+ /* 111 */ 11, 11, 11, 11, 11, 20, 11, 11, 21, 11, 11, 11, 22, 24, 23, 24, 24
+ };
+
+ private static final int[] MAP1 =
+ {
+ /* 0 */ 108, 124, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 156, 181, 181, 181, 181,
+ /* 21 */ 181, 214, 215, 213, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214,
+ /* 42 */ 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214,
+ /* 63 */ 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214,
+ /* 84 */ 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214,
+ /* 105 */ 214, 214, 214, 383, 330, 396, 353, 291, 262, 247, 308, 330, 330, 330, 322, 292, 284, 292, 284, 292, 292,
+ /* 126 */ 292, 292, 292, 292, 292, 292, 292, 292, 292, 292, 292, 292, 292, 292, 347, 347, 347, 347, 347, 347, 347,
+ /* 147 */ 277, 292, 292, 292, 292, 292, 292, 292, 292, 369, 330, 330, 331, 329, 330, 330, 292, 292, 292, 292, 292,
+ /* 168 */ 292, 292, 292, 292, 292, 292, 292, 292, 292, 292, 292, 292, 292, 330, 330, 330, 330, 330, 330, 330, 330,
+ /* 189 */ 330, 330, 330, 330, 330, 330, 330, 330, 330, 330, 330, 330, 330, 330, 330, 330, 330, 330, 330, 330, 330,
+ /* 210 */ 330, 330, 330, 291, 292, 292, 292, 292, 292, 292, 292, 292, 292, 292, 292, 292, 292, 292, 292, 292, 292,
+ /* 231 */ 292, 292, 292, 292, 292, 292, 292, 292, 292, 292, 292, 292, 292, 292, 292, 330, 24, 13, 11, 14, 11, 15,
+ /* 253 */ 11, 11, 16, 11, 11, 11, 17, 18, 19, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 24, 12, 24, 24, 11, 11,
+ /* 279 */ 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 24, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
+ /* 305 */ 11, 11, 11, 11, 11, 11, 11, 20, 11, 11, 21, 11, 11, 11, 22, 24, 23, 24, 24, 24, 24, 24, 24, 24, 8, 24, 24,
+ /* 332 */ 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ /* 363 */ 9, 24, 24, 10, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 11, 11, 24, 24, 24, 24, 24, 24, 24,
+ /* 390 */ 24, 24, 1, 1, 24, 24, 1, 24, 24, 24, 2, 24, 24, 24, 3, 4, 5, 6, 24, 7, 8, 24
+ };
+
+ private static final int[] MAP2 =
+ {
+ /* 0 */ 55296, 63744, 64976, 65008, 65534, 65536, 983040, 63743, 64975, 65007, 65533, 65535, 983039, 1114111, 24,
+ /* 15 */ 11, 24, 11, 24, 11, 24
+ };
+
+ private static final int[] INITIAL =
+ {
+ /* 0 */ 1, 2, 3, 4, 133, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
+ };
+
+ private static final int[] TRANSITION =
+ {
+ /* 0 */ 237, 237, 237, 237, 237, 237, 237, 237, 200, 208, 455, 237, 237, 237, 237, 237, 236, 230, 455, 237, 237,
+ /* 21 */ 237, 237, 237, 237, 245, 376, 382, 237, 237, 237, 237, 237, 380, 314, 382, 237, 237, 237, 237, 237, 263,
+ /* 42 */ 455, 237, 237, 237, 237, 237, 237, 295, 455, 237, 237, 237, 237, 237, 237, 322, 287, 281, 252, 237, 237,
+ /* 63 */ 237, 237, 344, 287, 281, 252, 237, 237, 237, 255, 358, 455, 237, 237, 237, 237, 237, 417, 380, 455, 237,
+ /* 84 */ 237, 237, 237, 237, 419, 390, 215, 329, 252, 237, 237, 237, 237, 398, 275, 382, 237, 237, 237, 237, 419,
+ /* 105 */ 390, 215, 410, 252, 237, 237, 237, 419, 390, 215, 329, 309, 237, 237, 237, 419, 390, 222, 365, 252, 237,
+ /* 126 */ 237, 237, 419, 390, 427, 329, 302, 237, 237, 237, 419, 435, 215, 329, 252, 237, 237, 237, 419, 443, 215,
+ /* 147 */ 329, 252, 237, 237, 237, 419, 390, 215, 329, 372, 237, 237, 237, 419, 390, 215, 336, 451, 237, 237, 237,
+ /* 168 */ 402, 390, 215, 329, 252, 237, 237, 237, 350, 463, 269, 237, 237, 237, 237, 237, 474, 471, 269, 237, 237,
+ /* 189 */ 237, 237, 237, 237, 380, 455, 237, 237, 237, 237, 237, 192, 192, 192, 192, 192, 192, 192, 192, 277, 192,
+ /* 210 */ 192, 192, 192, 192, 192, 0, 414, 595, 0, 277, 22, 663, 0, 414, 595, 0, 277, 22, 663, 32, 277, 16, 16, 0,
+ /* 234 */ 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 277, 22, 22, 22, 0, 22, 22, 0, 482, 547, 0, 0, 0, 0, 0, 18, 0, 0, 277,
+ /* 264 */ 0, 0, 768, 0, 768, 0, 0, 0, 277, 0, 22, 0, 0, 0, 277, 20, 31, 0, 0, 0, 348, 0, 414, 0, 0, 595, 0, 277, 22,
+ /* 293 */ 663, 0, 277, 0, 0, 0, 0, 0, 26, 0, 482, 547, 0, 0, 960, 0, 0, 482, 547, 0, 38, 0, 0, 0, 0, 277, 704, 0, 0,
+ /* 322 */ 277, 0, 663, 663, 0, 663, 27, 0, 482, 547, 348, 0, 414, 0, 0, 482, 547, 348, 0, 414, 0, 896, 277, 0, 663,
+ /* 347 */ 663, 0, 663, 0, 0, 1088, 0, 0, 0, 0, 1088, 277, 18, 0, 0, 0, 0, 18, 0, 482, 547, 348, 36, 414, 0, 0, 482,
+ /* 374 */ 547, 1024, 0, 0, 0, 0, 277, 0, 0, 0, 0, 0, 0, 0, 22, 0, 277, 0, 663, 663, 0, 663, 0, 348, 20, 0, 0, 0, 0,
+ /* 403 */ 0, 0, 0, 17, 0, 595, 17, 33, 482, 547, 348, 0, 414, 0, 0, 832, 0, 0, 0, 0, 0, 0, 595, 0, 29, 414, 595, 0,
+ /* 431 */ 277, 22, 663, 0, 277, 0, 663, 663, 24, 663, 0, 348, 277, 0, 663, 663, 25, 663, 0, 348, 37, 482, 547, 0, 0,
+ /* 456 */ 0, 0, 0, 277, 22, 0, 0, 1088, 0, 0, 0, 1088, 1088, 0, 0, 1152, 0, 0, 0, 0, 0, 1152, 0, 1152, 1152, 0
+ };
+
+ private static final int[] EXPECTED =
+ {
+ /* 0 */ 20, 4100, 65540, 131076, 32772, 131108, 131332, 98308, 196616, 1076, 1556, 3588, 90116, 69124, 132340, 16,
+ /* 16 */ 32768, 32, 256, 8, 8, 1024, 512, 8192, 16384, 64, 128, 16, 32768, 32, 1024, 8192, 16384, 64, 128, 32768,
+ /* 36 */ 16384, 16384
+ };
+
+ private static final String[] TOKEN =
+ {
+ "(0)",
+ "END",
+ "WhiteSpace",
+ "Text",
+ "Variable",
+ "Function",
+ "MarkupStart",
+ "MarkupEnd",
+ "Name",
+ "Nmtoken",
+ "Literal",
+ "'*'",
+ "'='",
+ "'let'",
+ "'match'",
+ "'when'",
+ "'{'",
+ "'}'"
+ };
+}
+
+// End
diff --git a/android_icu4j/src/main/java/android/icu/message2/Mf2Serializer.java b/android_icu4j/src/main/java/android/icu/message2/Mf2Serializer.java
new file mode 100644
index 0000000..64ae13e
--- /dev/null
+++ b/android_icu4j/src/main/java/android/icu/message2/Mf2Serializer.java
@@ -0,0 +1,501 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+// © 2022 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html
+
+package android.icu.message2;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import android.icu.message2.Mf2DataModel.Expression;
+import android.icu.message2.Mf2DataModel.Pattern;
+import android.icu.message2.Mf2DataModel.SelectorKeys;
+import android.icu.message2.Mf2DataModel.Text;
+import android.icu.message2.Mf2DataModel.Value;
+import android.icu.message2.Mf2Parser.EventHandler;
+import android.icu.message2.Mf2Serializer.Token.Type;
+
+// TODO: find a better name for this class
+class Mf2Serializer implements EventHandler {
+ private String input;
+ private final List<Token> tokens = new ArrayList<>();
+
+ static class Token {
+ final String name;
+ final int begin;
+ final int end;
+ final Kind kind;
+ private final Type type;
+ private final String input;
+
+ enum Kind {
+ TERMINAL,
+ NONTERMINAL_START,
+ NONTERMINAL_END
+ }
+
+ enum Type {
+ MESSAGE,
+ PATTERN,
+ TEXT,
+ PLACEHOLDER,
+ EXPRESSION,
+ OPERAND,
+ VARIABLE,
+ IGNORE,
+ FUNCTION,
+ OPTION,
+ NAME,
+ NMTOKEN,
+ LITERAL,
+ SELECTOR,
+ VARIANT,
+ DECLARATION, VARIANTKEY, DEFAULT,
+ }
+
+ Token(Kind kind, String name, int begin, int end, String input) {
+ this.kind = kind;
+ this.name = name;
+ this.begin = begin;
+ this.end = end;
+ this.input = input;
+ switch (name) {
+ case "Message": type = Type.MESSAGE; break;
+ case "Pattern": type = Type.PATTERN; break;
+ case "Text": type = Type.TEXT; break;
+ case "Placeholder": type = Type.PLACEHOLDER; break;
+ case "Expression": type = Type.EXPRESSION; break;
+ case "Operand": type = Type.OPERAND; break;
+ case "Variable": type = Type.VARIABLE; break;
+ case "Function": type = Type.FUNCTION; break;
+ case "Option": type = Type.OPTION; break;
+ case "Annotation": type = Type.IGNORE; break;
+ case "Name": type = Type.NAME; break;
+ case "Nmtoken": type = Type.NMTOKEN; break;
+ case "Literal": type = Type.LITERAL; break;
+ case "Selector": type = Type.SELECTOR; break;
+ case "Variant": type = Type.VARIANT; break;
+ case "VariantKey": type = Type.VARIANTKEY; break;
+ case "Declaration": type = Type.DECLARATION; break;
+
+ case "Markup": type = Type.IGNORE; break;
+ case "MarkupStart": type = Type.IGNORE; break;
+ case "MarkupEnd": type = Type.IGNORE; break;
+
+ case "'['": type = Type.IGNORE; break;
+ case "']'": type = Type.IGNORE; break;
+ case "'{'": type = Type.IGNORE; break;
+ case "'}'": type = Type.IGNORE; break;
+ case "'='": type = Type.IGNORE; break;
+ case "'match'": type = Type.IGNORE; break;
+ case "'when'": type = Type.IGNORE; break;
+ case "'let'": type = Type.IGNORE; break;
+ case "'*'": type = Type.DEFAULT; break;
+ default:
+ throw new IllegalArgumentException("Parse error: Unknown token \"" + name + "\"");
+ }
+ }
+
+ boolean isStart() {
+ return Kind.NONTERMINAL_START.equals(kind);
+ }
+
+ boolean isEnd() {
+ return Kind.NONTERMINAL_END.equals(kind);
+ }
+
+ boolean isTerminal() {
+ return Kind.TERMINAL.equals(kind);
+ }
+
+ @Override
+ public String toString() {
+ int from = begin == -1 ? 0 : begin;
+ String strval = end == -1 ? input.substring(from) : input.substring(from, end);
+ return String.format("Token(\"%s\", [%d, %d], %s) // \"%s\"", name, begin, end, kind, strval);
+ }
+ }
+
+ Mf2Serializer() {}
+
+ @Override
+ public void reset(CharSequence input) {
+ this.input = input.toString();
+ tokens.clear();
+ }
+
+ @Override
+ public void startNonterminal(String name, int begin) {
+ tokens.add(new Token(Token.Kind.NONTERMINAL_START, name, begin, -1, input));
+ }
+
+ @Override
+ public void endNonterminal(String name, int end) {
+ tokens.add(new Token(Token.Kind.NONTERMINAL_END, name, -1, end, input));
+ }
+
+ @Override
+ public void terminal(String name, int begin, int end) {
+ tokens.add(new Token(Token.Kind.TERMINAL, name, begin, end, input));
+ }
+
+ @Override
+ public void whitespace(int begin, int end) {
+ }
+
+ Mf2DataModel build() {
+ if (!tokens.isEmpty()) {
+ Token firstToken = tokens.get(0);
+ if (Type.MESSAGE.equals(firstToken.type) && firstToken.isStart()) {
+ return parseMessage();
+ }
+ }
+ return null;
+ }
+
+ private Mf2DataModel parseMessage() {
+ Mf2DataModel.Builder result = Mf2DataModel.builder();
+
+ for (int i = 0; i < tokens.size(); i++) {
+ Token token = tokens.get(i);
+ switch (token.type) {
+ case MESSAGE:
+ if (token.isStart() && i == 0) {
+ // all good
+ } else if (token.isEnd() && i == tokens.size() - 1) {
+ // We check if this last token is at the end of the input
+ if (token.end != input.length()) {
+ String leftover = input.substring(token.end)
+ .replace("\n", "")
+ .replace("\r", "")
+ .replace(" ", "")
+ .replace("\t", "")
+ ;
+ if (!leftover.isEmpty()) {
+ throw new IllegalArgumentException("Parse error: Content detected after the end of the message: '"
+ + input.substring(token.end) + "'");
+ }
+ }
+ return result.build();
+ } else {
+ // End of message, we ignore the rest
+ throw new IllegalArgumentException("Parse error: Extra tokens at the end of the message");
+ }
+ break;
+ case PATTERN:
+ ParseResult<Pattern> patternResult = parsePattern(i);
+ i = patternResult.skipLen;
+ result.setPattern(patternResult.resultValue);
+ break;
+ case DECLARATION:
+ Declaration declaration = new Declaration();
+ i = parseDeclaration(i, declaration);
+ result.addLocalVariable(declaration.variableName, declaration.expr);
+ break;
+ case SELECTOR:
+ ParseResult<List<Expression>> selectorResult = parseSelector(i);
+ result.addSelectors(selectorResult.resultValue);
+ i = selectorResult.skipLen;
+ break;
+ case VARIANT:
+ ParseResult<Variant> variantResult = parseVariant(i);
+ i = variantResult.skipLen;
+ Variant variant = variantResult.resultValue;
+ result.addVariant(variant.getSelectorKeys(), variant.getPattern());
+ break;
+ case IGNORE:
+ break;
+ default:
+ throw new IllegalArgumentException("Parse error: parseMessage UNEXPECTED TOKEN: '" + token + "'");
+ }
+ }
+ throw new IllegalArgumentException("Parse error: Error parsing MessageFormatter");
+ }
+
+ private ParseResult<Variant> parseVariant(int startToken) {
+ Variant.Builder result = Variant.builder();
+
+ for (int i = startToken; i < tokens.size(); i++) {
+ Token token = tokens.get(i);
+ switch (token.type) {
+ case VARIANT:
+ if (token.isStart()) { // all good
+ } else if (token.isEnd()) {
+ return new ParseResult<>(i, result.build());
+ }
+ break;
+ case LITERAL:
+ result.addSelectorKey(input.substring(token.begin + 1, token.end - 1));
+ break;
+ case NMTOKEN:
+ result.addSelectorKey(input.substring(token.begin, token.end));
+ break;
+ case DEFAULT:
+ result.addSelectorKey("*");
+ break;
+ case PATTERN:
+ ParseResult<Pattern> patternResult = parsePattern(i);
+ i = patternResult.skipLen;
+ result.setPattern(patternResult.resultValue);
+ break;
+ case VARIANTKEY:
+// variant.variantKey = new VariantKey(input.substring(token.begin, token.end));
+ break;
+ case IGNORE:
+ break;
+ default:
+ throw new IllegalArgumentException("Parse error: parseVariant UNEXPECTED TOKEN: '" + token + "'");
+ }
+ }
+ throw new IllegalArgumentException("Parse error: Error parsing Variant");
+ }
+
+ private ParseResult<List<Expression>> parseSelector(int startToken) {
+ List<Expression> result = new ArrayList<>();
+
+ for (int i = startToken; i < tokens.size(); i++) {
+ Token token = tokens.get(i);
+ switch (token.type) {
+ case SELECTOR:
+ if (token.isStart()) { // all good, do nothing
+ } else if (token.isEnd()) {
+ return new ParseResult<>(i, result);
+ }
+ break;
+ case EXPRESSION:
+ ParseResult<Expression> exprResult = parseExpression(i);
+ i = exprResult.skipLen;
+ result.add(exprResult.resultValue);
+ break;
+ case IGNORE:
+ break;
+ default:
+ throw new IllegalArgumentException("Parse error: parseSelector UNEXPECTED TOKEN: '" + token + "'");
+ }
+ }
+ throw new IllegalArgumentException("Parse error: Error parsing selectors");
+ }
+
+ private int parseDeclaration(int startToken, Declaration declaration) {
+ for (int i = startToken; i < tokens.size(); i++) {
+ Token token = tokens.get(i);
+ switch (token.type) {
+ case DECLARATION:
+ if (token.isStart()) { // all good
+ } else if (token.isEnd()) {
+ return i;
+ }
+ break;
+ case VARIABLE:
+ declaration.variableName = input.substring(token.begin + 1, token.end);
+ break;
+ case EXPRESSION:
+ ParseResult<Expression> exprResult = parseExpression(i);
+ i = exprResult.skipLen;
+ declaration.expr = exprResult.resultValue;
+ break;
+ case IGNORE:
+ break;
+ default:
+ throw new IllegalArgumentException("Parse error: parseDeclaration UNEXPECTED TOKEN: '" + token + "'");
+ }
+ }
+ throw new IllegalArgumentException("Parse error: Error parsing Declaration");
+ }
+
+ private ParseResult<Pattern> parsePattern(int startToken) {
+ Pattern.Builder result = Pattern.builder();
+
+ for (int i = startToken; i < tokens.size(); i++) {
+ Token token = tokens.get(i);
+ switch (token.type) {
+ case TEXT:
+ Text text = new Text(input.substring(token.begin, token.end));
+ result.add(text);
+ break;
+ case PLACEHOLDER:
+ break;
+ case EXPRESSION:
+ ParseResult<Expression> exprResult = parseExpression(i);
+ i = exprResult.skipLen;
+ result.add(exprResult.resultValue);
+ break;
+ case VARIABLE:
+ case IGNORE:
+ break;
+ case PATTERN:
+ if (token.isStart() && i == startToken) { // all good, do nothing
+ } else if (token.isEnd()) {
+ return new ParseResult<>(i, result.build());
+ }
+ break;
+ default:
+ throw new IllegalArgumentException("Parse error: parsePattern UNEXPECTED TOKEN: '" + token + "'");
+ }
+ }
+ throw new IllegalArgumentException("Parse error: Error parsing Pattern");
+ }
+
+ static class Option {
+ String name;
+ Value value;
+ }
+
+ static class Declaration {
+ String variableName;
+ Expression expr;
+ }
+
+ static class Variant {
+ private final SelectorKeys selectorKeys;
+ private final Pattern pattern;
+
+ private Variant(Builder builder) {
+ this.selectorKeys = builder.selectorKeys.build();
+ this.pattern = builder.pattern;
+ }
+
+ /**
+ * Creates a builder.
+ *
+ * @return the Builder.
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public SelectorKeys getSelectorKeys() {
+ return selectorKeys;
+ }
+
+ public Pattern getPattern() {
+ return pattern;
+ }
+
+ /**
+ * @hide Only a subset of ICU is exposed in Android
+ */
+ public static class Builder {
+ private final SelectorKeys.Builder selectorKeys = SelectorKeys.builder();
+ private Pattern pattern = Pattern.builder().build();
+
+ // Prevent direct creation
+ private Builder() {
+ }
+
+ public Builder setSelectorKeys(SelectorKeys selectorKeys) {
+ this.selectorKeys.addAll(selectorKeys.getKeys());
+ return this;
+ }
+
+ public Builder addSelectorKey(String selectorKey) {
+ this.selectorKeys.add(selectorKey);
+ return this;
+ }
+
+ public Builder setPattern(Pattern pattern) {
+ this.pattern = pattern;
+ return this;
+ }
+
+ public Variant build() {
+ return new Variant(this);
+ }
+ }
+ }
+
+ static class ParseResult<T> {
+ final int skipLen;
+ final T resultValue;
+
+ public ParseResult(int skipLen, T resultValue) {
+ this.skipLen = skipLen;
+ this.resultValue = resultValue;
+ }
+ }
+
+ private ParseResult<Expression> parseExpression(int startToken) {
+ Expression.Builder result = Expression.builder();
+
+ for (int i = startToken; i < tokens.size(); i++) {
+ Token token = tokens.get(i);
+ switch (token.type) {
+ case EXPRESSION: // intentional fall-through
+ case PLACEHOLDER:
+ if (token.isStart() && i == startToken) {
+ // all good
+ } else if (token.isEnd()) {
+ return new ParseResult<>(i, result.build());
+ }
+ break;
+ case FUNCTION:
+ result.setFunctionName(input.substring(token.begin + 1, token.end));
+ break;
+ case LITERAL:
+ result.setOperand(Value.builder()
+ .setLiteral(input.substring(token.begin + 1, token.end - 1))
+ .build());
+ break;
+ case VARIABLE:
+ result.setOperand(Value.builder()
+ .setVariableName(input.substring(token.begin + 1, token.end))
+ .build());
+ break;
+ case OPTION:
+ Option option = new Option();
+ i = parseOptions(i, option);
+ result.addOption(option.name, option.value);
+ break;
+ case OPERAND:
+ break;
+ case IGNORE:
+ break;
+ default:
+ throw new IllegalArgumentException("Parse error: parseExpression UNEXPECTED TOKEN: '" + token + "'");
+ }
+ }
+ throw new IllegalArgumentException("Parse error: Error parsing Expression");
+ }
+
+ private int parseOptions(int startToken, Option option) {
+ for (int i = startToken; i < tokens.size(); i++) {
+ Token token = tokens.get(i);
+ switch (token.type) {
+ case OPTION:
+ if (token.isStart() && i == startToken) {
+ // all good
+ } else if (token.isEnd()) {
+ return i;
+ }
+ break;
+ case NAME:
+ option.name = input.substring(token.begin, token.end);
+ break;
+ case LITERAL:
+ option.value = Value.builder()
+ .setLiteral(input.substring(token.begin + 1, token.end - 1))
+ .build();
+ break;
+ case NMTOKEN:
+ option.value = Value.builder()
+ .setLiteral(input.substring(token.begin, token.end))
+ .build();
+ break;
+ case VARIABLE:
+ option.value = Value.builder()
+ .setVariableName(input.substring(token.begin + 1, token.end))
+ .build();
+ break;
+ case IGNORE:
+ break;
+ default:
+ throw new IllegalArgumentException("Parse error: parseOptions UNEXPECTED TOKEN: '" + token + "'");
+ }
+ }
+ throw new IllegalArgumentException("Parse error: Error parsing Option");
+ }
+
+ static String dataModelToString(Mf2DataModel dataModel) {
+ return dataModel.toString();
+ }
+}
diff --git a/android_icu4j/src/main/java/android/icu/message2/NumberFormatterFactory.java b/android_icu4j/src/main/java/android/icu/message2/NumberFormatterFactory.java
new file mode 100644
index 0000000..f10cc18
--- /dev/null
+++ b/android_icu4j/src/main/java/android/icu/message2/NumberFormatterFactory.java
@@ -0,0 +1,133 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+// © 2022 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html
+
+package android.icu.message2;
+
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+
+import android.icu.math.BigDecimal;
+import android.icu.number.LocalizedNumberFormatter;
+import android.icu.number.NumberFormatter;
+import android.icu.number.Precision;
+import android.icu.number.UnlocalizedNumberFormatter;
+import android.icu.text.FormattedValue;
+import android.icu.util.CurrencyAmount;
+
+
+/**
+ * Creates a {@link Formatter} doing numeric formatting, similar to <code>{exp, number}</code>
+ * in {@link android.icu.text.MessageFormat}.
+ */
+class NumberFormatterFactory implements FormatterFactory {
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Formatter createFormatter(Locale locale, Map<String, Object> fixedOptions) {
+ return new NumberFormatterImpl(locale, fixedOptions);
+ }
+
+ static class NumberFormatterImpl implements Formatter {
+ private final Locale locale;
+ private final Map<String, Object> fixedOptions;
+ private final LocalizedNumberFormatter icuFormatter;
+ final boolean advanced;
+
+ private static LocalizedNumberFormatter formatterForOptions(Locale locale, Map<String, Object> fixedOptions) {
+ UnlocalizedNumberFormatter nf;
+ String skeleton = OptUtils.getString(fixedOptions, "skeleton");
+ if (skeleton != null) {
+ nf = NumberFormatter.forSkeleton(skeleton);
+ } else {
+ nf = NumberFormatter.with();
+ Integer minFractionDigits = OptUtils.getInteger(fixedOptions, "minimumFractionDigits");
+ if (minFractionDigits != null) {
+ nf = nf.precision(Precision.minFraction(minFractionDigits));
+ }
+ }
+ return nf.locale(locale);
+ }
+
+ NumberFormatterImpl(Locale locale, Map<String, Object> fixedOptions) {
+ this.locale = locale;
+ this.fixedOptions = new HashMap<>(fixedOptions);
+ String skeleton = OptUtils.getString(fixedOptions, "skeleton");
+ boolean fancy = skeleton != null;
+ this.icuFormatter = formatterForOptions(locale, fixedOptions);
+ this.advanced = fancy;
+ }
+
+ LocalizedNumberFormatter getIcuFormatter() {
+ return icuFormatter;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String formatToString(Object toFormat, Map<String, Object> variableOptions) {
+ return format(toFormat, variableOptions).toString();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public FormattedPlaceholder format(Object toFormat, Map<String, Object> variableOptions) {
+ LocalizedNumberFormatter realFormatter;
+ if (variableOptions.isEmpty()) {
+ realFormatter = this.icuFormatter;
+ } else {
+ Map<String, Object> mergedOptions = new HashMap<>(fixedOptions);
+ mergedOptions.putAll(variableOptions);
+ // This is really wasteful, as we don't use the existing
+ // formatter if even one option is variable.
+ // We can optimize, but for now will have to do.
+ realFormatter = formatterForOptions(locale, mergedOptions);
+ }
+
+ Integer offset = OptUtils.getInteger(variableOptions, "offset");
+ if (offset == null && fixedOptions != null) {
+ offset = OptUtils.getInteger(fixedOptions, "offset");
+ }
+ if (offset == null) {
+ offset = 0;
+ }
+
+ FormattedValue result = null;
+ if (toFormat == null) {
+ // This is also what MessageFormat does.
+ throw new NullPointerException("Argument to format can't be null");
+ } else if (toFormat instanceof Double) {
+ result = realFormatter.format((double) toFormat - offset);
+ } else if (toFormat instanceof Long) {
+ result = realFormatter.format((long) toFormat - offset);
+ } else if (toFormat instanceof Integer) {
+ result = realFormatter.format((int) toFormat - offset);
+ } else if (toFormat instanceof BigDecimal) {
+ BigDecimal bd = (BigDecimal) toFormat;
+ result = realFormatter.format(bd.subtract(BigDecimal.valueOf(offset)));
+ } else if (toFormat instanceof Number) {
+ result = realFormatter.format(((Number) toFormat).doubleValue() - offset);
+ } else if (toFormat instanceof CurrencyAmount) {
+ result = realFormatter.format((CurrencyAmount) toFormat);
+ } else {
+ // The behavior is not in the spec, will be in the registry.
+ // We can return "NaN", or try to parse the string as a number
+ String strValue = Objects.toString(toFormat);
+ Number nrValue = OptUtils.asNumber(strValue);
+ if (nrValue != null) {
+ result = realFormatter.format(nrValue.doubleValue() - offset);
+ } else {
+ result = new PlainStringFormattedValue("NaN");
+ }
+ }
+ return new FormattedPlaceholder(toFormat, result);
+ }
+ }
+}
diff --git a/android_icu4j/src/main/java/android/icu/message2/OptUtils.java b/android_icu4j/src/main/java/android/icu/message2/OptUtils.java
new file mode 100644
index 0000000..b7ece56
--- /dev/null
+++ b/android_icu4j/src/main/java/android/icu/message2/OptUtils.java
@@ -0,0 +1,53 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+// © 2022 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html
+
+package android.icu.message2;
+
+import java.util.Map;
+
+class OptUtils {
+ private OptUtils() {}
+
+ static Number asNumber(Object value) {
+ if (value instanceof Number) {
+ return (Number) value;
+ }
+ if (value instanceof CharSequence) {
+ String strValue = value.toString();
+ try {
+ return Double.parseDouble(strValue);
+ } catch (NumberFormatException e) {
+ }
+ try {
+ return Integer.decode(strValue);
+ } catch (NumberFormatException e) {
+ }
+ }
+ return null;
+ }
+
+ static Integer getInteger(Map<String, Object> options, String key) {
+ Object value = options.get(key);
+ if (value == null) {
+ return null;
+ }
+ Number nrValue = asNumber(value);
+ if (nrValue != null) {
+ return nrValue.intValue();
+ }
+ return null;
+ }
+
+ static String getString(Map<String, Object> options, String key) {
+ Object value = options.get(key);
+ if (value == null) {
+ return null;
+ }
+ if (value instanceof CharSequence) {
+ return value.toString();
+ }
+ return null;
+ }
+
+}
diff --git a/android_icu4j/src/main/java/android/icu/message2/PlainStringFormattedValue.java b/android_icu4j/src/main/java/android/icu/message2/PlainStringFormattedValue.java
new file mode 100644
index 0000000..388647b
--- /dev/null
+++ b/android_icu4j/src/main/java/android/icu/message2/PlainStringFormattedValue.java
@@ -0,0 +1,134 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+// © 2022 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html
+
+package android.icu.message2;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.text.AttributedCharacterIterator;
+
+import android.icu.text.ConstrainedFieldPosition;
+import android.icu.text.FormattedValue;
+
+/**
+ * Very-very rough implementation of FormattedValue, packaging a string.
+ * Expect it to change.
+ *
+ * @deprecated This API is for unit testing only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+@Deprecated
+public class PlainStringFormattedValue implements FormattedValue {
+ private final String value;
+
+ /**
+ * Constructor, taking the string to store.
+ *
+ * @param value the string value to store
+ *
+ * @deprecated This API is for unit testing only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public PlainStringFormattedValue(String value) {
+ if (value == null) {
+ throw new IllegalAccessError("Should not try to wrap a null in a formatted value");
+ }
+ this.value = value;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @deprecated This API is for unit testing only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ @Override
+ public int length() {
+ return value == null ? 0 : value.length();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @deprecated This API is for unit testing only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ @Override
+ public char charAt(int index) {
+ return value.charAt(index);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @deprecated This API is for unit testing only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ @Override
+ public CharSequence subSequence(int start, int end) {
+ return value.subSequence(start, end);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @deprecated This API is for unit testing only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ @Override
+ public <A extends Appendable> A appendTo(A appendable) {
+ try {
+ appendable.append(value);
+ } catch (IOException e) {
+ throw new UncheckedIOException("problem appending", e);
+ }
+ return appendable;
+ }
+
+ /**
+ * Not yet implemented.
+ *
+ * {@inheritDoc}
+ *
+ * @deprecated This API is for unit testing only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ @Override
+ public boolean nextPosition(ConstrainedFieldPosition cfpos) {
+ throw new RuntimeException("nextPosition not yet implemented");
+ }
+
+ /**
+ * Not yet implemented.
+ *
+ * {@inheritDoc}
+ *
+ * @deprecated This API is for unit testing only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ @Override
+ public AttributedCharacterIterator toCharacterIterator() {
+ throw new RuntimeException("toCharacterIterator not yet implemented");
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @deprecated This API is for unit testing only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ @Override
+ public String toString() {
+ return value;
+ }
+}
diff --git a/android_icu4j/src/main/java/android/icu/message2/PluralSelectorFactory.java b/android_icu4j/src/main/java/android/icu/message2/PluralSelectorFactory.java
new file mode 100644
index 0000000..b941709
--- /dev/null
+++ b/android_icu4j/src/main/java/android/icu/message2/PluralSelectorFactory.java
@@ -0,0 +1,111 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+// © 2022 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html
+
+package android.icu.message2;
+
+import java.util.Locale;
+import java.util.Map;
+
+import android.icu.number.FormattedNumber;
+import android.icu.text.FormattedValue;
+import android.icu.text.PluralRules;
+import android.icu.text.PluralRules.PluralType;
+
+/**
+ * Creates a {@link Selector} doing plural selection, similar to <code>{exp, plural}</code>
+ * in {@link android.icu.text.MessageFormat}.
+ */
+class PluralSelectorFactory implements SelectorFactory {
+ private final PluralType pluralType;
+
+ /**
+ * Creates a {@code PluralSelectorFactory} of the desired type.
+ *
+ * @param type the kind of plural selection we want
+ */
+ // TODO: Use an enum
+ PluralSelectorFactory(String type) {
+ switch (type) {
+ case "ordinal":
+ pluralType = PluralType.ORDINAL;
+ break;
+ case "cardinal": // intentional fallthrough
+ default:
+ pluralType = PluralType.CARDINAL;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Selector createSelector(Locale locale, Map<String, Object> fixedOptions) {
+ PluralRules rules = PluralRules.forLocale(locale, pluralType);
+ return new PluralSelectorImpl(rules, fixedOptions);
+ }
+
+ private static class PluralSelectorImpl implements Selector {
+ private final PluralRules rules;
+ private Map<String, Object> fixedOptions;
+
+ private PluralSelectorImpl(PluralRules rules, Map<String, Object> fixedOptions) {
+ this.rules = rules;
+ this.fixedOptions = fixedOptions;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean matches(Object value, String key, Map<String, Object> variableOptions) {
+ if (value == null) {
+ return false;
+ }
+ if ("*".equals(key)) {
+ return true;
+ }
+
+ Integer offset = OptUtils.getInteger(variableOptions, "offset");
+ if (offset == null && fixedOptions != null) {
+ offset = OptUtils.getInteger(fixedOptions, "offset");
+ }
+ if (offset == null) {
+ offset = 0;
+ }
+
+ double valToCheck = Double.MIN_VALUE;
+ FormattedValue formattedValToCheck = null;
+ if (value instanceof FormattedPlaceholder) {
+ FormattedPlaceholder fph = (FormattedPlaceholder) value;
+ value = fph.getInput();
+ formattedValToCheck = fph.getFormattedValue();
+ }
+
+ if (value instanceof Double) {
+ valToCheck = (double) value;
+ } else if (value instanceof Integer) {
+ valToCheck = (Integer) value;
+ } else {
+ return false;
+ }
+
+ // If there is nothing "tricky" about the formatter part we compare values directly.
+ // Right now ICU4J checks if the formatter is a DecimalFormt, which also feels "hacky".
+ // We need something better.
+ if (!fixedOptions.containsKey("skeleton") && !variableOptions.containsKey("skeleton")) {
+ try { // for match exact.
+ if (Double.parseDouble(key) == valToCheck) {
+ return true;
+ }
+ } catch (NumberFormatException e) {
+ }
+ }
+
+ String match = formattedValToCheck instanceof FormattedNumber
+ ? rules.select((FormattedNumber) formattedValToCheck)
+ : rules.select(valToCheck - offset);
+ return match.equals(key);
+ }
+ }
+}
diff --git a/android_icu4j/src/main/java/android/icu/message2/Selector.java b/android_icu4j/src/main/java/android/icu/message2/Selector.java
new file mode 100644
index 0000000..895c2d7
--- /dev/null
+++ b/android_icu4j/src/main/java/android/icu/message2/Selector.java
@@ -0,0 +1,39 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+// © 2022 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html
+
+package android.icu.message2;
+
+import java.util.Map;
+
+/**
+ * The interface that must be implemented by all selectors
+ * that can be used from {@link MessageFormatter}.
+ *
+ * <p>Selectors are used to choose between different message variants,
+ * similar to <code>plural</code>, <code>selectordinal</code>,
+ * and <code>select</code> in {@link android.icu.text.MessageFormat}.</p>
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+@Deprecated
+public interface Selector {
+ /**
+ * A method that is invoked for the object to match and each key.
+ *
+ * <p>For example an English plural {@code matches} would return {@code true}
+ * for {@code matches(1, "1")}, {@code matches(1, "one")}, and {@code matches(1, "*")}.</p>
+ *
+ * @param value the value to select on.
+ * @param key the key to test for matching.
+ * @param variableOptions options that are not know at build time.
+ * @return the formatted string.
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ boolean matches(Object value, String key, Map<String, Object> variableOptions);
+}
diff --git a/android_icu4j/src/main/java/android/icu/message2/SelectorFactory.java b/android_icu4j/src/main/java/android/icu/message2/SelectorFactory.java
new file mode 100644
index 0000000..2b9ead3
--- /dev/null
+++ b/android_icu4j/src/main/java/android/icu/message2/SelectorFactory.java
@@ -0,0 +1,34 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+// © 2022 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html
+
+package android.icu.message2;
+
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * The interface that must be implemented for each selection function
+ * that can be used from {@link MessageFormatter}.
+ *
+ * <p>The we use it to create and cache various selectors with various options.</p>
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+@Deprecated
+public interface SelectorFactory {
+ /**
+ * The method that is called to create a selector.
+ *
+ * @param locale the locale to use for selection.
+ * @param fixedOptions the options to use for selection. The keys and values are function dependent.
+ * @return The Selector.
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ Selector createSelector(Locale locale, Map<String, Object> fixedOptions);
+}
diff --git a/android_icu4j/src/main/java/android/icu/message2/TextSelectorFactory.java b/android_icu4j/src/main/java/android/icu/message2/TextSelectorFactory.java
new file mode 100644
index 0000000..f293a12
--- /dev/null
+++ b/android_icu4j/src/main/java/android/icu/message2/TextSelectorFactory.java
@@ -0,0 +1,37 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+// © 2022 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html
+
+package android.icu.message2;
+
+import java.util.Locale;
+import java.util.Map;
+
+
+/**
+ * Creates a {@link Selector} doing literal selection, similar to <code>{exp, select}</code>
+ * in {@link android.icu.text.MessageFormat}.
+ */
+class TextSelectorFactory implements SelectorFactory {
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Selector createSelector(Locale locale, Map<String, Object> fixedOptions) {
+ return new TextSelector();
+ }
+
+ private static class TextSelector implements Selector {
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean matches(Object value, String key, Map<String, Object> variableOptions) {
+ if ("*".equals(key)) {
+ return true;
+ }
+ return key.equals(value);
+ }
+ }
+}
diff --git a/android_icu4j/src/main/java/android/icu/number/FormattedNumber.java b/android_icu4j/src/main/java/android/icu/number/FormattedNumber.java
index 0f10b8a..7855bbf 100644
--- a/android_icu4j/src/main/java/android/icu/number/FormattedNumber.java
+++ b/android_icu4j/src/main/java/android/icu/number/FormattedNumber.java
@@ -11,6 +11,7 @@
import android.icu.impl.Utility;
import android.icu.impl.number.DecimalQuantity;
import android.icu.text.ConstrainedFieldPosition;
+import android.icu.text.DisplayOptions;
import android.icu.text.FormattedValue;
import android.icu.text.PluralRules.IFixedDecimal;
import android.icu.util.MeasureUnit;
@@ -121,6 +122,17 @@
}
/**
+ * Gets the noun class of the formatted output. Returns `UNDEFINED` when the noun class is not
+ * supported yet.
+ *
+ * @return NounClass
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public DisplayOptions.NounClass getNounClass() {
+ return DisplayOptions.NounClass.fromIdentifier(this.gender);
+ }
+
+ /**
* The gender of the formatted output.
*
* @deprecated This API is for technology preview only.
diff --git a/android_icu4j/src/main/java/android/icu/number/FractionPrecision.java b/android_icu4j/src/main/java/android/icu/number/FractionPrecision.java
index ced2a35..3f6966a 100644
--- a/android_icu4j/src/main/java/android/icu/number/FractionPrecision.java
+++ b/android_icu4j/src/main/java/android/icu/number/FractionPrecision.java
@@ -30,8 +30,6 @@
* @param priority
* How to disambiguate between fraction digits and significant digits.
* @return A precision for chaining or passing to the NumberFormatter precision() setter.
- *
- * @hide draft / provisional / internal are hidden on Android
*/
public Precision withSignificantDigits(
int minSignificantDigits,
@@ -41,7 +39,7 @@
maxSignificantDigits >= minSignificantDigits &&
maxSignificantDigits <= RoundingUtils.MAX_INT_FRAC_SIG) {
return constructFractionSignificant(
- this, minSignificantDigits, maxSignificantDigits, priority);
+ this, minSignificantDigits, maxSignificantDigits, priority, false);
} else {
throw new IllegalArgumentException("Significant digits must be between 1 and "
+ RoundingUtils.MAX_INT_FRAC_SIG
@@ -73,7 +71,7 @@
public Precision withMinDigits(int minSignificantDigits) {
if (minSignificantDigits >= 1 && minSignificantDigits <= RoundingUtils.MAX_INT_FRAC_SIG) {
return constructFractionSignificant(
- this, 1, minSignificantDigits, NumberFormatter.RoundingPriority.RELAXED);
+ this, 1, minSignificantDigits, NumberFormatter.RoundingPriority.RELAXED, true);
} else {
throw new IllegalArgumentException("Significant digits must be between 1 and "
+ RoundingUtils.MAX_INT_FRAC_SIG
@@ -105,7 +103,7 @@
public Precision withMaxDigits(int maxSignificantDigits) {
if (maxSignificantDigits >= 1 && maxSignificantDigits <= RoundingUtils.MAX_INT_FRAC_SIG) {
return constructFractionSignificant(
- this, 1, maxSignificantDigits, NumberFormatter.RoundingPriority.STRICT);
+ this, 1, maxSignificantDigits, NumberFormatter.RoundingPriority.STRICT, true);
} else {
throw new IllegalArgumentException("Significant digits must be between 1 and "
+ RoundingUtils.MAX_INT_FRAC_SIG
diff --git a/android_icu4j/src/main/java/android/icu/number/NumberFormatter.java b/android_icu4j/src/main/java/android/icu/number/NumberFormatter.java
index 101b06e..05174d7 100644
--- a/android_icu4j/src/main/java/android/icu/number/NumberFormatter.java
+++ b/android_icu4j/src/main/java/android/icu/number/NumberFormatter.java
@@ -109,22 +109,15 @@
*
* <p>Here, RELAXED favors Max-Fraction and STRICT favors Max-Significant. Note that this larger
* number caused the two modes to favor the opposite result.
- *
- * @hide Only a subset of ICU is exposed in Android
- * @hide draft / provisional / internal are hidden on Android
*/
public static enum RoundingPriority {
/**
* Favor greater precision by relaxing one of the rounding constraints.
- *
- * @hide draft / provisional / internal are hidden on Android
*/
RELAXED,
/**
* Favor adherence to all rounding constraints by producing lower precision.
- *
- * @hide draft / provisional / internal are hidden on Android
*/
STRICT,
}
@@ -415,15 +408,11 @@
/**
* Same as AUTO, but do not show the sign on negative zero.
- *
- * @hide draft / provisional / internal are hidden on Android
*/
NEGATIVE,
/**
* Same as ACCOUNTING, but do not show the sign on negative zero.
- *
- * @hide draft / provisional / internal are hidden on Android
*/
ACCOUNTING_NEGATIVE,
}
@@ -463,22 +452,15 @@
* <li>AUTO: 0.90, 1.00, 1.10
* <li>HIDE_IF_WHOLE: 0.90, 1, 1.10
* </ul>
- *
- * @hide Only a subset of ICU is exposed in Android
- * @hide draft / provisional / internal are hidden on Android
*/
public static enum TrailingZeroDisplay {
/**
* Display trailing zeros according to the settings for minimum fraction and significant digits.
- *
- * @hide draft / provisional / internal are hidden on Android
*/
AUTO,
/**
* Same as AUTO, but hide trailing zeros after the decimal separator if they are all zero.
- *
- * @hide draft / provisional / internal are hidden on Android
*/
HIDE_IF_WHOLE,
}
diff --git a/android_icu4j/src/main/java/android/icu/number/NumberFormatterImpl.java b/android_icu4j/src/main/java/android/icu/number/NumberFormatterImpl.java
index 698d42a..b37e16e 100644
--- a/android_icu4j/src/main/java/android/icu/number/NumberFormatterImpl.java
+++ b/android_icu4j/src/main/java/android/icu/number/NumberFormatterImpl.java
@@ -364,7 +364,9 @@
// The default middle modifier is weak (thus the false argument).
MutablePatternModifier patternMod = new MutablePatternModifier(false);
AffixPatternProvider affixProvider =
- (macros.affixProvider != null)
+ (macros.affixProvider != null && (
+ // For more information on this condition, see ICU-22073
+ !isCompactNotation || isCurrency == macros.affixProvider.hasCurrencySign()))
? macros.affixProvider
: patternInfo;
patternMod.setPatternInfo(affixProvider, null);
diff --git a/android_icu4j/src/main/java/android/icu/number/NumberFormatterSettings.java b/android_icu4j/src/main/java/android/icu/number/NumberFormatterSettings.java
index 2efa445..76946f6 100644
--- a/android_icu4j/src/main/java/android/icu/number/NumberFormatterSettings.java
+++ b/android_icu4j/src/main/java/android/icu/number/NumberFormatterSettings.java
@@ -12,6 +12,8 @@
import android.icu.number.NumberFormatter.SignDisplay;
import android.icu.number.NumberFormatter.UnitWidth;
import android.icu.text.DecimalFormatSymbols;
+import android.icu.text.DisplayOptions;
+import android.icu.text.DisplayOptions.GrammaticalCase;
import android.icu.text.NumberingSystem;
import android.icu.util.Currency;
import android.icu.util.Measure;
@@ -529,6 +531,22 @@
}
/**
+ * Specifies the {@code DisplayOptions}. For example, {@code GrammaticalCase} specifies
+ * the desired case for a unit formatter's output (e.g. accusative, dative, genitive).
+ *
+ * @return The fluent chain.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public T displayOptions(DisplayOptions displayOptions) {
+ // `displayCase` does not recognise the `undefined`
+ if (displayOptions.getGrammaticalCase() == GrammaticalCase.UNDEFINED) {
+ return create(KEY_UNIT_DISPLAY_CASE, null);
+ }
+
+ return create(KEY_UNIT_DISPLAY_CASE, displayOptions.getGrammaticalCase().getIdentifier());
+ }
+
+ /**
* Specifies the desired case for a unit formatter's output (e.g.
* accusative, dative, genitive).
*
diff --git a/android_icu4j/src/main/java/android/icu/number/NumberPropertyMapper.java b/android_icu4j/src/main/java/android/icu/number/NumberPropertyMapper.java
index 8ba138b..5fd805e 100644
--- a/android_icu4j/src/main/java/android/icu/number/NumberPropertyMapper.java
+++ b/android_icu4j/src/main/java/android/icu/number/NumberPropertyMapper.java
@@ -302,8 +302,6 @@
} else {
macros.notation = Notation.compactShort();
}
- // Do not forward the affix provider.
- macros.affixProvider = null;
}
/////////////////
diff --git a/android_icu4j/src/main/java/android/icu/number/NumberRangeFormatter.java b/android_icu4j/src/main/java/android/icu/number/NumberRangeFormatter.java
index 9618b5a..1d986fa 100644
--- a/android_icu4j/src/main/java/android/icu/number/NumberRangeFormatter.java
+++ b/android_icu4j/src/main/java/android/icu/number/NumberRangeFormatter.java
@@ -145,7 +145,6 @@
* Class for span fields in FormattedNumberRange.
*
* @hide Only a subset of ICU is exposed in Android
- * @hide draft / provisional / internal are hidden on Android
*/
public static final class SpanField extends UFormat.SpanField {
private static final long serialVersionUID = 8750397196515368729L;
@@ -155,8 +154,6 @@
*
* Instances of NUMBER_RANGE_SPAN should have an associated value, the index within the input
* list that is represented by the span.
- *
- * @hide draft / provisional / internal are hidden on Android
*/
public static final SpanField NUMBER_RANGE_SPAN = new SpanField("number-range-span");
diff --git a/android_icu4j/src/main/java/android/icu/number/NumberRangeFormatterImpl.java b/android_icu4j/src/main/java/android/icu/number/NumberRangeFormatterImpl.java
index bff637e..b7ae9a8 100644
--- a/android_icu4j/src/main/java/android/icu/number/NumberRangeFormatterImpl.java
+++ b/android_icu4j/src/main/java/android/icu/number/NumberRangeFormatterImpl.java
@@ -158,7 +158,7 @@
: NumberRangeFormatter.RangeIdentityFallback.APPROXIMATELY;
String nsName = formatterImpl1.getRawMicroProps().nsName;
- if (nsName == null || !nsName.equals(formatterImpl2.getRawMicroProps().nsName)) {
+ if (nsName == null || (!fSameFormatters && !nsName.equals(formatterImpl2.getRawMicroProps().nsName))) {
throw new IllegalArgumentException("Both formatters must have same numbering system");
}
getNumberRangeData(macros.loc, nsName, this);
diff --git a/android_icu4j/src/main/java/android/icu/number/NumberSkeletonImpl.java b/android_icu4j/src/main/java/android/icu/number/NumberSkeletonImpl.java
index 6356939..5532eb7 100644
--- a/android_icu4j/src/main/java/android/icu/number/NumberSkeletonImpl.java
+++ b/android_icu4j/src/main/java/android/icu/number/NumberSkeletonImpl.java
@@ -1336,8 +1336,9 @@
// @, @@, @@@
maxSig = minSig;
}
- RoundingPriority priority;
+ FractionPrecision oldRounder = (FractionPrecision) macros.precision;
if (offset < segment.length()) {
+ RoundingPriority priority;
if (maxSig == -1) {
throw new SkeletonSyntaxException(
"Invalid digits option: Wildcard character not allowed with the priority annotation", segment);
@@ -1356,21 +1357,18 @@
throw new SkeletonSyntaxException(
"Invalid digits option for fraction rounder", segment);
}
+ macros.precision = oldRounder.withSignificantDigits(minSig, maxSig, priority);
} else if (maxSig == -1) {
// withMinDigits
- maxSig = minSig;
- minSig = 1;
- priority = RoundingPriority.RELAXED;
+ macros.precision = oldRounder.withMinDigits(minSig);
} else if (minSig == 1) {
// withMaxDigits
- priority = RoundingPriority.STRICT;
+ macros.precision = oldRounder.withMaxDigits(maxSig);
} else {
throw new SkeletonSyntaxException(
"Invalid digits option: Priority annotation required", segment);
}
- FractionPrecision oldRounder = (FractionPrecision) macros.precision;
- macros.precision = oldRounder.withSignificantDigits(minSig, maxSig, priority);
return true;
}
@@ -1578,11 +1576,19 @@
Precision.FracSigRounderImpl impl = (Precision.FracSigRounderImpl) macros.precision;
BlueprintHelpers.generateFractionStem(impl.minFrac, impl.maxFrac, sb);
sb.append('/');
- BlueprintHelpers.generateDigitsStem(impl.minSig, impl.maxSig, sb);
- if (impl.priority == RoundingPriority.RELAXED) {
- sb.append('r');
+ if (impl.retain) {
+ if (impl.priority == RoundingPriority.RELAXED) {
+ BlueprintHelpers.generateDigitsStem(impl.maxSig, -1, sb);
+ } else {
+ BlueprintHelpers.generateDigitsStem(1, impl.maxSig, sb);
+ }
} else {
- sb.append('s');
+ BlueprintHelpers.generateDigitsStem(impl.minSig, impl.maxSig, sb);
+ if (impl.priority == RoundingPriority.RELAXED) {
+ sb.append('r');
+ } else {
+ sb.append('s');
+ }
}
} else if (macros.precision instanceof Precision.IncrementRounderImpl) {
Precision.IncrementRounderImpl impl = (Precision.IncrementRounderImpl) macros.precision;
diff --git a/android_icu4j/src/main/java/android/icu/number/Precision.java b/android_icu4j/src/main/java/android/icu/number/Precision.java
index c892ad8..7d139df 100644
--- a/android_icu4j/src/main/java/android/icu/number/Precision.java
+++ b/android_icu4j/src/main/java/android/icu/number/Precision.java
@@ -332,7 +332,6 @@
* when the number is an integer, use HIDE_IF_WHOLE.
*
* @param trailingZeroDisplay Option to configure the display of trailing zeros.
- * @hide draft / provisional / internal are hidden on Android
*/
public Precision trailingZeroDisplay(TrailingZeroDisplay trailingZeroDisplay) {
Precision result = this.createCopy();
@@ -360,6 +359,16 @@
abstract Precision createCopy();
/**
+ * Call this function to copy the fields from the Precision base class.
+ *
+ * Note: It would be nice if this returned the copy, but most impls return the child class, not Precision.
+ */
+ /* package-private */ void createCopyHelper(Precision copy) {
+ copy.mathContext = mathContext;
+ copy.trailingZeroDisplay = trailingZeroDisplay;
+ }
+
+ /**
* @deprecated ICU 60 This API is ICU internal only.
* @hide draft / provisional / internal are hidden on Android
*/
@@ -387,7 +396,8 @@
static final SignificantRounderImpl FIXED_SIG_3 = new SignificantRounderImpl(3, 3);
static final SignificantRounderImpl RANGE_SIG_2_3 = new SignificantRounderImpl(2, 3);
- static final FracSigRounderImpl COMPACT_STRATEGY = new FracSigRounderImpl(0, 0, 1, 2, RoundingPriority.RELAXED);
+ static final FracSigRounderImpl COMPACT_STRATEGY = new FracSigRounderImpl(0, 0, 1, 2, RoundingPriority.RELAXED,
+ false);
static final IncrementFiveRounderImpl NICKEL = new IncrementFiveRounderImpl(new BigDecimal("0.05"), 2, 2);
@@ -423,16 +433,16 @@
}
}
- static Precision constructFractionSignificant(
- FractionPrecision base_, int minSig, int maxSig, RoundingPriority priority) {
+ static Precision constructFractionSignificant(FractionPrecision base_, int minSig, int maxSig,
+ RoundingPriority priority, boolean retain) {
assert base_ instanceof FractionRounderImpl;
FractionRounderImpl base = (FractionRounderImpl) base_;
Precision returnValue;
- if (base.minFrac == 0 && base.maxFrac == 0 && minSig == 1 && maxSig == 2 &&
- priority == RoundingPriority.RELAXED) {
+ if (base.minFrac == 0 && base.maxFrac == 0 && minSig == 1 && maxSig == 2 && priority == RoundingPriority.RELAXED
+ && !retain) {
returnValue = COMPACT_STRATEGY;
} else {
- returnValue = new FracSigRounderImpl(base.minFrac, base.maxFrac, minSig, maxSig, priority);
+ returnValue = new FracSigRounderImpl(base.minFrac, base.maxFrac, minSig, maxSig, priority, retain);
}
return returnValue.withMode(base.mathContext);
}
@@ -590,7 +600,7 @@
@Override
BogusRounder createCopy() {
BogusRounder copy = new BogusRounder();
- copy.mathContext = mathContext;
+ createCopyHelper(copy);
return copy;
}
@@ -603,7 +613,7 @@
@Deprecated
public Precision into(Precision precision) {
Precision copy = precision.createCopy();
- copy.mathContext = mathContext;
+ createCopyHelper(copy);
return copy;
}
}
@@ -622,7 +632,7 @@
@Override
InfiniteRounderImpl createCopy() {
InfiniteRounderImpl copy = new InfiniteRounderImpl();
- copy.mathContext = mathContext;
+ createCopyHelper(copy);
return copy;
}
}
@@ -645,7 +655,7 @@
@Override
FractionRounderImpl createCopy() {
FractionRounderImpl copy = new FractionRounderImpl(minFrac, maxFrac);
- copy.mathContext = mathContext;
+ createCopyHelper(copy);
return copy;
}
}
@@ -681,7 +691,7 @@
@Override
SignificantRounderImpl createCopy() {
SignificantRounderImpl copy = new SignificantRounderImpl(minSig, maxSig);
- copy.mathContext = mathContext;
+ createCopyHelper(copy);
return copy;
}
}
@@ -692,13 +702,16 @@
final int minSig;
final int maxSig;
final RoundingPriority priority;
+ final boolean retain;
- public FracSigRounderImpl(int minFrac, int maxFrac, int minSig, int maxSig, RoundingPriority priority) {
+ public FracSigRounderImpl(int minFrac, int maxFrac, int minSig, int maxSig, RoundingPriority priority,
+ boolean retain) {
this.minFrac = minFrac;
this.maxFrac = maxFrac;
this.minSig = minSig;
this.maxSig = maxSig;
this.priority = priority;
+ this.retain = retain;
}
@Override
@@ -711,18 +724,42 @@
} else {
roundingMag = Math.max(roundingMag1, roundingMag2);
}
- value.roundToMagnitude(roundingMag, mathContext);
+ if (!value.isZeroish()) {
+ int upperMag = value.getMagnitude();
+ value.roundToMagnitude(roundingMag, mathContext);
+ if (!value.isZeroish() && value.getMagnitude() != upperMag && roundingMag1 == roundingMag2) {
+ // roundingMag2 needs to be the magnitude after rounding
+ roundingMag2 += 1;
+ }
+ }
int displayMag1 = getDisplayMagnitudeFraction(minFrac);
int displayMag2 = getDisplayMagnitudeSignificant(value, minSig);
- int displayMag = Math.min(displayMag1, displayMag2);
+ int displayMag;
+ if (retain) {
+ // withMinDigits + withMaxDigits
+ displayMag = Math.min(displayMag1, displayMag2);
+ } else if (priority == RoundingPriority.RELAXED) {
+ if (roundingMag2 <= roundingMag1) {
+ displayMag = displayMag2;
+ } else {
+ displayMag = displayMag1;
+ }
+ } else {
+ assert(priority == RoundingPriority.STRICT);
+ if (roundingMag2 <= roundingMag1) {
+ displayMag = displayMag1;
+ } else {
+ displayMag = displayMag2;
+ }
+ }
setResolvedMinFraction(value, Math.max(0, -displayMag));
}
@Override
FracSigRounderImpl createCopy() {
- FracSigRounderImpl copy = new FracSigRounderImpl(minFrac, maxFrac, minSig, maxSig, priority);
- copy.mathContext = mathContext;
+ FracSigRounderImpl copy = new FracSigRounderImpl(minFrac, maxFrac, minSig, maxSig, priority, retain);
+ createCopyHelper(copy);
return copy;
}
}
@@ -740,13 +777,13 @@
@Override
public void apply(DecimalQuantity value) {
value.roundToIncrement(increment, mathContext);
- setResolvedMinFraction(value, increment.scale());
+ setResolvedMinFraction(value, Math.max(0, increment.scale()));
}
@Override
IncrementRounderImpl createCopy() {
IncrementRounderImpl copy = new IncrementRounderImpl(increment);
- copy.mathContext = mathContext;
+ createCopyHelper(copy);
return copy;
}
}
@@ -775,7 +812,7 @@
@Override
IncrementOneRounderImpl createCopy() {
IncrementOneRounderImpl copy = new IncrementOneRounderImpl(increment, minFrac, maxFrac);
- copy.mathContext = mathContext;
+ createCopyHelper(copy);
return copy;
}
}
@@ -802,7 +839,7 @@
@Override
IncrementFiveRounderImpl createCopy() {
IncrementFiveRounderImpl copy = new IncrementFiveRounderImpl(increment, minFrac, maxFrac);
- copy.mathContext = mathContext;
+ createCopyHelper(copy);
return copy;
}
}
@@ -823,7 +860,7 @@
@Override
CurrencyRounderImpl createCopy() {
CurrencyRounderImpl copy = new CurrencyRounderImpl(usage);
- copy.mathContext = mathContext;
+ createCopyHelper(copy);
return copy;
}
}
diff --git a/android_icu4j/src/main/java/android/icu/platform/AndroidDataFiles.java b/android_icu4j/src/main/java/android/icu/platform/AndroidDataFiles.java
index 73e2d50..74ab82f 100644
--- a/android_icu4j/src/main/java/android/icu/platform/AndroidDataFiles.java
+++ b/android_icu4j/src/main/java/android/icu/platform/AndroidDataFiles.java
@@ -37,7 +37,6 @@
public static final String ANDROID_ROOT_ENV = "ANDROID_ROOT";
public static final String ANDROID_I18N_ROOT_ENV = "ANDROID_I18N_ROOT";
public static final String ANDROID_TZDATA_ROOT_ENV = "ANDROID_TZDATA_ROOT";
- public static final String ANDROID_DATA_ENV = "ANDROID_DATA";
// VisibleForTesting
public static String getTimeZoneModuleIcuFile(String fileName) {
@@ -63,15 +62,7 @@
// Note: This logic below should match the logic in IcuRegistration.cpp in external/icu/
// to ensure consistent behavior between ICU4C and ICU4J.
- // ICU should first look in ANDROID_DATA. This is used for (optional) time zone data
- // delivered by APK (https://source.android.com/devices/tech/config/timezone-rules)
- String dataIcuDataPath =
- getEnvironmentPath(ANDROID_DATA_ENV, "/misc/zoneinfo/current/icu/");
- if (dataIcuDataPath != null) {
- paths.add(dataIcuDataPath);
- }
-
- // ICU should then look for a mounted time zone module file in /apex. This is used for
+ // ICU should look for a mounted time zone module file in /apex. This is used for
// (optional) time zone data that can be updated with an APEX file.
String timeZoneModuleIcuDataPath = getTimeZoneModuleIcuFile("");
paths.add(timeZoneModuleIcuDataPath);
@@ -83,16 +74,4 @@
return String.join(":", paths);
}
-
- /**
- * Creates a path by combining the value of an environment variable with a relative path.
- * Returns {@code null} if the environment variable is not set.
- */
- private static String getEnvironmentPath(String environmentVariable, String path) {
- String variable = System.getenv(environmentVariable);
- if (variable == null) {
- return null;
- }
- return variable + path;
- }
}
diff --git a/android_icu4j/src/main/java/android/icu/text/AnyTransliterator.java b/android_icu4j/src/main/java/android/icu/text/AnyTransliterator.java
index 934b03b..abde1d3 100644
--- a/android_icu4j/src/main/java/android/icu/text/AnyTransliterator.java
+++ b/android_icu4j/src/main/java/android/icu/text/AnyTransliterator.java
@@ -53,6 +53,12 @@
static final String LATIN_PIVOT = "-Latin;Latin-";
/**
+ * Special code for handling width characters
+ */
+ private static final Transliterator WIDTH_FIX =
+ Transliterator.getInstance("[[:dt=Nar:][:dt=Wide:]] nfkd");
+
+ /**
* Cache mapping UScriptCode values to Transliterator*.
*/
private ConcurrentHashMap<Integer, Transliterator> cache;
@@ -68,11 +74,6 @@
private int targetScript;
/**
- * Special code for handling width characters
- */
- private Transliterator widthFix = Transliterator.getInstance("[[:dt=Nar:][:dt=Wide:]] nfkd");
-
- /**
* Implements {@link Transliterator#handleTransliterate}.
*/
@Override
@@ -176,7 +177,7 @@
if (isWide(targetScript)) {
return null;
} else {
- return widthFix;
+ return WIDTH_FIX;
}
}
@@ -201,7 +202,7 @@
if (t != null) {
if (!isWide(targetScript)) {
List<Transliterator> v = new ArrayList<Transliterator>();
- v.add(widthFix);
+ v.add(WIDTH_FIX);
v.add(t);
t = new CompoundTransliterator(v);
}
@@ -210,7 +211,7 @@
t = prevCachedT;
}
} else if (!isWide(targetScript)) {
- return widthFix;
+ return WIDTH_FIX;
}
}
@@ -342,7 +343,7 @@
/**
- * Returns TRUE if there are any more runs. TRUE is always
+ * Returns true if there are any more runs. true is always
* returned at least once. Upon return, the caller should
* examine scriptCode, start, and limit.
*/
@@ -385,7 +386,7 @@
++limit;
}
- // Return TRUE even if the entire text is COMMON / INHERITED, in
+ // Return true even if the entire text is COMMON / INHERITED, in
// which case scriptCode will be UScript.INVALID_CODE.
return true;
}
@@ -408,7 +409,7 @@
if (filter != null && filter instanceof UnicodeSet) {
filter = new UnicodeSet((UnicodeSet)filter);
}
- return new AnyTransliterator(getID(), filter, target, targetScript, widthFix, cache);
+ return new AnyTransliterator(getID(), filter, target, targetScript, WIDTH_FIX, cache);
}
/* (non-Javadoc)
diff --git a/android_icu4j/src/main/java/android/icu/text/BidiLine.java b/android_icu4j/src/main/java/android/icu/text/BidiLine.java
index ef95239..4f3fbe8 100644
--- a/android_icu4j/src/main/java/android/icu/text/BidiLine.java
+++ b/android_icu4j/src/main/java/android/icu/text/BidiLine.java
@@ -94,7 +94,7 @@
are already set to paragraph level.
Setting trailingWSStart to pBidi->length will avoid changing the
level of B chars from 0 to paraLevel in getLevels when
- orderParagraphsLTR==TRUE
+ orderParagraphsLTR==true
*/
if (dirProps[start - 1] == Bidi.B) {
bidi.trailingWSStart = start; /* currently == bidi.length */
diff --git a/android_icu4j/src/main/java/android/icu/text/BreakIteratorFactory.java b/android_icu4j/src/main/java/android/icu/text/BreakIteratorFactory.java
index e8dfb42..1922926 100644
--- a/android_icu4j/src/main/java/android/icu/text/BreakIteratorFactory.java
+++ b/android_icu4j/src/main/java/android/icu/text/BreakIteratorFactory.java
@@ -137,7 +137,8 @@
typeKeyExt = "_" + keyValue;
}
String language = locale.getLanguage();
- if (language != null && language.equals("ja")) {
+ // lw=phrase is only supported in Japanese and Korean
+ if (language != null && (language.equals("ja") || language.equals("ko"))) {
keyValue = locale.getKeywordValue("lw");
if (keyValue != null && keyValue.equals("phrase")) {
typeKeyExt += "_" + keyValue;
diff --git a/android_icu4j/src/main/java/android/icu/text/CompoundTransliterator.java b/android_icu4j/src/main/java/android/icu/text/CompoundTransliterator.java
index fb85f8b..c9677f0 100644
--- a/android_icu4j/src/main/java/android/icu/text/CompoundTransliterator.java
+++ b/android_icu4j/src/main/java/android/icu/text/CompoundTransliterator.java
@@ -139,7 +139,7 @@
* @param splitTrans a transliterator to be inserted
* before the entry at offset idSplitPoint in the id string. May be
* NULL to insert no entry.
- * @param fixReverseID if TRUE, then reconstruct the ID of reverse
+ * @param fixReverseID if true, then reconstruct the ID of reverse
* entries by calling getID() of component entries. Some constructors
* do not require this because they apply a facade ID anyway.
*/
@@ -174,7 +174,7 @@
* is, it should be in the FORWARD order; if direction is REVERSE then
* the list order will be reversed.
* @param direction either FORWARD or REVERSE
- * @param fixReverseID if TRUE, then reconstruct the ID of reverse
+ * @param fixReverseID if true, then reconstruct the ID of reverse
* entries by calling getID() of component entries. Some constructors
* do not require this because they apply a facade ID anyway.
*/
@@ -258,7 +258,7 @@
* Override Transliterator:
* Create a rule string that can be passed to createFromRules()
* to recreate this transliterator.
- * @param escapeUnprintable if TRUE then convert unprintable
+ * @param escapeUnprintable if true then convert unprintable
* character to their hex escape representations, \\uxxxx or
* \\Uxxxxxxxx. Unprintable characters are those other than
* U+000A, U+0020..U+007E.
diff --git a/android_icu4j/src/main/java/android/icu/text/ConstrainedFieldPosition.java b/android_icu4j/src/main/java/android/icu/text/ConstrainedFieldPosition.java
index cb513a9..8fa483b 100644
--- a/android_icu4j/src/main/java/android/icu/text/ConstrainedFieldPosition.java
+++ b/android_icu4j/src/main/java/android/icu/text/ConstrainedFieldPosition.java
@@ -197,7 +197,7 @@
* Gets the field for the current position.
*
* The return value is well-defined and non-null only after
- * FormattedValue#nextPosition returns TRUE.
+ * FormattedValue#nextPosition returns true.
*
* @return The field saved in the instance. See above for null conditions.
*/
@@ -208,7 +208,7 @@
/**
* Gets the INCLUSIVE start index for the current position.
*
- * The return value is well-defined only after FormattedValue#nextPosition returns TRUE.
+ * The return value is well-defined only after FormattedValue#nextPosition returns true.
*
* @return The start index saved in the instance.
*/
@@ -219,7 +219,7 @@
/**
* Gets the EXCLUSIVE end index stored for the current position.
*
- * The return value is well-defined only after FormattedValue#nextPosition returns TRUE.
+ * The return value is well-defined only after FormattedValue#nextPosition returns true.
*
* @return The end index saved in the instance.
*/
@@ -230,7 +230,7 @@
/**
* Gets the value associated with the current field position. The field value is often not set.
*
- * The return value is well-defined only after FormattedValue#nextPosition returns TRUE.
+ * The return value is well-defined only after FormattedValue#nextPosition returns true.
*
* @return The value for the current position. Might be null.
*/
diff --git a/android_icu4j/src/main/java/android/icu/text/DateIntervalFormat.java b/android_icu4j/src/main/java/android/icu/text/DateIntervalFormat.java
index 9b693af..8948b79 100644
--- a/android_icu4j/src/main/java/android/icu/text/DateIntervalFormat.java
+++ b/android_icu4j/src/main/java/android/icu/text/DateIntervalFormat.java
@@ -207,11 +207,12 @@
* // and parses into
* DateInterval dtInterval = new DateInterval(1000*3600*24L, 1000*3600*24*2L);
* DateIntervalFormat dtIntervalFmt = DateIntervalFormat.getInstance(
- * YEAR_MONTH_DAY, Locale("en", "GB", ""));
- * StringBuffer str = new StringBuffer("");
- * FieldPosition pos = new FieldPosition(0);
+ * DateFormat.YEAR_MONTH_DAY, new Locale("en", "GB", ""));
+ * StringBuffer result = new StringBuffer("");
+ * FieldPosition pos = new FieldPosition(-1);
* // formatting
- * dtIntervalFmt.format(dtInterval, dateIntervalString, pos);
+ * dtIntervalFmt.format(dtInterval, result, pos);
+ * assertEquals("interval", "1–2 January 1970", result.toString());
*
* </pre>
*
@@ -1588,18 +1589,27 @@
StringBuilder result = new StringBuilder(skeleton);
char hourMetachar = '\0';
- int metacharStart = 0;
- int metacharCount = 0;
+ char dayPeriodChar = '\0';
+ int hourFieldStart = 0;
+ int hourFieldLength = 0;
+ int dayPeriodStart = 0;
+ int dayPeriodLength = 0;
for (int i = 0; i < result.length(); i++) {
char c = result.charAt(i);
- if (c == 'j' || c == 'J' || c == 'C') {
+ if (c == 'j' || c == 'J' || c == 'C' || c == 'h' || c == 'H' || c == 'k' || c == 'K') {
if (hourMetachar == '\0') {
hourMetachar = c;
- metacharStart = i;
+ hourFieldStart = i;
}
- ++metacharCount;
+ ++hourFieldLength;
+ } else if (c == 'a' || c == 'b' || c == 'B') {
+ if (dayPeriodChar == '\0') {
+ dayPeriodChar = c;
+ dayPeriodStart = i;
+ }
+ ++dayPeriodLength;
} else {
- if (hourMetachar != '\0') {
+ if (hourMetachar != '\0' && dayPeriodChar != '\0') {
break;
}
}
@@ -1607,7 +1617,6 @@
if (hourMetachar != '\0') {
char hourChar = 'H';
- char dayPeriodChar = 'a';
DateTimePatternGenerator dtptng = DateTimePatternGenerator.getInstance(locale);
String convertedPattern = dtptng.getBestPattern(String.valueOf(hourMetachar));
@@ -1635,34 +1644,30 @@
dayPeriodChar = 'b';
} else if (convertedPattern.indexOf('B') != -1) {
dayPeriodChar = 'B';
+ } else if (dayPeriodChar == '\0') {
+ dayPeriodChar = 'a';
}
- if (hourChar == 'H' || hourChar == 'k') {
- result.replace(metacharStart, metacharStart + metacharCount, String.valueOf(hourChar));
- } else {
- StringBuilder hourAndDayPeriod = new StringBuilder();
- hourAndDayPeriod.append(hourChar);
- switch (metacharCount) {
- case 1:
- case 2:
- default:
- hourAndDayPeriod.append(dayPeriodChar);
- break;
- case 3:
- case 4:
- for (int i = 0; i < 4; i++) {
- hourAndDayPeriod.append(dayPeriodChar);
- }
- break;
- case 5:
- case 6:
- for (int i = 0; i < 5; i++) {
- hourAndDayPeriod.append(dayPeriodChar);
- }
- break;
+ StringBuilder hourAndDayPeriod = new StringBuilder();
+ hourAndDayPeriod.append(hourChar);
+ if (hourChar != 'H' && hourChar != 'k') {
+ int newDayPeriodLength = 0;
+ if (dayPeriodLength >= 5 || hourFieldLength >= 5) {
+ newDayPeriodLength = 5;
+ } else if (dayPeriodLength >= 3 || hourFieldLength >= 3) {
+ newDayPeriodLength = 3;
+ } else {
+ newDayPeriodLength = 1;
}
- result.replace(metacharStart, metacharStart + metacharCount, hourAndDayPeriod.toString());
+ for (int i = 0; i < newDayPeriodLength; i++) {
+ hourAndDayPeriod.append(dayPeriodChar);
+ }
}
+ result.replace(hourFieldStart, hourFieldStart + hourFieldLength, hourAndDayPeriod.toString());
+ if (dayPeriodStart > hourFieldStart) {
+ dayPeriodStart += hourAndDayPeriod.length() - hourFieldLength;
+ }
+ result.delete(dayPeriodStart, dayPeriodStart + dayPeriodLength);
}
return result.toString();
}
@@ -2095,8 +2100,16 @@
if (suppressDayPeriodField) {
if (bestMatchIntervalPattern.indexOf(" a") != -1) {
bestMatchIntervalPattern = findReplaceInPattern(bestMatchIntervalPattern, " a", "");
+ } else if (bestMatchIntervalPattern.indexOf("\u00A0a") != -1) {
+ bestMatchIntervalPattern = findReplaceInPattern(bestMatchIntervalPattern, "\u00A0a", "");
+ } else if (bestMatchIntervalPattern.indexOf("\u202Fa") != -1) {
+ bestMatchIntervalPattern = findReplaceInPattern(bestMatchIntervalPattern, "\u202Fa", "");
} else if (bestMatchIntervalPattern.indexOf("a ") != -1) {
bestMatchIntervalPattern = findReplaceInPattern(bestMatchIntervalPattern, "a ", "");
+ } else if (bestMatchIntervalPattern.indexOf("a\u00A0") != -1) {
+ bestMatchIntervalPattern = findReplaceInPattern(bestMatchIntervalPattern, "a\u00A0", "");
+ } else if (bestMatchIntervalPattern.indexOf("a\u202F") != -1) {
+ bestMatchIntervalPattern = findReplaceInPattern(bestMatchIntervalPattern, "a\u202F", "");
}
bestMatchIntervalPattern = findReplaceInPattern(bestMatchIntervalPattern, "a", "");
}
diff --git a/android_icu4j/src/main/java/android/icu/text/DateIntervalInfo.java b/android_icu4j/src/main/java/android/icu/text/DateIntervalInfo.java
index e5bbb8f..4069b5b 100644
--- a/android_icu4j/src/main/java/android/icu/text/DateIntervalInfo.java
+++ b/android_icu4j/src/main/java/android/icu/text/DateIntervalInfo.java
@@ -906,8 +906,8 @@
* Get default order -- whether the first date in pattern is later date
* or not.
*
- * return default date ordering in interval pattern. TRUE if the first date
- * in pattern is later date, FALSE otherwise.
+ * return default date ordering in interval pattern. true if the first date
+ * in pattern is later date, false otherwise.
*/
public boolean getDefaultOrder()
{
diff --git a/android_icu4j/src/main/java/android/icu/text/DateTimePatternGenerator.java b/android_icu4j/src/main/java/android/icu/text/DateTimePatternGenerator.java
index 3bd036f..1c51b1e 100644
--- a/android_icu4j/src/main/java/android/icu/text/DateTimePatternGenerator.java
+++ b/android_icu4j/src/main/java/android/icu/text/DateTimePatternGenerator.java
@@ -309,8 +309,11 @@
}
private void setDateTimeFromCalendar(ULocale uLocale) {
- String dateTimeFormat = Calendar.getDateTimePattern(Calendar.getInstance(uLocale), uLocale, DateFormat.MEDIUM);
- setDateTimeFormat(dateTimeFormat);
+ Calendar cal = Calendar.getInstance(uLocale);
+ for (int style = DateFormat.FULL; style <= DateFormat.SHORT; style++) {
+ String dateTimeFormat = Calendar.getDateAtTimePattern(cal, uLocale, style);
+ setDateTimeFormat(style, dateTimeFormat);
+ }
}
private void setDecimalSymbols(ULocale uLocale) {
@@ -351,6 +354,7 @@
String language = uLocale.getLanguage();
String country = uLocale.getCountry();
+
if (language.isEmpty() || country.isEmpty()) {
// Note: addLikelySubtags is documented not to throw in Java,
// unlike in C++.
@@ -359,6 +363,15 @@
country = max.getCountry();
}
+ String regionOverride = uLocale.getKeywordValue("rg");
+ if (regionOverride != null && !regionOverride.isEmpty()) {
+ // chop off any subdivision codes that may have been included
+ if (regionOverride.length() > 2) {
+ regionOverride = regionOverride.substring(0, 2);
+ }
+ country = regionOverride;
+ }
+
if (language.isEmpty()) {
// Unexpected, but fail gracefully
language = "und";
@@ -676,8 +689,26 @@
if (datePattern == null) return timePattern == null ? "" : timePattern;
if (timePattern == null) return datePattern;
+ // determine which dateTimeFormat to use
+ String canonicalSkeleton = current.toCanonicalString(); // month fields use M, weekday fields use E
+ int style = DateFormat.SHORT;
+ int monthFieldLen = 0;
+ int monthFieldOffset = canonicalSkeleton.indexOf('M');
+ if (monthFieldOffset >= 0) {
+ monthFieldLen = 1 + canonicalSkeleton.lastIndexOf('M') - monthFieldOffset;
+ }
+ if (monthFieldLen == 4) {
+ if (canonicalSkeleton.indexOf('E') >= 0) {
+ style = DateFormat.FULL;
+ } else {
+ style = DateFormat.LONG;
+ }
+ } else if (monthFieldLen == 3) {
+ style = DateFormat.MEDIUM;
+ }
+ // and now use it to compose date and time
return SimpleFormatterImpl.formatRawPattern(
- getDateTimeFormat(), 2, 2, timePattern, datePattern);
+ getDateTimeFormat(style), 2, 2, timePattern, datePattern);
}
/*
@@ -1012,21 +1043,77 @@
* for those two skeletons, so the result is put together with this pattern,
* resulting in "d-MMM h:mm".
*
+ * There are four DateTimeFormats in a DateTimePatternGenerator object,
+ * corresponding to date styles DateFormat.FULL..DateFormat.SHORT. This method sets
+ * all of them to the specified pattern. To set them individually, see
+ * setDateTimeFormat(int style, ...).
+ *
* @param dateTimeFormat message format pattern, where {1} will be replaced by the date
* pattern and {0} will be replaced by the time pattern.
*/
public void setDateTimeFormat(String dateTimeFormat) {
checkFrozen();
- this.dateTimeFormat = dateTimeFormat;
+ for (int style = DateFormat.FULL; style <= DateFormat.SHORT; style++) {
+ setDateTimeFormat(style, dateTimeFormat);
+ }
}
/**
* Getter corresponding to setDateTimeFormat.
*
+ * There are four DateTimeFormats in a DateTimePatternGenerator object,
+ * corresponding to date styles DateFormat.FULL..DateFormat.SHORT. This method gets
+ * the style for DateFormat.MEDIUM (the default). To get them individually, see
+ * getDateTimeFormat(int style).
+ *
* @return pattern
*/
public String getDateTimeFormat() {
- return dateTimeFormat;
+ return getDateTimeFormat(DateFormat.MEDIUM);
+ }
+
+ /**
+ * dateTimeFormats are message patterns used to compose combinations of date
+ * and time patterns. There are four length styles, corresponding to the
+ * inferred style of the date pattern:
+ * - DateFormat.FULL (for date pattern with weekday and long month), else
+ * - DateFormat.LONG (for a date pattern with long month), else
+ * - DateFormat.MEDIUM (for a date pattern with abbreviated month), else
+ * - DateFormat.SHORT (for any other date pattern).
+ * For details on dateTimeFormats, see
+ * https://www.unicode.org/reports/tr35/tr35-dates.html#dateTimeFormats.
+ * The default pattern in the root locale for all styles is "{1} {0}".
+ *
+ * @param style
+ * one of DateFormat.FULL..DateFormat.SHORT. An exception will
+ * be thrown if out of range.
+ * @param dateTimeFormat
+ * the new dateTimeFormat to set for the specified style
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public void setDateTimeFormat(int style, String dateTimeFormat) {
+ if (style < DateFormat.FULL || style > DateFormat.SHORT) {
+ throw new IllegalArgumentException("Illegal style here: " + style);
+ }
+ checkFrozen();
+ this.dateTimeFormats[style] = dateTimeFormat;
+ }
+
+ /**
+ * Getter corresponding to setDateTimeFormat.
+ *
+ * @param style
+ * one of DateFormat.FULL..DateFormat.SHORT. An exception will
+ * be thrown if out of range.
+ * @return
+ * the current dateTimeFormat for the specified style.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public String getDateTimeFormat(int style) {
+ if (style < DateFormat.FULL || style > DateFormat.SHORT) {
+ throw new IllegalArgumentException("Illegal style here: " + style);
+ }
+ return dateTimeFormats[style];
}
/**
@@ -1405,7 +1492,7 @@
* the pattern map from parent locales.
*
* @param key of the availableFormatMask in CLDR
- * @return TRUE if the corresponding slot of CLDR_AVAIL_FORMAT_KEY[]
+ * @return true if the corresponding slot of CLDR_AVAIL_FORMAT_KEY[]
* has been added to DateTimePatternGenerator.
*/
private boolean isAvailableFormatSet(String key) {
@@ -1450,6 +1537,7 @@
DateTimePatternGenerator result = (DateTimePatternGenerator) (super.clone());
result.skeleton2pattern = (TreeMap<DateTimeMatcher, PatternWithSkeletonFlag>) skeleton2pattern.clone();
result.basePattern_pattern = (TreeMap<String, PatternWithSkeletonFlag>) basePattern_pattern.clone();
+ result.dateTimeFormats = dateTimeFormats.clone();
result.appendItemFormats = appendItemFormats.clone();
result.fieldDisplayNames = fieldDisplayNames.clone();
result.current = new DateTimeMatcher();
@@ -1970,7 +2058,14 @@
private TreeMap<DateTimeMatcher, PatternWithSkeletonFlag> skeleton2pattern = new TreeMap<>(); // items are in priority order
private TreeMap<String, PatternWithSkeletonFlag> basePattern_pattern = new TreeMap<>(); // items are in priority order
private String decimal = "?";
- private String dateTimeFormat = "{1} {0}";
+ // For the following, need fallback patterns in case an empty instance
+ // of DateTimePatterngenerator is used for formatting.
+ private String[] dateTimeFormats = {
+ "{1} {0}",
+ "{1} {0}",
+ "{1} {0}",
+ "{1} {0}"
+ };
private String[] appendItemFormats = new String[TYPE_LIMIT];
private String[][] fieldDisplayNames = new String[TYPE_LIMIT][DisplayWidth.COUNT];
private char defaultHourFormatChar = 'H';
@@ -2709,8 +2804,8 @@
char ch1 = original.getFieldChar(field);
char ch2 = value.charAt(0);
if ( allowDuplicateFields ||
- (ch1 == 'r' && ch2 == 'U') ||
- (ch1 == 'U' && ch2 == 'r') ) {
+ (ch1 == 'r' && (ch2 == 'U' || ch2 == 'y')) ||
+ ((ch1 == 'U' || ch1 == 'y') && ch2 == 'r') ) {
continue;
}
throw new IllegalArgumentException("Conflicting fields:\t"
diff --git a/android_icu4j/src/main/java/android/icu/text/DisplayOptions.java b/android_icu4j/src/main/java/android/icu/text/DisplayOptions.java
new file mode 100644
index 0000000..3f890e8
--- /dev/null
+++ b/android_icu4j/src/main/java/android/icu/text/DisplayOptions.java
@@ -0,0 +1,751 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+// © 2022 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html
+
+package android.icu.text;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Represents all the display options that are supported by CLDR such as grammatical case, noun
+ * class, ... etc. It currently supports enums, but may be extended in the future to have other
+ * types of data. It replaces a DisplayContext[] as a method parameter.
+ * <p>
+ * NOTE: This class is Immutable, and uses a Builder interface.
+ * <p>For example:
+ * {@code DisplayOptions x =
+ * DisplayOptions.builder()
+ * .setNounClass(NounClass.DATIVE)
+ * .setPluralCategory(PluralCategory.FEW)
+ * .build();
+ * }
+ *
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+public final class DisplayOptions {
+ private final GrammaticalCase grammaticalCase;
+ private final NounClass nounClass;
+ private final PluralCategory pluralCategory;
+ private final Capitalization capitalization;
+ private final NameStyle nameStyle;
+ private final DisplayLength displayLength;
+ private final SubstituteHandling substituteHandling;
+
+ private DisplayOptions(Builder builder) {
+ this.grammaticalCase = builder.grammaticalCase;
+ this.nounClass = builder.nounClass;
+ this.pluralCategory = builder.pluralCategory;
+ this.capitalization = builder.capitalization;
+ this.nameStyle = builder.nameStyle;
+ this.displayLength = builder.displayLength;
+ this.substituteHandling = builder.substituteHandling;
+ }
+
+ /**
+ * Creates a builder with the {@code UNDEFINED} value for all the parameters.
+ *
+ * @return Builder
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Creates a builder with the same parameters from this object.
+ *
+ * @return Builder
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public Builder copyToBuilder() {
+ return new Builder(this);
+ }
+
+ /**
+ * Gets the grammatical case.
+ *
+ * @return GrammaticalCase
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public GrammaticalCase getGrammaticalCase() {
+ return this.grammaticalCase;
+ }
+
+ /**
+ * Gets the noun class.
+ *
+ * @return NounClass
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public NounClass getNounClass() {
+ return this.nounClass;
+ }
+
+ /**
+ * Gets the plural category.
+ *
+ * @return PluralCategory
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public PluralCategory getPluralCategory() {
+ return this.pluralCategory;
+ }
+
+ /**
+ * Gets the capitalization.
+ *
+ * @return Capitalization
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public Capitalization getCapitalization() {
+ return this.capitalization;
+ }
+
+ /**
+ * Gets the name style.
+ *
+ * @return NameStyle
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public NameStyle getNameStyle() {
+ return this.nameStyle;
+ }
+
+ /**
+ * Gets the display length.
+ *
+ * @return DisplayLength
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public DisplayLength getDisplayLength() {
+ return this.displayLength;
+ }
+
+ /**
+ * Gets the substitute handling.
+ *
+ * @return SubstituteHandling
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public SubstituteHandling getSubstituteHandling() {
+ return this.substituteHandling;
+ }
+
+ /**
+ * Responsible for building {@code DisplayOptions}.
+ *
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public static class Builder {
+ private GrammaticalCase grammaticalCase;
+ private NounClass nounClass;
+ private PluralCategory pluralCategory;
+ private Capitalization capitalization;
+ private NameStyle nameStyle;
+ private DisplayLength displayLength;
+ private SubstituteHandling substituteHandling;
+
+ /**
+ * Creates a {@code DisplayOptions.Builder} with the default values.
+ *
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ private Builder() {
+ this.grammaticalCase = GrammaticalCase.UNDEFINED;
+ this.nounClass = NounClass.UNDEFINED;
+ this.pluralCategory = PluralCategory.UNDEFINED;
+ this.capitalization = Capitalization.UNDEFINED;
+ this.nameStyle = NameStyle.UNDEFINED;
+ this.displayLength = DisplayLength.UNDEFINED;
+ this.substituteHandling = SubstituteHandling.UNDEFINED;
+ }
+
+ /**
+ * Creates a {@code Builder} with all the information from a {@code DisplayOptions}.
+ *
+ * @param displayOptions Options to be copied.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ private Builder(DisplayOptions displayOptions) {
+ this.grammaticalCase = displayOptions.grammaticalCase;
+ this.nounClass = displayOptions.nounClass;
+ this.pluralCategory = displayOptions.pluralCategory;
+ this.capitalization = displayOptions.capitalization;
+ this.nameStyle = displayOptions.nameStyle;
+ this.displayLength = displayOptions.displayLength;
+ this.substituteHandling = displayOptions.substituteHandling;
+ }
+
+ /**
+ * Sets the grammatical case.
+ *
+ * @param grammaticalCase The grammatical case.
+ * @return Builder
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public Builder setGrammaticalCase(GrammaticalCase grammaticalCase) {
+ this.grammaticalCase = grammaticalCase;
+ return this;
+ }
+
+ /**
+ * Sets the noun class.
+ *
+ * @param nounClass The noun class.
+ * @return Builder
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public Builder setNounClass(NounClass nounClass) {
+ this.nounClass = nounClass;
+ return this;
+ }
+
+ /**
+ * Sets the plural category.
+ *
+ * @param pluralCategory The plural category.
+ * @return Builder
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public Builder setPluralCategory(PluralCategory pluralCategory) {
+ this.pluralCategory = pluralCategory;
+ return this;
+ }
+
+ /**
+ * Sets the capitalization.
+ *
+ * @param capitalization The capitalization.
+ * @return Builder
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public Builder setCapitalization(Capitalization capitalization) {
+ this.capitalization = capitalization;
+ return this;
+ }
+
+ /**
+ * Sets the name style.
+ *
+ * @param nameStyle The name style.
+ * @return Builder
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public Builder setNameStyle(NameStyle nameStyle) {
+ this.nameStyle = nameStyle;
+ return this;
+ }
+
+ /**
+ * Sets the display length.
+ *
+ * @param displayLength The display length.
+ * @return Builder
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public Builder setDisplayLength(DisplayLength displayLength) {
+ this.displayLength = displayLength;
+ return this;
+ }
+
+ /**
+ * Sets the substitute handling.
+ *
+ * @param substituteHandling The substitute handling.
+ * @return Builder
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public Builder setSubstituteHandling(SubstituteHandling substituteHandling) {
+ this.substituteHandling = substituteHandling;
+ return this;
+ }
+
+ /**
+ * Builds the display options.
+ *
+ * @return DisplayOptions
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public DisplayOptions build() {
+ DisplayOptions displayOptions = new DisplayOptions(this);
+ return displayOptions;
+ }
+ }
+
+ /**
+ * Represents all the grammatical noun classes that are supported by CLDR.
+ *
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public enum NounClass {
+ /**
+ * A possible setting for NounClass. The noun class context to be used is unknown (this is the
+ * default value).
+ *
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ UNDEFINED("undefined"),
+ /**
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ OTHER("other"),
+ /**
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ NEUTER("neuter"),
+ /**
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ FEMININE("feminine"),
+ /**
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ MASCULINE("masculine"),
+ /**
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ ANIMATE("animate"),
+ /**
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ INANIMATE("inanimate"),
+ /**
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ PERSONAL("personal"),
+ /**
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ COMMON("common");
+
+ private final String identifier;
+
+ private NounClass(String identifier) {
+ this.identifier = identifier;
+ }
+
+ /**
+ * Unmodifiable List of all noun classes constants. List version of {@link #values()}.
+ *
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public static final List<NounClass> VALUES =
+ Collections.unmodifiableList(Arrays.asList(NounClass.values()));
+
+ /**
+ * @return the lowercase CLDR keyword string for the noun class.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public final String getIdentifier() {
+ return this.identifier;
+ }
+
+ /**
+ * @param identifier in lower case such as "feminine" or "masculine"
+ * @return the plural category corresponding to the identifier, or {@code UNDEFINED}
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public static final NounClass fromIdentifier(String identifier) {
+ if (identifier == null) {
+ return NounClass.UNDEFINED;
+ }
+
+ for (NounClass nounClass : VALUES) {
+ if (identifier.equals(nounClass.getIdentifier())) {
+ return nounClass;
+ }
+ }
+
+ return NounClass.UNDEFINED;
+ }
+ }
+
+ /**
+ * Represents all the name styles.
+ *
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public enum NameStyle {
+ /**
+ * A possible setting for NameStyle. The NameStyle context to be used is unknown (this is the
+ * default value).
+ *
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ UNDEFINED,
+ /**
+ * Use standard names when generating a locale name, e.g. en_GB displays as 'English (United
+ * Kingdom)'.
+ *
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ STANDARD_NAMES,
+
+ /**
+ * Use dialect names, when generating a locale name, e.g. en_GB displays as 'British English'.
+ *
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ DIALECT_NAMES;
+
+ /**
+ * Unmodifiable List of all name styles constants. List version of {@link #values()}.
+ *
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public static final List<NameStyle> VALUES =
+ Collections.unmodifiableList(Arrays.asList(NameStyle.values()));
+ }
+
+ /**
+ * Represents all the substitute handlings.
+ *
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public enum SubstituteHandling {
+ /**
+ * A possible setting for SubstituteHandling. The SubstituteHandling context to be used is
+ * unknown (this is the default value).
+ *
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ UNDEFINED,
+ /**
+ * Returns a fallback value (e.g., the input code) when no data is available. This is the
+ * default behaviour.
+ *
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ SUBSTITUTE,
+
+ /**
+ * Returns a null value when no data is available.
+ *
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ NO_SUBSTITUTE;
+
+ /**
+ * Unmodifiable List of all substitute handlings constants. List version of {@link #values()}.
+ *
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public static final List<SubstituteHandling> VALUES =
+ Collections.unmodifiableList(Arrays.asList(SubstituteHandling.values()));
+ }
+
+ /**
+ * Represents all the display lengths.
+ *
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public enum DisplayLength {
+ /**
+ * A possible setting for DisplayLength. The DisplayLength context to be used is unknown (this
+ * is the default value).
+ *
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ UNDEFINED,
+ /**
+ * Uses full names when generating a locale name, e.g. "United States" for US.
+ *
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ LENGTH_FULL,
+
+ /**
+ * Use short names when generating a locale name, e.g. "U.S." for US.
+ *
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ LENGTH_SHORT;
+
+ /**
+ * Unmodifiable List of all display lengths constants. List version of {@link #values()}.
+ *
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public static final List<DisplayLength> VALUES =
+ Collections.unmodifiableList(Arrays.asList(DisplayLength.values()));
+ }
+
+ /**
+ * Represents all the capitalization options.
+ *
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public enum Capitalization {
+ /**
+ * A possible setting for Capitalization. The capitalization context to be used is unknown (this
+ * is the default value).
+ *
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ UNDEFINED,
+
+ /**
+ * The capitalization context if a date, date symbol or display name is to be formatted with
+ * capitalization appropriate for the beginning of a sentence.
+ *
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ BEGINNING_OF_SENTENCE,
+
+ /**
+ * The capitalization context if a date, date symbol or display name is to be formatted with
+ * capitalization appropriate for the middle of a sentence.
+ *
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ MIDDLE_OF_SENTENCE,
+
+ /**
+ * The capitalization context if a date, date symbol or display name is to be formatted with
+ * capitalization appropriate for stand-alone usage such as an isolated name on a calendar
+ * page.
+ *
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ STANDALONE,
+
+ /**
+ * The capitalization context if a date, date symbol or display name is to be formatted with
+ * capitalization appropriate for a user-interface list or menu item.
+ *
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ UI_LIST_OR_MENU;
+
+ /**
+ * Unmodifiable List of all the capitalizations constants. List version of {@link #values()}.
+ *
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public static final List<Capitalization> VALUES =
+ Collections.unmodifiableList(Arrays.asList(Capitalization.values()));
+ }
+
+ /**
+ * Standard CLDR plural category constants. See http://www.unicode.org/reports/tr35/tr35-numbers.html#Language_Plural_Rules
+ *
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public enum PluralCategory {
+ /**
+ * A possible setting for PluralCategory. The plural category context to be used is unknown
+ * (this is the default value).
+ *
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ UNDEFINED("undefined"),
+
+ /**
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ ZERO("zero"),
+
+ /**
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ ONE("one"),
+
+ /**
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ TWO("two"),
+
+ /**
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ FEW("few"),
+
+ /**
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ MANY("many"),
+
+ /**
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ OTHER("other");
+
+ private final String identifier;
+
+ private PluralCategory(String identifier) {
+ this.identifier = identifier;
+ }
+
+ /**
+ * Unmodifiable List of all plural categories constants. List version of {@link #values()}.
+ *
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public static final List<PluralCategory> VALUES =
+ Collections.unmodifiableList(Arrays.asList(PluralCategory.values()));
+
+ /**
+ * @return the lowercase CLDR keyword string for the plural category
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public final String getIdentifier() {
+ return this.identifier;
+ }
+
+ /**
+ * @param identifier in lower case such as "few" or "other"
+ * @return the plural category corresponding to the identifier, or {@code UNDEFINED}
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public static final PluralCategory fromIdentifier(String identifier) {
+ if (identifier == null) {
+ return PluralCategory.UNDEFINED;
+ }
+
+ for (PluralCategory pluralCategory : VALUES) {
+ if (identifier.equals(pluralCategory.getIdentifier())) {
+ return pluralCategory;
+ }
+ }
+
+ return PluralCategory.UNDEFINED;
+ }
+ }
+
+ /**
+ * Represents all the grammatical cases that are supported by CLDR.
+ *
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public enum GrammaticalCase {
+ /**
+ * A possible setting for GrammaticalCase. The grammatical case context to be used is unknown
+ * (this is the default value).
+ *
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ UNDEFINED("undefined"),
+
+ /**
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ ABLATIVE("ablative"),
+
+ /**
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ ACCUSATIVE("accusative"),
+
+ /**
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ COMITATIVE("comitative"),
+
+ /**
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ DATIVE("dative"),
+
+ /**
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ ERGATIVE("ergative"),
+
+ /**
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ GENITIVE("genitive"),
+
+ /**
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ INSTRUMENTAL("instrumental"),
+
+ /**
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ LOCATIVE("locative"),
+
+ /**
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ LOCATIVE_COPULATIVE("locative_copulative"),
+
+ /**
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ NOMINATIVE("nominative"),
+
+ /**
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ OBLIQUE("oblique"),
+
+ /**
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ PREPOSITIONAL("prepositional"),
+
+ /**
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ SOCIATIVE("sociative"),
+
+ /**
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ VOCATIVE("vocative");
+
+ private final String identifier;
+
+ private GrammaticalCase(String identifier) {
+ this.identifier = identifier;
+ }
+
+ /**
+ * Unmodifiable List of all grammatical cases constants. List version of {@link #values()}.
+ *
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public static final List<GrammaticalCase> VALUES =
+ Collections.unmodifiableList(Arrays.asList(GrammaticalCase.values()));
+
+ /**
+ * @return the lowercase CLDR keyword string for the grammatical case.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public final String getIdentifier() {
+ return this.identifier;
+ }
+
+ /**
+ * @param identifier in lower case such as "dative" or "nominative"
+ * @return the plural category corresponding to the identifier, or {@code UNDEFINED}
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public static final GrammaticalCase fromIdentifier(String identifier) {
+ if (identifier == null) {
+ return GrammaticalCase.UNDEFINED;
+ }
+
+ for (GrammaticalCase grammaticalCase : VALUES) {
+ if (identifier.equals(grammaticalCase.getIdentifier())) {
+ return grammaticalCase;
+ }
+ }
+
+ return GrammaticalCase.UNDEFINED;
+ }
+ }
+}
diff --git a/android_icu4j/src/main/java/android/icu/text/FormattedValue.java b/android_icu4j/src/main/java/android/icu/text/FormattedValue.java
index 2b9f3d4..a7c682e 100644
--- a/android_icu4j/src/main/java/android/icu/text/FormattedValue.java
+++ b/android_icu4j/src/main/java/android/icu/text/FormattedValue.java
@@ -43,7 +43,7 @@
* To loop over all field positions:
*
* <pre>
- * ConstrainableFieldPosition cfpos = new ConstrainableFieldPosition();
+ * ConstrainedFieldPosition cfpos = new ConstrainedFieldPosition();
* while (fmtval.nextPosition(cfpos)) {
* // handle the field position; get information from cfpos
* }
diff --git a/android_icu4j/src/main/java/android/icu/text/NumberFormat.java b/android_icu4j/src/main/java/android/icu/text/NumberFormat.java
index 9fb8c24..c03bc08 100644
--- a/android_icu4j/src/main/java/android/icu/text/NumberFormat.java
+++ b/android_icu4j/src/main/java/android/icu/text/NumberFormat.java
@@ -1866,6 +1866,12 @@
public static final Field COMPACT = new Field("compact");
/**
+ * Approximately sign. In ICU 70, this was categorized under the generic SIGN field.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public static final Field APPROXIMATELY_SIGN = new Field("approximately sign");
+
+ /**
* Constructs a new instance of NumberFormat.Field with the given field
* name.
*/
diff --git a/android_icu4j/src/main/java/android/icu/text/PersonName.java b/android_icu4j/src/main/java/android/icu/text/PersonName.java
new file mode 100644
index 0000000..634abf4
--- /dev/null
+++ b/android_icu4j/src/main/java/android/icu/text/PersonName.java
@@ -0,0 +1,272 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+// © 2022 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html
+package android.icu.text;
+
+import java.util.Locale;
+import java.util.Set;
+
+/**
+ * An object used to provide name data to the PersonNameFormatter for formatting.
+ * Clients can implement this interface to talk directly to some other subsystem
+ * that actually contains the name data (instead of having to copy it into a separate
+ * object just for formatting) or to override the default modifier behavior described
+ * above. A concrete SimplePersonName object that does store the field values directly
+ * is provided.
+ *
+ * @see SimplePersonName
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+@Deprecated
+public interface PersonName {
+ //==============================================================================
+ // Identifiers used to request field values from the PersonName object
+
+ /**
+ * Identifiers for the name fields supported by the PersonName object.
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ enum NameField {
+ /**
+ * Contains titles and other words that precede the actual name, such as "Mr."
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ PREFIX("prefix"),
+
+ /**
+ * The given name. May contain more than one token.
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ GIVEN("given"),
+
+ /**
+ * Additional given names. (In English, this is usually the "middle name" and
+ * may contain more than one word.)
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ GIVEN2("given2"),
+
+ /**
+ * The surname. In Spanish, this is the patronymic surname.
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ SURNAME("surname"),
+
+ /**
+ * Additional surnames. This is only used in a few languages, such as Spanish,
+ * where it is the matronymic surname. (In most languages, multiple surnames all
+ * just go in the SURNAME field.)
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ SURNAME2("surname2"),
+
+ /**
+ * Generational and professional qualifiers that generally follow the actual name,
+ * such as "Jr." or "M.D."
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ SUFFIX("suffix"),
+
+ /**
+ * The preferred field order for the name. PersonName objects generally shouldn't provide
+ * this field, allowing the PersonNameFormatter to deduce the proper field order based on
+ * the locales of the name of the formatter. But this can be used to force a particular
+ * field order, generally in cases where the deduction logic in PersonNameFormatter would
+ * guess wrong. When used, the only valid values are "givenFirst" and "surnameFirst".
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ PREFERRED_ORDER("preferredOrder");
+
+ private final String name;
+
+ private NameField(String name) {
+ this.name = name;
+ }
+
+ /**
+ * Returns the NameField's display name.
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ @Override
+ public String toString() {
+ return name;
+ }
+
+ /**
+ * Returns the appropriate NameField for its display name.
+ * @deprecated This API is for ICU internal use only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public static NameField forString(String name) {
+ for (NameField field : values()) {
+ if (field.name.equals(name)) {
+ return field;
+ }
+ }
+ throw new IllegalArgumentException("Invalid field name " + name);
+ }
+ }
+
+ /**
+ * Identifiers for the name field modifiers supported by the PersonName and PersonNameFormatter objects.
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ enum FieldModifier {
+ /**
+ * Requests an "informal" variant of the field, generally a nickname of some type:
+ * if "given" is "James", "given-informal" might be "Jimmy". Only applied to the "given"
+ * field. If the PersonName object doesn't apply this modifier, PersonNameFormatter just
+ * uses the unmodified version of "given".
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ INFORMAL("informal"),
+
+ /**
+ * If the field contains a main word with one or more separate prefixes, such as
+ * "van den Hul", this requests just the prefixes ("van den"). Only applied to the "surname"
+ * field. If the PersonName object doesn't apply this modifier, PersonNameFormatter
+ * assumes there are no prefixes.
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ PREFIX("prefix"),
+
+ /**
+ * If the field contains a main word with one or more separate prefixes, such as
+ * "van den Hul", this requests just the main word ("Hul"). Only applied to the "surname"
+ * field. If the implementing class doesn't apply this modifier, PersonNameFormatter
+ * assumes the entire "surname" field is the "core".
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ CORE("core"),
+
+ /**
+ * Requests an initial for the specified field. PersonNameFormatter will do
+ * this algorithmically, but a PersonName object can apply this modifier itself if it wants
+ * different initial-generation logic (or stores the initial separately).
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ INITIAL("initial"),
+
+ /**
+ * Requests an initial for the specified field, suitable for use in a monogram
+ * (this usually differs from "initial" in that "initial" often adds a period and "monogram"
+ * never does). PersonNameFormatter will do this algorithmically, but a PersonName object can
+ * apply this modifier itself if it wants different monogram-generation logic.
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ MONOGRAM("monogram"),
+
+ /**
+ * Requests the field value converted to ALL CAPS. PersonName objects
+ * generally won't need to handle this modifier themselves.
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ ALL_CAPS("allCaps"),
+
+ /**
+ * Requests the field value with the first grapheme of each word converted to titlecase.
+ * A PersonName object might handle this modifier itself to capitalize words more
+ * selectively.
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ INITIAL_CAP("initialCap");
+
+ private final String name;
+
+ private FieldModifier(String name) {
+ this.name = name;
+ }
+
+ /**
+ * Returns the FieldModifier's display name.
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ @Override
+ public String toString() {
+ return name;
+ }
+
+ /**
+ * Returns the appropriate fieldModifier for its display name.
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public static FieldModifier forString(String name) {
+ for (FieldModifier modifier : values()) {
+ if (modifier.name.equals(name)) {
+ return modifier;
+ }
+ }
+ throw new IllegalArgumentException("Invalid modifier name " + name);
+ }
+ }
+
+ //==============================================================================
+ // Public API on PersonName
+ /**
+ * Returns the locale of the name-- that is, the language or country of origin for the person being named.
+ * An implementing class is allowed to return null here to indicate the name's locale is unknown.
+ *
+ * @return The name's locale, or null if it's not known.
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Locale getNameLocale();
+
+ /**
+ * Returns one field of the name, possibly in a modified form.
+ *
+ * @param identifier The identifier of the requested field.
+ * @param modifiers An **IN/OUT** parameter that specifies modifiers to apply to the basic field value.
+ * An implementing class can choose to handle or ignore any modifiers; it should modify
+ * the passed-in Set so that on exit, it contains only the requested modifiers that it
+ * DIDN'T handle. This parameter may not be null, and must either be mutable or empty.
+ * @return The value of the requested field, optionally modified by some or all of the requested modifiers, or
+ * null if the requested field isn't present in the name.
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public String getFieldValue(NameField identifier, Set<FieldModifier> modifiers);
+}
diff --git a/android_icu4j/src/main/java/android/icu/text/PersonNameFormatter.java b/android_icu4j/src/main/java/android/icu/text/PersonNameFormatter.java
new file mode 100644
index 0000000..034deee
--- /dev/null
+++ b/android_icu4j/src/main/java/android/icu/text/PersonNameFormatter.java
@@ -0,0 +1,370 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+// © 2022 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html
+package android.icu.text;
+
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Set;
+
+import android.icu.impl.personname.PersonNameFormatterImpl;
+
+/**
+ * A class for formatting names of people. Takes raw name data for a person and renders it into a string according to
+ * the caller's specifications, taking into account how people's names are rendered in the caller's locale.
+ *
+ * The Length, Usage, and Formality options can be used to get a wide variety of results. In English, they would
+ * produce results along these lines:
+ * <table border="1">
+ * <tr>
+ * <th rowspan="2">
+ * </th>
+ * <th colspan="2">
+ * REFERRING
+ * </th>
+ * <th colspan="2">
+ * ADDRESSING
+ * </th>
+ * <th colspan="2">
+ * MONOGRAM
+ * </th>
+ * </tr>
+ * <tr>
+ * <th>FORMAL</th>
+ * <th>INFORMAL</th>
+ * <th>FORMAL</th>
+ * <th>INFORMAL</th>
+ * <th>FORMAL</th>
+ * <th>INFORMAL</th>
+ * </tr>
+ * <tr>
+ * <th>LONG</th>
+ * <td>James Earl Carter Jr.</td>
+ * <td>Jimmy Carter</td>
+ * <td>Mr. Carter</td>
+ * <td>Jimmy</td>
+ * <td>JEC</td>
+ * <td>JC</td>
+ * </tr>
+ * <tr>
+ * <th>MEDIUM</th>
+ * <td>James E. Carter Jr.</td>
+ * <td>Jimmy Carter</td>
+ * <td>Mr. Carter</td>
+ * <td>Jimmy</td>
+ * <td>C</td>
+ * <td>J</td>
+ * </tr>
+ * <tr>
+ * <th>SHORT</th>
+ * <td>J. E. Carter</td>
+ * <td>Jimmy Carter</td>
+ * <td>Mr. Carter</td>
+ * <td>Jimmy</td>
+ * <td>C</td>
+ * <td>J</td>
+ * </tr>
+ * </table>
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+@Deprecated
+public class PersonNameFormatter {
+ //==============================================================================
+ // Parameters that control formatting behavior
+
+ /**
+ * Specifies the desired length of the formatted name.
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public enum Length {
+ /**
+ * The longest name length. Generally uses most of the fields in the name object.
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ LONG,
+
+ /**
+ * The most typical name length. Generally includes the given name and surname, but generally
+ * not most of the other fields.
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ MEDIUM,
+
+ /**
+ * A shortened name. Skips most fields and may abbreviate some name fields to just their initials.
+ * When Formality is INFORMAL, may only include one field.
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ SHORT
+ }
+
+ /**
+ * Specifies the intended usage of the formatted name.
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public enum Usage {
+ /**
+ * Used for when the name is going to be used to address the user directly: "Turn left here, John."
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ ADDRESSING,
+
+ /**
+ * Used in general cases, when the name is used to refer to somebody else.
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ REFERRING,
+
+ /**
+ * Used to generate monograms, short 1 to 3-character versions of the name suitable for use in things
+ * like chat avatars. In English, this is usually the person's initials, but this isn't true in all
+ * languages. When the caller specifies Usage.MONOGRAM, the Length parameter can be used to get different
+ * lengths of monograms: Length.SHORT is generally a single letter; Length.LONG may be as many as three or four.
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ MONOGRAM
+ }
+
+ /**
+ * Specifies the intended formality of the formatted name.
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public enum Formality {
+ /**
+ * The more formal version of the name.
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ FORMAL,
+
+ /**
+ * The more informal version of the name. In English, this might omit fields or use the "informal" variant
+ * of the given name.
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ INFORMAL
+ }
+
+ /**
+ * Additional options to customize the behavior of the formatter.
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public enum Options {
+ /**
+ * Causes the formatter to generate results suitable for inclusion in a sorted list. For GN-first languages,
+ * this generally means moving the surname to the beginning of the string, with a comma between it and
+ * the rest of the name: e.g., "Carter, James E. Jr.".
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ SORTING,
+
+ /**
+ * Requests that the surname in the formatted result be rendered in ALL CAPS. This is often done with
+ * Japanese names to highlight which name is the surname.
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ SURNAME_ALLCAPS
+ }
+
+ private final PersonNameFormatterImpl impl;
+
+ //==============================================================================
+ // Builder for PersonNameFormatter
+
+ /**
+ * A utility class that can be used to construct a PersonNameFormatter.
+ * Use PersonNameFormatter.builder() to get a new instance.
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public static class Builder {
+ /**
+ * Sets the locale for the formatter to be constructed.
+ * @param locale The new formatter locale. May not be null.
+ * @return This builder.
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Builder setLocale(Locale locale) {
+ if (locale != null) {
+ this.locale = locale;
+ }
+ return this;
+ }
+
+ /**
+ * Sets the name length for the formatter to be constructed.
+ * @param length The new name length.
+ * @return This builder.
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Builder setLength(Length length) {
+ this.length = length;
+ return this;
+ }
+
+ /**
+ * Sets the name usage for the formatter to be constructed.
+ * @param usage The new name length.
+ * @return This builder.
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Builder setUsage(Usage usage) {
+ this.usage = usage;
+ return this;
+ }
+
+ /**
+ * Sets the name formality for the formatter to be constructed.
+ * @param formality The new name length.
+ * @return This builder.
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Builder setFormality(Formality formality) {
+ this.formality = formality;
+ return this;
+ }
+
+ /**
+ * Sets the options set for the formatter to be constructed. The Set passed in
+ * here replaces the entire options set the builder already has (if one has
+ * already been set); this method doesn't modify the builder's options set.
+ * @param options The new options set.
+ * @return This builder.
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Builder setOptions(Set<Options> options) {
+ this.options = options;
+ return this;
+ }
+
+ /**
+ * Returns a new PersonNameFormatter with the values that were passed to this builder.
+ * This method doesn't freeze or delete the builder; you can call build() more than once
+ * (presumably after calling the other methods to change the parameter) to create more
+ * than one PersonNameFormatter; you don't need a new Builder for each PersonNameFormatter.
+ * @return A new PersonNameFormatter.
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public PersonNameFormatter build() {
+ return new PersonNameFormatter(locale, length, usage, formality, options);
+ }
+
+ private Builder() {
+ }
+
+ private Locale locale = Locale.getDefault();
+ private Length length = Length.MEDIUM;
+ private Usage usage = Usage.REFERRING;
+ private Formality formality = Formality.FORMAL;
+ private Set<Options> options = new HashSet<>();
+ }
+
+ //==============================================================================
+ // Public API on PersonNameFormatter
+
+ /**
+ * Returns a Builder object that can be used to construct a new PersonNameFormatter.
+ * @return A new Builder.
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Returns a Builder object whose fields match those used to construct this formatter,
+ * allowing a new formatter to be created based on this one.
+ * @return A new Builder that can be used to create a new formatter based on this formatter.
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Builder toBuilder() {
+ Builder builder = builder();
+ builder.setLocale(impl.getLocale());
+ builder.setLength(impl.getLength());
+ builder.setUsage(impl.getUsage());
+ builder.setFormality(impl.getFormality());
+ builder.setOptions(impl.getOptions());
+ return builder;
+ }
+
+ /**
+ * Formats a name.
+ * @param name A PersonName object that supplies individual field values (optionally, with modifiers applied)
+ * to the formatter for formatting.
+ * @return The name, formatted according to the locale and other parameters passed to the formatter's constructor.
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public String formatToString(PersonName name) {
+ // TODO: Add a format() method that returns a FormattedPersonName object that descends from FormattedValue.
+ return impl.formatToString(name);
+ }
+
+ //==============================================================================
+ // Internal implementation
+ private PersonNameFormatter(Locale locale, Length length, Usage usage, Formality formality, Set<Options> options) {
+ this.impl = new PersonNameFormatterImpl(locale, length, usage, formality, options);
+ }
+
+ /**
+ * @deprecated This API is for unit testing only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public PersonNameFormatter(Locale locale, String[] patterns) {
+ this.impl = new PersonNameFormatterImpl(locale, patterns);
+ }
+}
diff --git a/android_icu4j/src/main/java/android/icu/text/PluralRules.java b/android_icu4j/src/main/java/android/icu/text/PluralRules.java
index dce91c1..c7bab06 100644
--- a/android_icu4j/src/main/java/android/icu/text/PluralRules.java
+++ b/android_icu4j/src/main/java/android/icu/text/PluralRules.java
@@ -16,21 +16,22 @@
import java.io.ObjectOutputStream;
import java.io.ObjectStreamException;
import java.io.Serializable;
+import java.math.BigDecimal;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
-import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
-import java.util.TreeSet;
import java.util.regex.Pattern;
import android.icu.impl.PluralRulesLoader;
import android.icu.impl.StandardPlural;
+import android.icu.impl.number.DecimalQuantity;
+import android.icu.impl.number.DecimalQuantity_DualStorageBCD;
import android.icu.impl.number.range.StandardPluralRanges;
import android.icu.number.FormattedNumber;
import android.icu.number.FormattedNumberRange;
@@ -334,6 +335,16 @@
public static final double NO_UNIQUE_VALUE = -0.00123456777;
/**
+ * Value returned by {@link #getUniqueKeywordDecimalQuantityValue} when there is no
+ * unique value to return.
+ * @deprecated This API is ICU internal only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public static final DecimalQuantity NO_UNIQUE_VALUE_DECIMAL_QUANTITY =
+ new DecimalQuantity_DualStorageBCD(-0.00123456777);
+
+ /**
* Type of plurals and PluralRules.
*/
public enum PluralType {
@@ -908,51 +919,6 @@
}
/**
- * @deprecated This API is ICU internal only.
- * @hide original deprecated declaration
- * @hide draft / provisional / internal are hidden on Android
- */
- @Deprecated
- public FixedDecimal (String n) {
- // Ugly, but for samples we don't care.
- this(parseDecimalSampleRangeNumString(n));
- }
-
- /**
- * @deprecated This API is ICU internal only
- * @hide draft / provisional / internal are hidden on Android
- */
- @Deprecated
- private static FixedDecimal parseDecimalSampleRangeNumString(String num) {
- if (num.contains("e") || num.contains("c")) {
- int ePos = num.lastIndexOf('e');
- if (ePos < 0) {
- ePos = num.lastIndexOf('c');
- }
- int expNumPos = ePos + 1;
- String exponentStr = num.substring(expNumPos);
- int exponent = Integer.parseInt(exponentStr);
- String fractionStr = num.substring(0, ePos);
- return FixedDecimal.createWithExponent(
- Double.parseDouble(fractionStr),
- getVisibleFractionCount(fractionStr),
- exponent);
- } else {
- return new FixedDecimal(Double.parseDouble(num), getVisibleFractionCount(num));
- }
- }
-
- private static int getVisibleFractionCount(String value) {
- value = value.trim();
- int decimalPos = value.indexOf('.') + 1;
- if (decimalPos == 0) {
- return 0;
- } else {
- return value.length() - decimalPos;
- }
- }
-
- /**
* {@inheritDoc}
*
* @deprecated This API is ICU internal only.
@@ -1121,20 +1087,6 @@
return (isNegative ? -source : source) * Math.pow(10, exponent);
}
- /**
- * @deprecated This API is ICU internal only.
- * @hide original deprecated declaration
- * @hide draft / provisional / internal are hidden on Android
- */
- @Deprecated
- public long getShiftedValue() {
- if (exponent != 0 && visibleDecimalDigitCount == 0 && decimalDigits == 0) {
- // Need to take exponent into account if we have it
- return (long)(source * Math.pow(10, exponent));
- }
- return integerValue * baseFactor + decimalDigits;
- }
-
private void writeObject(
ObjectOutputStream out)
throws IOException {
@@ -1195,36 +1147,33 @@
}
/**
- * A range of NumberInfo that includes all values with the same visibleFractionDigitCount.
+ * A range of DecimalQuantity representing PluralRules samples that includes
+ * all values with the same visibleFractionDigitCount.
* @deprecated This API is ICU internal only.
* @hide Only a subset of ICU is exposed in Android
- * @hide original deprecated declaration
* @hide draft / provisional / internal are hidden on Android
*/
@Deprecated
- public static class FixedDecimalRange {
+ public static class DecimalQuantitySamplesRange {
/**
* @deprecated This API is ICU internal only.
- * @hide original deprecated declaration
* @hide draft / provisional / internal are hidden on Android
*/
@Deprecated
- public final FixedDecimal start;
+ public final DecimalQuantity start;
/**
* @deprecated This API is ICU internal only.
- * @hide original deprecated declaration
* @hide draft / provisional / internal are hidden on Android
*/
@Deprecated
- public final FixedDecimal end;
+ public final DecimalQuantity end;
/**
* @deprecated This API is ICU internal only.
- * @hide original deprecated declaration
* @hide draft / provisional / internal are hidden on Android
*/
@Deprecated
- public FixedDecimalRange(FixedDecimal start, FixedDecimal end) {
- if (start.visibleDecimalDigitCount != end.visibleDecimalDigitCount) {
+ public DecimalQuantitySamplesRange(DecimalQuantity start, DecimalQuantity end) {
+ if (start.getPluralOperand(Operand.v)!= end.getPluralOperand(Operand.v)) {
throw new IllegalArgumentException("Ranges must have the same number of visible decimals: " + start + "~" + end);
}
this.start = start;
@@ -1232,42 +1181,38 @@
}
/**
* @deprecated This API is ICU internal only.
- * @hide original deprecated declaration
* @hide draft / provisional / internal are hidden on Android
*/
@Deprecated
@Override
public String toString() {
- return start + (end == start ? "" : "~" + end);
+ return start.toExponentString() + (end == start ? "" : "~" + end.toExponentString());
}
}
/**
- * A list of NumberInfo that includes all values with the same visibleFractionDigitCount.
+ * A list of DecimalQuantity representing PluralRules that includes all
+ * values with the same visibleFractionDigitCount.
* @deprecated This API is ICU internal only.
* @hide Only a subset of ICU is exposed in Android
- * @hide original deprecated declaration
* @hide draft / provisional / internal are hidden on Android
*/
@Deprecated
- public static class FixedDecimalSamples {
+ public static class DecimalQuantitySamples {
/**
* @deprecated This API is ICU internal only.
- * @hide original deprecated declaration
* @hide draft / provisional / internal are hidden on Android
*/
@Deprecated
public final SampleType sampleType;
/**
* @deprecated This API is ICU internal only.
- * @hide original deprecated declaration
* @hide draft / provisional / internal are hidden on Android
*/
@Deprecated
- public final Set<FixedDecimalRange> samples;
+ public final Set<DecimalQuantitySamplesRange> samples;
/**
* @deprecated This API is ICU internal only.
- * @hide original deprecated declaration
* @hide draft / provisional / internal are hidden on Android
*/
@Deprecated
@@ -1277,7 +1222,7 @@
* @param sampleType
* @param samples
*/
- private FixedDecimalSamples(SampleType sampleType, Set<FixedDecimalRange> samples, boolean bounded) {
+ private DecimalQuantitySamples(SampleType sampleType, Set<DecimalQuantitySamplesRange> samples, boolean bounded) {
super();
this.sampleType = sampleType;
this.samples = samples;
@@ -1286,11 +1231,11 @@
/*
* Parse a list of the form described in CLDR. The source must be trimmed.
*/
- static FixedDecimalSamples parse(String source) {
+ static DecimalQuantitySamples parse(String source) {
SampleType sampleType2;
boolean bounded2 = true;
boolean haveBound = false;
- Set<FixedDecimalRange> samples2 = new LinkedHashSet<>();
+ Set<DecimalQuantitySamplesRange> samples2 = new LinkedHashSet<>();
if (source.startsWith("integer")) {
sampleType2 = SampleType.INTEGER;
@@ -1301,7 +1246,7 @@
}
source = source.substring(7).trim(); // remove both
- for (String range : COMMA_SEPARATED.split(source)) {
+ for (String range : COMMA_SEPARATED.split(source, 0)) {
if (range.equals("…") || range.equals("...")) {
bounded2 = false;
haveBound = true;
@@ -1310,54 +1255,107 @@
if (haveBound) {
throw new IllegalArgumentException("Can only have … at the end of samples: " + range);
}
- String[] rangeParts = TILDE_SEPARATED.split(range);
+ String[] rangeParts = TILDE_SEPARATED.split(range, 0);
switch (rangeParts.length) {
case 1:
- FixedDecimal sample = new FixedDecimal(rangeParts[0]);
+ DecimalQuantity sample =
+ DecimalQuantity_DualStorageBCD.fromExponentString(rangeParts[0]);
checkDecimal(sampleType2, sample);
- samples2.add(new FixedDecimalRange(sample, sample));
+ samples2.add(new DecimalQuantitySamplesRange(sample, sample));
break;
case 2:
- FixedDecimal start = new FixedDecimal(rangeParts[0]);
- FixedDecimal end = new FixedDecimal(rangeParts[1]);
+ DecimalQuantity start =
+ DecimalQuantity_DualStorageBCD.fromExponentString(rangeParts[0]);
+ DecimalQuantity end =
+ DecimalQuantity_DualStorageBCD.fromExponentString(rangeParts[1]);
checkDecimal(sampleType2, start);
checkDecimal(sampleType2, end);
- samples2.add(new FixedDecimalRange(start, end));
+ samples2.add(new DecimalQuantitySamplesRange(start, end));
break;
default: throw new IllegalArgumentException("Ill-formed number range: " + range);
}
}
- return new FixedDecimalSamples(sampleType2, Collections.unmodifiableSet(samples2), bounded2);
+ return new DecimalQuantitySamples(sampleType2, Collections.unmodifiableSet(samples2), bounded2);
}
- private static void checkDecimal(SampleType sampleType2, FixedDecimal sample) {
- if ((sampleType2 == SampleType.INTEGER) != (sample.getVisibleDecimalDigitCount() == 0)) {
+ private static void checkDecimal(SampleType sampleType2, DecimalQuantity sample) {
+ // TODO(CLDR-15452): Remove the need for the fallback check for exponent notation integers classified
+ // as "@decimal" type samples, if/when changes are made to
+ // resolve https://unicode-org.atlassian.net/browse/CLDR-15452
+ if ((sampleType2 == SampleType.INTEGER && sample.getPluralOperand(Operand.v) != 0)
+ || (sampleType2 == SampleType.DECIMAL && sample.getPluralOperand(Operand.v) == 0
+ && sample.getPluralOperand(Operand.e) == 0)) {
throw new IllegalArgumentException("Ill-formed number range: " + sample);
}
}
/**
* @deprecated This API is ICU internal only.
- * @hide original deprecated declaration
* @hide draft / provisional / internal are hidden on Android
*/
@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
- long startDouble = item.start.getShiftedValue();
- long endDouble = item.end.getShiftedValue();
-
- for (long d = startDouble; d <= endDouble; d += 1) {
- result.add(d/(double)item.start.baseFactor);
- }
- }
+ public Collection<Double> addSamples(Collection<Double> result) {
+ addSamples(result, null);
return result;
}
/**
* @deprecated This API is ICU internal only.
- * @hide original deprecated declaration
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Collection<DecimalQuantity> addDecimalQuantitySamples(Collection<DecimalQuantity> result) {
+ addSamples(null, result);
+ return result;
+ }
+
+ /**
+ * @deprecated This API is ICU internal only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public void addSamples(Collection<Double> doubleResult, Collection<DecimalQuantity> dqResult) {
+ if ((doubleResult == null && dqResult == null)
+ || (doubleResult != null && dqResult != null)) {
+ return;
+ }
+ boolean isDouble = doubleResult != null;
+ for (DecimalQuantitySamplesRange range : samples) {
+ DecimalQuantity start = range.start;
+ DecimalQuantity end = range.end;
+ int lowerDispMag = start.getLowerDisplayMagnitude();
+ int exponent = start.getExponent();
+ int incrementScale = lowerDispMag + exponent;
+ BigDecimal incrementBd = BigDecimal.ONE.movePointRight(incrementScale);
+
+ for (DecimalQuantity dq = start.createCopy(); dq.toDouble() <= end.toDouble(); ) {
+ if (isDouble) {
+ double dblValue = dq.toDouble();
+ // Hack Alert: don't return any decimal samples with integer values that
+ // originated from a format with trailing decimals.
+ // This API is returning doubles, which can't distinguish having displayed
+ // zeros to the right of the decimal.
+ // This results in test failures with values mapping back to a different keyword.
+ if (!(dblValue == Math.floor(dblValue)) && dq.getPluralOperand(Operand.v) > 0) {
+ doubleResult.add(dblValue);
+ }
+ } else {
+ dqResult.add(dq);
+ }
+
+ // Increment dq for next iteration
+ java.math.BigDecimal dqBd = dq.toBigDecimal();
+ java.math.BigDecimal newDqBd = dqBd.add(incrementBd);
+ dq = new DecimalQuantity_DualStorageBCD(newDqBd);
+ dq.setMinFraction(-lowerDispMag);
+ dq.adjustMagnitude(-exponent);
+ dq.adjustExponent(exponent);
+ }
+ }
+ }
+
+ /**
+ * @deprecated This API is ICU internal only.
* @hide draft / provisional / internal are hidden on Android
*/
@Deprecated
@@ -1365,7 +1363,7 @@
public String toString() {
StringBuilder b = new StringBuilder("@").append(sampleType.toString().toLowerCase(Locale.ENGLISH));
boolean first = true;
- for (FixedDecimalRange item : samples) {
+ for (DecimalQuantitySamplesRange item : samples) {
if (first) {
first = false;
} else {
@@ -1381,24 +1379,22 @@
/**
* @deprecated This API is ICU internal only.
- * @hide original deprecated declaration
* @hide draft / provisional / internal are hidden on Android
*/
@Deprecated
- public Set<FixedDecimalRange> getSamples() {
+ public Set<DecimalQuantitySamplesRange> getSamples() {
return samples;
}
/**
* @deprecated This API is ICU internal only.
- * @hide original deprecated declaration
* @hide draft / provisional / internal are hidden on Android
*/
@Deprecated
- public void getStartEndSamples(Set<FixedDecimal> target) {
- for (FixedDecimalRange item : samples) {
- target.add(item.start);
- target.add(item.end);
+ public void getStartEndSamples(Set<DecimalQuantity> target) {
+ for (DecimalQuantitySamplesRange range : samples) {
+ target.add(range.start);
+ target.add(range.end);
}
}
}
@@ -1479,10 +1475,10 @@
throws ParseException {
Constraint result = null;
- String[] or_together = OR_SEPARATED.split(description);
+ String[] or_together = OR_SEPARATED.split(description, 0);
for (int i = 0; i < or_together.length; ++i) {
Constraint andConstraint = null;
- String[] and_together = AND_SEPARATED.split(or_together[i]);
+ String[] and_together = AND_SEPARATED.split(or_together[i], 0);
for (int j = 0; j < and_together.length; ++j) {
Constraint newConstraint = NO_CONSTRAINT;
@@ -1677,21 +1673,21 @@
}
description = description.substring(x+1).trim();
- String[] constraintOrSamples = AT_SEPARATED.split(description);
+ String[] constraintOrSamples = AT_SEPARATED.split(description, 0);
boolean sampleFailure = false;
- FixedDecimalSamples integerSamples = null, decimalSamples = null;
+ DecimalQuantitySamples integerSamples = null, decimalSamples = null;
switch (constraintOrSamples.length) {
case 1: break;
case 2:
- integerSamples = FixedDecimalSamples.parse(constraintOrSamples[1]);
+ integerSamples = DecimalQuantitySamples.parse(constraintOrSamples[1]);
if (integerSamples.sampleType == SampleType.DECIMAL) {
decimalSamples = integerSamples;
integerSamples = null;
}
break;
case 3:
- integerSamples = FixedDecimalSamples.parse(constraintOrSamples[1]);
- decimalSamples = FixedDecimalSamples.parse(constraintOrSamples[2]);
+ integerSamples = DecimalQuantitySamples.parse(constraintOrSamples[1]);
+ decimalSamples = DecimalQuantitySamples.parse(constraintOrSamples[2]);
if (integerSamples.sampleType != SampleType.INTEGER || decimalSamples.sampleType != SampleType.DECIMAL) {
throw new IllegalArgumentException("Must have @integer then @decimal in " + description);
}
@@ -1731,7 +1727,7 @@
if (description.endsWith(";")) {
description = description.substring(0,description.length()-1);
}
- String[] rules = SEMI_SEPARATED.split(description);
+ String[] rules = SEMI_SEPARATED.split(description, 0);
for (int i = 0; i < rules.length; ++i) {
Rule rule = parseRule(rules[i].trim());
result.hasExplicitBoundingInfo |= rule.integerSamples != null || rule.decimalSamples != null;
@@ -1926,10 +1922,10 @@
private static final long serialVersionUID = 1;
private final String keyword;
private final Constraint constraint;
- private final FixedDecimalSamples integerSamples;
- private final FixedDecimalSamples decimalSamples;
+ private final DecimalQuantitySamples integerSamples;
+ private final DecimalQuantitySamples decimalSamples;
- public Rule(String keyword, Constraint constraint, FixedDecimalSamples integerSamples, FixedDecimalSamples decimalSamples) {
+ public Rule(String keyword, Constraint constraint, DecimalQuantitySamples integerSamples, DecimalQuantitySamples decimalSamples) {
this.keyword = keyword;
this.constraint = constraint;
this.integerSamples = integerSamples;
@@ -2040,7 +2036,7 @@
public boolean isLimited(String keyword, SampleType sampleType) {
if (hasExplicitBoundingInfo) {
- FixedDecimalSamples mySamples = getDecimalSamples(keyword, sampleType);
+ DecimalQuantitySamples mySamples = getDecimalSamples(keyword, sampleType);
return mySamples == null ? true : mySamples.bounded;
}
@@ -2092,7 +2088,7 @@
return false;
}
- public FixedDecimalSamples getDecimalSamples(String keyword, SampleType sampleType) {
+ public DecimalQuantitySamples getDecimalSamples(String keyword, SampleType sampleType) {
for (Rule rule : rules) {
if (rule.getKeyword().equals(keyword)) {
return sampleType == SampleType.INTEGER ? rule.integerSamples : rule.decimalSamples;
@@ -2346,11 +2342,29 @@
* @return The unique value for the keyword, or NO_UNIQUE_VALUE.
*/
public double getUniqueKeywordValue(String keyword) {
- Collection<Double> values = getAllKeywordValues(keyword);
+ DecimalQuantity uniqValDq = getUniqueKeywordDecimalQuantityValue(keyword);
+ if (uniqValDq.equals(NO_UNIQUE_VALUE_DECIMAL_QUANTITY)) {
+ return NO_UNIQUE_VALUE;
+ } else {
+ return uniqValDq.toDouble();
+ }
+ }
+
+ /**
+ * Returns the unique value that this keyword matches, or {@link #NO_UNIQUE_VALUE}
+ * if the keyword matches multiple values or is not defined for this PluralRules.
+ *
+ * @param keyword the keyword to check for a unique value
+ * @deprecated This API is ICU internal only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public DecimalQuantity getUniqueKeywordDecimalQuantityValue(String keyword) {
+ Collection<DecimalQuantity> values = getAllKeywordDecimalQuantityValues(keyword);
if (values != null && values.size() == 1) {
return values.iterator().next();
}
- return NO_UNIQUE_VALUE;
+ return NO_UNIQUE_VALUE_DECIMAL_QUANTITY;
}
/**
@@ -2362,6 +2376,31 @@
* is immutable. It will be empty if the keyword is not defined.
*/
public Collection<Double> getAllKeywordValues(String keyword) {
+ Collection<DecimalQuantity> samples = getAllKeywordDecimalQuantityValues(keyword);
+ if (samples == null) {
+ return null;
+ } else {
+ Collection<Double> result = new LinkedHashSet<>();
+ for (DecimalQuantity dq : samples) {
+ result.add(dq.toDouble());
+ }
+ return result;
+ }
+ }
+
+ /**
+ * Returns all the values that trigger this keyword, or null if the number of such
+ * values is unlimited.
+ *
+ * @param keyword the keyword
+ * @return the values that trigger this keyword, or null. The returned collection
+ * is immutable. It will be empty if the keyword is not defined.
+ *
+ * @deprecated This API is ICU internal only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Collection<DecimalQuantity> getAllKeywordDecimalQuantityValues(String keyword) {
return getAllKeywordValues(keyword, SampleType.INTEGER);
}
@@ -2379,12 +2418,11 @@
* @hide draft / provisional / internal are hidden on Android
*/
@Deprecated
- public Collection<Double> getAllKeywordValues(String keyword, SampleType type) {
+ public Collection<DecimalQuantity> getAllKeywordValues(String keyword, SampleType type) {
if (!isLimited(keyword, type)) {
return null;
}
- Collection<Double> samples = getSamples(keyword, type);
- return samples == null ? null : Collections.unmodifiableCollection(samples);
+ return getDecimalQuantitySamples(keyword, type);
}
/**
@@ -2401,6 +2439,22 @@
}
/**
+ * 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.
+ *
+ * @param keyword the keyword to test
+ * @return a list of values matching the keyword.
+ * @deprecated ICU internal only
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Collection<DecimalQuantity> getDecimalQuantitySamples(String keyword) {
+ return getDecimalQuantitySamples(keyword, SampleType.INTEGER);
+ }
+
+ /**
* Returns a list of values for which select() would return that keyword,
* or null if the keyword is not defined.
* The returned collection is unmodifiable.
@@ -2417,15 +2471,43 @@
*/
@Deprecated
public Collection<Double> getSamples(String keyword, SampleType sampleType) {
+ Collection<DecimalQuantity> samples = getDecimalQuantitySamples(keyword, sampleType);
+ if (samples == null) {
+ return null;
+ } else {
+ Collection<Double> result = new LinkedHashSet<>();
+ for (DecimalQuantity dq: samples) {
+ result.add(dq.toDouble());
+ }
+ return result;
+ }
+ }
+
+ /**
+ * Returns a list of 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. The keyword might be defined, and yet have an empty set of samples,
+ * 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.
+ * @deprecated ICU internal only
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Collection<DecimalQuantity> getDecimalQuantitySamples(String keyword, SampleType sampleType) {
if (!keywords.contains(keyword)) {
return null;
}
- Set<Double> result = new TreeSet<>();
+ Set<DecimalQuantity> result = new LinkedHashSet<>();
if (rules.hasExplicitBoundingInfo) {
- FixedDecimalSamples samples = rules.getDecimalSamples(keyword, sampleType);
+ DecimalQuantitySamples samples = rules.getDecimalSamples(keyword, sampleType);
return samples == null ? Collections.unmodifiableSet(result)
- : Collections.unmodifiableSet(samples.addSamples(result));
+ : Collections.unmodifiableCollection(samples.addDecimalQuantitySamples(result));
}
// hack in case the rule is created without explicit samples
@@ -2434,31 +2516,31 @@
switch (sampleType) {
case INTEGER:
for (int i = 0; i < 200; ++i) {
- if (!addSample(keyword, i, maxCount, result)) {
+ if (!addSample(keyword, new DecimalQuantity_DualStorageBCD(i), maxCount, result)) {
break;
}
}
- addSample(keyword, 1000000, maxCount, result); // hack for Welsh
+ addSample(keyword, new DecimalQuantity_DualStorageBCD(1000000), maxCount, result); // hack for Welsh
break;
case DECIMAL:
for (int i = 0; i < 2000; ++i) {
- if (!addSample(keyword, new FixedDecimal(i/10d, 1), maxCount, result)) {
+ DecimalQuantity_DualStorageBCD nextSample = new DecimalQuantity_DualStorageBCD(i);
+ nextSample.adjustMagnitude(-1);
+ if (!addSample(keyword, nextSample, maxCount, result)) {
break;
}
}
- addSample(keyword, new FixedDecimal(1000000d, 1), maxCount, result); // hack for Welsh
+ addSample(keyword, DecimalQuantity_DualStorageBCD.fromExponentString("1000000.0"), maxCount, result); // hack for Welsh
break;
}
+
return result.size() == 0 ? null : Collections.unmodifiableSet(result);
}
- /**
- * @hide original deprecated declaration
- */
- private boolean addSample(String keyword, Number sample, int maxCount, Set<Double> result) {
- String selectedKeyword = sample instanceof FixedDecimal ? select((FixedDecimal)sample) : select(sample.doubleValue());
+ private boolean addSample(String keyword, DecimalQuantity sample, int maxCount, Set<DecimalQuantity> result) {
+ String selectedKeyword = select(sample);
if (selectedKeyword.equals(keyword)) {
- result.add(sample.doubleValue());
+ result.add(sample);
if (--maxCount < 0) {
return false;
}
@@ -2481,7 +2563,7 @@
* @hide draft / provisional / internal are hidden on Android
*/
@Deprecated
- public FixedDecimalSamples getDecimalSamples(String keyword, SampleType sampleType) {
+ public DecimalQuantitySamples getDecimalSamples(String keyword, SampleType sampleType) {
return rules.getDecimalSamples(keyword, sampleType);
}
@@ -2588,14 +2670,14 @@
* the offset used, or 0.0d if not. Internally, the offset is subtracted from each explicit value before
* checking against the keyword values.
* @param explicits
- * a set of Doubles that are used explicitly (eg [=0], "[=1]"). May be empty or null.
+ * a set of {@code DecimalQuantity}s that are used explicitly (eg [=0], "[=1]"). May be empty or null.
* @param uniqueValue
* If non null, set to the unique value.
* @return the KeywordStatus
* @hide draft / provisional / internal are hidden on Android
*/
- public KeywordStatus getKeywordStatus(String keyword, int offset, Set<Double> explicits,
- Output<Double> uniqueValue) {
+ public KeywordStatus getKeywordStatus(String keyword, int offset, Set<DecimalQuantity> explicits,
+ Output<DecimalQuantity> uniqueValue) {
return getKeywordStatus(keyword, offset, explicits, uniqueValue, SampleType.INTEGER);
}
/**
@@ -2607,19 +2689,18 @@
* the offset used, or 0.0d if not. Internally, the offset is subtracted from each explicit value before
* checking against the keyword values.
* @param explicits
- * a set of Doubles that are used explicitly (eg [=0], "[=1]"). May be empty or null.
+ * a set of {@code DecimalQuantity}s 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
* @deprecated This API is ICU internal only.
- * @hide original deprecated declaration
* @hide draft / provisional / internal are hidden on Android
*/
@Deprecated
- public KeywordStatus getKeywordStatus(String keyword, int offset, Set<Double> explicits,
- Output<Double> uniqueValue, SampleType sampleType) {
+ public KeywordStatus getKeywordStatus(String keyword, int offset,
+ Set<DecimalQuantity> explicits, Output<DecimalQuantity> uniqueValue, SampleType sampleType) {
if (uniqueValue != null) {
uniqueValue.value = null;
}
@@ -2632,7 +2713,7 @@
return KeywordStatus.UNBOUNDED;
}
- Collection<Double> values = getSamples(keyword, sampleType);
+ Collection<DecimalQuantity> values = getDecimalQuantitySamples(keyword, sampleType);
int originalSize = values.size();
@@ -2654,9 +2735,12 @@
// Compute if the quick test is insufficient.
- HashSet<Double> subtractedSet = new HashSet<>(values);
- for (Double explicit : explicits) {
- subtractedSet.remove(explicit - offset);
+ ArrayList<DecimalQuantity> subtractedSet = new ArrayList<>(values);
+ for (DecimalQuantity explicit : explicits) {
+ BigDecimal explicitBd = explicit.toBigDecimal();
+ BigDecimal valToRemoveBd = explicitBd.subtract(new BigDecimal(offset));
+ DecimalQuantity_DualStorageBCD valToRemove = new DecimalQuantity_DualStorageBCD(valToRemoveBd);
+ subtractedSet.remove(valToRemove);
}
if (subtractedSet.size() == 0) {
return KeywordStatus.SUPPRESSED;
diff --git a/android_icu4j/src/main/java/android/icu/text/PluralSamples.java b/android_icu4j/src/main/java/android/icu/text/PluralSamples.java
deleted file mode 100644
index 42ca5b9..0000000
--- a/android_icu4j/src/main/java/android/icu/text/PluralSamples.java
+++ /dev/null
@@ -1,333 +0,0 @@
-/* GENERATED SOURCE. DO NOT MODIFY. */
-// © 2016 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html
-/*
- *******************************************************************************
- * Copyright (C) 2013-2015, International Business Machines Corporation and
- * others. All Rights Reserved.
- *******************************************************************************
- */
-package android.icu.text;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-import java.util.TreeSet;
-
-import android.icu.text.PluralRules.FixedDecimal;
-import android.icu.text.PluralRules.KeywordStatus;
-import android.icu.util.Output;
-
-/**
- * @author markdavis
- * Refactor samples as first step to moving into CLDR
- *
- * @deprecated This API is ICU internal only.
- * @hide Only a subset of ICU is exposed in Android
- * @hide draft / provisional / internal are hidden on Android
- */
-@Deprecated
-public class PluralSamples {
-
- private PluralRules pluralRules;
- private final Map<String, List<Double>> _keySamplesMap;
-
- /**
- * @deprecated This API is ICU internal only.
- * @hide draft / provisional / internal are hidden on Android
- */
- @Deprecated
- public final Map<String, Boolean> _keyLimitedMap;
- private final Map<String, Set<FixedDecimal>> _keyFractionSamplesMap;
- private final Set<FixedDecimal> _fractionSamples;
-
- /**
- * @deprecated This API is ICU internal only.
- * @hide draft / provisional / internal are hidden on Android
- */
- @Deprecated
- public PluralSamples(PluralRules pluralRules) {
- this.pluralRules = pluralRules;
- Set<String> keywords = pluralRules.getKeywords();
- // ensure both _keySamplesMap and _keyLimitedMap are initialized.
- // If this were allowed to vary on a per-call basis, we'd have to recheck and
- // possibly rebuild the samples cache. Doesn't seem worth it.
- // This 'max samples' value only applies to keywords that are unlimited, for
- // other keywords all the matching values are returned. This might be a lot.
- final int MAX_SAMPLES = 3;
-
- Map<String, Boolean> temp = new HashMap<String, Boolean>();
- for (String k : keywords) {
- temp.put(k, pluralRules.isLimited(k));
- }
- _keyLimitedMap = temp;
-
- Map<String, List<Double>> sampleMap = new HashMap<String, List<Double>>();
- int keywordsRemaining = keywords.size();
-
- int limit = 128; // Math.max(5, getRepeatLimit() * MAX_SAMPLES) * 2;
-
- for (int i = 0; keywordsRemaining > 0 && i < limit; ++i) {
- keywordsRemaining = addSimpleSamples(pluralRules, MAX_SAMPLES, sampleMap, keywordsRemaining, i / 2.0);
- }
- // Hack for Celtic
- keywordsRemaining = addSimpleSamples(pluralRules, MAX_SAMPLES, sampleMap, keywordsRemaining, 1000000);
-
-
- // collect explicit samples
- Map<String, Set<FixedDecimal>> sampleFractionMap = new HashMap<String, Set<FixedDecimal>>();
- Set<FixedDecimal> mentioned = new TreeSet<FixedDecimal>();
- // make sure that there is at least one 'other' value
- Map<String, Set<FixedDecimal>> foundKeywords = new HashMap<String, Set<FixedDecimal>>();
- for (FixedDecimal s : mentioned) {
- String keyword = pluralRules.select(s);
- addRelation(foundKeywords, keyword, s);
- }
- main:
- if (foundKeywords.size() != keywords.size()) {
- for (int i = 1; i < 1000; ++i) {
- boolean done = addIfNotPresent(i, mentioned, foundKeywords);
- if (done) break main;
- }
- // if we are not done, try tenths
- for (int i = 10; i < 1000; ++i) {
- boolean done = addIfNotPresent(i/10d, mentioned, foundKeywords);
- if (done) break main;
- }
- System.out.println("Failed to find sample for each keyword: " + foundKeywords + "\n\t" + pluralRules + "\n\t" + mentioned);
- }
- mentioned.add(new FixedDecimal(0)); // always there
- mentioned.add(new FixedDecimal(1)); // always there
- mentioned.add(new FixedDecimal(2)); // always there
- mentioned.add(new FixedDecimal(0.1,1)); // always there
- mentioned.add(new FixedDecimal(1.99,2)); // always there
- mentioned.addAll(fractions(mentioned));
- for (FixedDecimal s : mentioned) {
- String keyword = pluralRules.select(s);
- Set<FixedDecimal> list = sampleFractionMap.get(keyword);
- if (list == null) {
- list = new LinkedHashSet<FixedDecimal>(); // will be sorted because the iteration is
- sampleFractionMap.put(keyword, list);
- }
- list.add(s);
- }
-
- if (keywordsRemaining > 0) {
- for (String k : keywords) {
- if (!sampleMap.containsKey(k)) {
- sampleMap.put(k, Collections.<Double>emptyList());
- }
- if (!sampleFractionMap.containsKey(k)) {
- sampleFractionMap.put(k, Collections.<FixedDecimal>emptySet());
- }
- }
- }
-
- // Make lists immutable so we can return them directly
- for (Entry<String, List<Double>> entry : sampleMap.entrySet()) {
- sampleMap.put(entry.getKey(), Collections.unmodifiableList(entry.getValue()));
- }
- for (Entry<String, Set<FixedDecimal>> entry : sampleFractionMap.entrySet()) {
- sampleFractionMap.put(entry.getKey(), Collections.unmodifiableSet(entry.getValue()));
- }
- _keySamplesMap = sampleMap;
- _keyFractionSamplesMap = sampleFractionMap;
- _fractionSamples = Collections.unmodifiableSet(mentioned);
- }
-
- private int addSimpleSamples(PluralRules pluralRules, final int MAX_SAMPLES, Map<String, List<Double>> sampleMap,
- int keywordsRemaining, double val) {
- String keyword = pluralRules.select(val);
- boolean keyIsLimited = _keyLimitedMap.get(keyword);
-
- List<Double> list = sampleMap.get(keyword);
- if (list == null) {
- list = new ArrayList<Double>(MAX_SAMPLES);
- sampleMap.put(keyword, list);
- } else if (!keyIsLimited && list.size() == MAX_SAMPLES) {
- return keywordsRemaining;
- }
- list.add(Double.valueOf(val));
-
- if (!keyIsLimited && list.size() == MAX_SAMPLES) {
- --keywordsRemaining;
- }
- return keywordsRemaining;
- }
-
- private void addRelation(Map<String, Set<FixedDecimal>> foundKeywords, String keyword, FixedDecimal s) {
- Set<FixedDecimal> set = foundKeywords.get(keyword);
- if (set == null) {
- foundKeywords.put(keyword, set = new HashSet<FixedDecimal>());
- }
- set.add(s);
- }
-
- private boolean addIfNotPresent(double d, Set<FixedDecimal> mentioned, Map<String, Set<FixedDecimal>> foundKeywords) {
- FixedDecimal numberInfo = new FixedDecimal(d);
- String keyword = pluralRules.select(numberInfo);
- if (!foundKeywords.containsKey(keyword) || keyword.equals("other")) {
- addRelation(foundKeywords, keyword, numberInfo);
- mentioned.add(numberInfo);
- if (keyword.equals("other")) {
- if (foundKeywords.get("other").size() > 1) {
- return true;
- }
- }
- }
- return false;
- }
-
- private static final int[] TENS = {1, 10, 100, 1000, 10000, 100000, 1000000};
-
- private static final int LIMIT_FRACTION_SAMPLES = 3;
-
-
- private Set<FixedDecimal> fractions(Set<FixedDecimal> original) {
- Set<FixedDecimal> toAddTo = new HashSet<FixedDecimal>();
-
- Set<Integer> result = new HashSet<Integer>();
- for (FixedDecimal base1 : original) {
- result.add((int)base1.integerValue);
- }
- List<Integer> ints = new ArrayList<Integer>(result);
- Set<String> keywords = new HashSet<String>();
-
- for (int j = 0; j < ints.size(); ++j) {
- Integer base = ints.get(j);
- String keyword = pluralRules.select(base);
- if (keywords.contains(keyword)) {
- continue;
- }
- keywords.add(keyword);
- toAddTo.add(new FixedDecimal(base,1)); // add .0
- toAddTo.add(new FixedDecimal(base,2)); // add .00
- Integer fract = getDifferentCategory(ints, keyword);
- if (fract >= TENS[LIMIT_FRACTION_SAMPLES-1]) { // make sure that we always get the value
- toAddTo.add(new FixedDecimal(base + "." + fract));
- } else {
- for (int visibleFractions = 1; visibleFractions < LIMIT_FRACTION_SAMPLES; ++visibleFractions) {
- for (int i = 1; i <= visibleFractions; ++i) {
- // with visible fractions = 3, and fract = 1, then we should get x.10, 0.01
- // with visible fractions = 3, and fract = 15, then we should get x.15, x.15
- if (fract >= TENS[i]) {
- continue;
- }
- toAddTo.add(new FixedDecimal(base + fract/(double)TENS[i], visibleFractions));
- }
- }
- }
- }
- return toAddTo;
- }
-
- private Integer getDifferentCategory(List<Integer> ints, String keyword) {
- for (int i = ints.size() - 1; i >= 0; --i) {
- Integer other = ints.get(i);
- String keywordOther = pluralRules.select(other);
- if (!keywordOther.equals(keyword)) {
- return other;
- }
- }
- return 37;
- }
-
- /**
- * @deprecated This API is ICU internal only.
- * @hide draft / provisional / internal are hidden on Android
- */
- @Deprecated
- public KeywordStatus getStatus(String keyword, int offset, Set<Double> explicits, Output<Double> uniqueValue) {
- if (uniqueValue != null) {
- uniqueValue.value = null;
- }
-
- if (!pluralRules.getKeywords().contains(keyword)) {
- return KeywordStatus.INVALID;
- }
- Collection<Double> values = pluralRules.getAllKeywordValues(keyword);
- if (values == null) {
- return KeywordStatus.UNBOUNDED;
- }
- int originalSize = values.size();
-
- if (explicits == null) {
- explicits = Collections.emptySet();
- }
-
- // Quick check on whether there are multiple elements
-
- if (originalSize > explicits.size()) {
- if (originalSize == 1) {
- if (uniqueValue != null) {
- uniqueValue.value = values.iterator().next();
- }
- return KeywordStatus.UNIQUE;
- }
- return KeywordStatus.BOUNDED;
- }
-
- // Compute if the quick test is insufficient.
-
- HashSet<Double> subtractedSet = new HashSet<Double>(values);
- for (Double explicit : explicits) {
- subtractedSet.remove(explicit - offset);
- }
- if (subtractedSet.size() == 0) {
- return KeywordStatus.SUPPRESSED;
- }
-
- if (uniqueValue != null && subtractedSet.size() == 1) {
- uniqueValue.value = subtractedSet.iterator().next();
- }
-
- return originalSize == 1 ? KeywordStatus.UNIQUE : KeywordStatus.BOUNDED;
- }
-
- Map<String, List<Double>> getKeySamplesMap() {
- return _keySamplesMap;
- }
-
- Map<String, Set<FixedDecimal>> getKeyFractionSamplesMap() {
- return _keyFractionSamplesMap;
- }
-
- Set<FixedDecimal> getFractionSamples() {
- return _fractionSamples;
- }
-
- /**
- * Returns all the values that trigger this keyword, or null if the number of such
- * values is unlimited.
- *
- * @param keyword the keyword
- * @return the values that trigger this keyword, or null. The returned collection
- * is immutable. It will be empty if the keyword is not defined.
- */
-
- Collection<Double> getAllKeywordValues(String keyword) {
- // HACK for now
- if (!pluralRules.getKeywords().contains(keyword)) {
- return Collections.<Double>emptyList();
- }
- Collection<Double> result = getKeySamplesMap().get(keyword);
-
- // We depend on MAX_SAMPLES here. It's possible for a conjunction
- // of unlimited rules that 'looks' unlimited to return a limited
- // number of values. There's no bounds to this limited number, in
- // general, because you can construct arbitrarily complex rules. Since
- // we always generate 3 samples if a rule is really unlimited, that's
- // where we put the cutoff.
- if (result.size() > 2 && !_keyLimitedMap.get(keyword)) {
- return null;
- }
- return result;
- }
-}
diff --git a/android_icu4j/src/main/java/android/icu/text/RBBINode.java b/android_icu4j/src/main/java/android/icu/text/RBBINode.java
index 9c05bf2..0729c68 100644
--- a/android_icu4j/src/main/java/android/icu/text/RBBINode.java
+++ b/android_icu4j/src/main/java/android/icu/text/RBBINode.java
@@ -91,7 +91,7 @@
// corresponds to columns in the final
// state transition table.
- boolean fLookAheadEnd; // For endMark nodes, set TRUE if
+ boolean fLookAheadEnd; // For endMark nodes, set true if
// marking the end of a look-ahead rule.
boolean fRuleRoot; // True if this node is the root of a rule.
diff --git a/android_icu4j/src/main/java/android/icu/text/RelativeDateTimeFormatter.java b/android_icu4j/src/main/java/android/icu/text/RelativeDateTimeFormatter.java
index 80ed906..76dae00 100644
--- a/android_icu4j/src/main/java/android/icu/text/RelativeDateTimeFormatter.java
+++ b/android_icu4j/src/main/java/android/icu/text/RelativeDateTimeFormatter.java
@@ -1420,28 +1420,9 @@
this.ulocale = ulocale;
}
- private String getDateTimePattern(ICUResourceBundle r) {
- String calType = r.getStringWithFallback("calendar/default");
- if (calType == null || calType.equals("")) {
- calType = "gregorian";
- }
- String resourcePath = "calendar/" + calType + "/DateTimePatterns";
- ICUResourceBundle patternsRb = r.findWithFallback(resourcePath);
- if (patternsRb == null && calType.equals("gregorian")) {
- // Try with gregorian.
- patternsRb = r.findWithFallback("calendar/gregorian/DateTimePatterns");
- }
- if (patternsRb == null || patternsRb.getSize() < 9) {
- // Undefined or too few elements.
- return "{1} {0}";
- } else {
- int elementType = patternsRb.get(8).getType();
- if (elementType == UResourceBundle.ARRAY) {
- return patternsRb.get(8).getString(0);
- } else {
- return patternsRb.getString(8);
- }
- }
+ private String getDateTimePattern() {
+ Calendar cal = Calendar.getInstance(ulocale);
+ return Calendar.getDateAtTimePattern(cal, ulocale, DateFormat.MEDIUM);
}
public RelativeDateTimeFormatterData load() {
@@ -1469,7 +1450,7 @@
return new RelativeDateTimeFormatterData(
sink.qualitativeUnitMap, sink.styleRelUnitPatterns,
- getDateTimePattern(r));
+ getDateTimePattern());
}
}
diff --git a/android_icu4j/src/main/java/android/icu/text/RuleBasedBreakIterator.java b/android_icu4j/src/main/java/android/icu/text/RuleBasedBreakIterator.java
index 2a91f3c..566c1e7 100644
--- a/android_icu4j/src/main/java/android/icu/text/RuleBasedBreakIterator.java
+++ b/android_icu4j/src/main/java/android/icu/text/RuleBasedBreakIterator.java
@@ -1415,42 +1415,89 @@
boolean populateNear(int position) {
assert(position < fBoundaries[fStartBufIdx] || position > fBoundaries[fEndBufIdx]);
- // Find a boundary somewhere in the vicinity of the requested position.
- // Depending on the safe rules and the text data, it could be either before, at, or after
- // the requested position.
-
+ // Add boundaries to the cache near the specified position.
+ // The given position need not be a boundary itself.
+ // The input position must be within the range of the text, and
+ // on a code point boundary.
+ // If the requested position is a break boundary, leave the iteration
+ // position on it.
+ // If the requested position is not a boundary, leave the iteration
+ // position on the preceding boundary and include both the
+ // preceding and following boundaries in the cache.
+ // Additional boundaries, either preceding or following, may be added
+ // to the cache as a side effect.
// If the requested position is not near already cached positions, clear the existing cache,
// find a near-by boundary and begin new cache contents there.
- if ((position < fBoundaries[fStartBufIdx] - 15) || position > (fBoundaries[fEndBufIdx] + 15)) {
- int aBoundary = fText.getBeginIndex();
- int ruleStatusIndex = 0;
- if (position > aBoundary + 20) {
- int backupPos = handleSafePrevious(position);
- if (backupPos > aBoundary) {
- // Advance to the boundary following the backup position.
- // There is a complication: the safe reverse rules identify pairs of code points
- // that are safe. If advancing from the safe point moves forwards by less than
- // two code points, we need to advance one more time to ensure that the boundary
- // is good, including a correct rules status value.
- //
- fPosition = backupPos;
+ // Threshold for a text position to be considered near to existing cache contents.
+ // TODO: See issue ICU-22024 "perf tuning of Cache needed."
+ // This value is subject to change. See the ticket for more details.
+ final int CACHE_NEAR = 15;
+
+ int startOfText = fText.getBeginIndex();
+ int aBoundary = -1;
+ int ruleStatusIndex = 0;
+ boolean retainCache = false;
+ if ((position > fBoundaries[fStartBufIdx] - CACHE_NEAR) && position < (fBoundaries[fEndBufIdx] + CACHE_NEAR)) {
+ // Requested position is near the existing cache. Retain it.
+ retainCache = true;
+ } else if (position <= startOfText + CACHE_NEAR) {
+ // Requested position is near the start of the text. Fill cache from start, skipping
+ // the need to find a safe point.
+ retainCache = false;
+ aBoundary = startOfText;
+ } else {
+ // Requested position is not near the existing cache.
+ // Find a safe point to refill the cache from.
+ int backupPos = handleSafePrevious(position);
+
+ if (fBoundaries[fEndBufIdx] < position && fBoundaries[fEndBufIdx] >= (backupPos - CACHE_NEAR)) {
+ // The requested position is beyond the end of the existing cache, but the
+ // reverse rules produced a position near or before the cached region.
+ // Retain the existing cache, and fill from the end of it.
+ retainCache = true;
+ } else if (backupPos < startOfText + CACHE_NEAR) {
+ // The safe reverse rules moved us to near the start of text.
+ // Take that (index 0) as the backup boundary, avoiding the complication
+ // (in the following block) of moving forward from the safe point to a known boundary.
+ //
+ // Retain the cache if it begins not too far from the requested position.
+ aBoundary = startOfText;
+ retainCache = (fBoundaries[fStartBufIdx] <= (position + CACHE_NEAR));
+ } else {
+ // The safe reverse rules produced a position that is neither near the existing
+ // cache, nor near the start of text.
+ // Advance to the boundary following.
+ // There is a complication: the safe reverse rules identify pairs of code points
+ // that are safe. If advancing from the safe point moves forwards by less than
+ // two code points, we need to advance one more time to ensure that the boundary
+ // is good, including a correct rules status value.
+ //
+ retainCache = false;
+ fPosition = backupPos;
+ aBoundary = handleNext();
+ if (aBoundary == backupPos + 1 ||
+ (aBoundary == backupPos + 2 &&
+ Character.isHighSurrogate(fText.setIndex(backupPos)) &&
+ Character.isLowSurrogate(fText.next()))) {
+ // The initial handleNext() only advanced by a single code point. Go again.
+ // Safe rules identify safe pairs.
aBoundary = handleNext();
- if (aBoundary == backupPos + 1 ||
- (aBoundary == backupPos + 2 &&
- Character.isHighSurrogate(fText.setIndex(backupPos)) &&
- Character.isLowSurrogate(fText.next()))) {
- // The initial handleNext() only advanced by a single code point. Go again.
- // Safe rules identify safe pairs.
- aBoundary = handleNext();
- }
+ }
+ if (aBoundary == BreakIterator.DONE) {
+ aBoundary = fText.getEndIndex();
}
ruleStatusIndex = fRuleStatusIndex;
}
+ }
+
+ if (!retainCache) {
+ assert(aBoundary != -1);
reset(aBoundary, ruleStatusIndex); // Reset cache to hold aBoundary as a single starting point.
}
+
// Fill in boundaries between existing cache content and the new requested position.
if (fBoundaries[fEndBufIdx] < position) {
diff --git a/android_icu4j/src/main/java/android/icu/text/RuleBasedTransliterator.java b/android_icu4j/src/main/java/android/icu/text/RuleBasedTransliterator.java
index edcf60e..152b8be 100644
--- a/android_icu4j/src/main/java/android/icu/text/RuleBasedTransliterator.java
+++ b/android_icu4j/src/main/java/android/icu/text/RuleBasedTransliterator.java
@@ -185,7 +185,7 @@
* Return a representation of this transliterator as source rules.
* These rules will produce an equivalent transliterator if used
* to construct a new transliterator.
- * @param escapeUnprintable if TRUE then convert unprintable
+ * @param escapeUnprintable if true then convert unprintable
* character to their hex escape representations, \\uxxxx or
* \\Uxxxxxxxx. Unprintable characters are those other than
* U+000A, U+0020..U+007E.
diff --git a/android_icu4j/src/main/java/android/icu/text/SimpleDateFormat.java b/android_icu4j/src/main/java/android/icu/text/SimpleDateFormat.java
index bf0f3a7..934ffe0 100644
--- a/android_icu4j/src/main/java/android/icu/text/SimpleDateFormat.java
+++ b/android_icu4j/src/main/java/android/icu/text/SimpleDateFormat.java
@@ -1228,11 +1228,7 @@
if (patternsRb == null || patternsRb.getSize() < 9) {
cachedDefaultPattern = FALLBACKPATTERN;
} else {
- int defaultIndex = 8;
- if (patternsRb.getSize() >= 13) {
- defaultIndex += (SHORT + 1);
- }
- String basePattern = patternsRb.getString(defaultIndex);
+ String basePattern = Calendar.getDateAtTimePattern(cal, cachedDefaultLocale, SHORT);
cachedDefaultPattern = SimpleFormatterImpl.formatRawPattern(
basePattern, 2, 2,
@@ -2520,7 +2516,7 @@
while (idx < plen) {
char pch = patl.charAt(idx);
- if (PatternProps.isWhiteSpace(pch))
+ if (PatternProps.isWhiteSpace(pch) || UCharacter.isUWhiteSpace(pch))
idx++;
else
break;
@@ -2827,16 +2823,18 @@
while (idx < plen && pos < tlen) {
char pch = patternLiteral.charAt(idx);
char ich = text.charAt(pos);
- if (PatternProps.isWhiteSpace(pch)
- && PatternProps.isWhiteSpace(ich)) {
+ if ((PatternProps.isWhiteSpace(pch) || UCharacter.isUWhiteSpace(pch))
+ && (PatternProps.isWhiteSpace(ich) || UCharacter.isUWhiteSpace(ich))) {
// White space characters found in both patten and input.
// Skip contiguous white spaces.
while ((idx + 1) < plen &&
- PatternProps.isWhiteSpace(patternLiteral.charAt(idx + 1))) {
+ (PatternProps.isWhiteSpace(patternLiteral.charAt(idx + 1)) ||
+ UCharacter.isUWhiteSpace(patternLiteral.charAt(idx + 1)))) {
++idx;
}
while ((pos + 1) < tlen &&
- PatternProps.isWhiteSpace(text.charAt(pos + 1))) {
+ (PatternProps.isWhiteSpace(text.charAt(pos + 1)) ||
+ UCharacter.isUWhiteSpace(text.charAt(pos + 1)))) {
++pos;
}
} else if (pch != ich) {
@@ -2893,6 +2891,68 @@
static final UnicodeSet DATE_PATTERN_TYPE = new UnicodeSet("[GyYuUQqMLlwWd]").freeze();
/**
+ * Attempt to match the text at a given position against two arrays of
+ * month symbol strings. Since multiple strings in the array may match (for
+ * example, if the array contains "a", "ab", and "abc", all will match
+ * the input string "abcd") the longest match is returned. As a side
+ * effect, the given field of <code>cal</code> is set to the index
+ * of the best match, if there is one.
+ * @param text the time text being parsed.
+ * @param start where to start parsing.
+ * @param wideData the string array of wide month symbols
+ * @param shortData the string array of short month symbols
+ * @param cal
+ * @return the new start position if matching succeeded; a negative
+ * number indicating matching failure, otherwise. As a side effect,
+ * sets the <code>cal</code> field <code>field</code> to the index
+ * of the best match, if matching succeeded.
+ * @deprecated This API is ICU internal only.
+ * Does not handle monthPattern.
+ * field is always Calendar.MONTH
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ private int matchAlphaMonthStrings(String text, int start, String[] wideData, String[] shortData, Calendar cal)
+ {
+ int i;
+ int bestMatchLength = 0, bestMatch = -1;
+
+ for (i = 0; i<wideData.length; ++i)
+ {
+ int length = wideData[i].length();
+ int matchLength = 0;
+ // Always compare if we have no match yet; otherwise only compare
+ // against potentially better matches (longer strings).
+ if (length > bestMatchLength &&
+ (matchLength = regionMatchesWithOptionalDot(text, start, wideData[i], length)) >= 0)
+ {
+ bestMatch = i;
+ bestMatchLength = matchLength;
+ }
+ }
+ for (i = 0; i<shortData.length; ++i)
+ {
+ int length = shortData[i].length();
+ int matchLength = 0;
+ // Always compare if we have no match yet; otherwise only compare
+ // against potentially better matches (longer strings).
+ if (length > bestMatchLength &&
+ (matchLength = regionMatchesWithOptionalDot(text, start, shortData[i], length)) >= 0)
+ {
+ bestMatch = i;
+ bestMatchLength = matchLength;
+ }
+ }
+ if (bestMatch >= 0)
+ {
+ cal.set(Calendar.MONTH, bestMatch);
+ return start + bestMatchLength;
+ }
+ return ~start;
+ }
+
+
+ /**
* Attempt to match the text at a given position against an array of
* strings. Since multiple strings in the array may match (for
* example, if the array contains "a", "ab", and "abc", all will match
@@ -3174,7 +3234,9 @@
return ~start;
}
int c = UTF16.charAt(text, start);
- if (!UCharacter.isUWhiteSpace(c) || !PatternProps.isWhiteSpace(c)) {
+ // Changed the following from || to &&, as in ICU4C; needed to skip NBSP, NNBSP.
+ // Only UWhiteSpace includes \u00A0\u202F\u2009\u3000...; only PatternProps.isWhiteSpace includes \u200E\u200F
+ if (!UCharacter.isUWhiteSpace(c) && !PatternProps.isWhiteSpace(c)) {
break;
}
start += UTF16.getCharCount(c);
@@ -3337,14 +3399,22 @@
boolean haveMonthPat = (formatData.leapMonthPatterns != null && formatData.leapMonthPatterns.length >= DateFormatSymbols.DT_MONTH_PATTERN_COUNT);
// Try count == 4 first:, unless we're strict
int newStart = 0;
+ if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH)&& count>=3 && count <=4 && !haveMonthPat) {
+ newStart = (patternCharIndex == 2)?
+ matchAlphaMonthStrings(text, start, formatData.months, formatData.shortMonths, cal):
+ matchAlphaMonthStrings(text, start, formatData.standaloneMonths, formatData.standaloneShortMonths, cal);
+ if (newStart > 0) {
+ return newStart;
+ }
+ }
if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 4) {
newStart = (patternCharIndex == 2)?
matchString(text, start, Calendar.MONTH, formatData.months,
(haveMonthPat)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_FORMAT_WIDE]: null, cal):
matchString(text, start, Calendar.MONTH, formatData.standaloneMonths,
(haveMonthPat)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_STANDALONE_WIDE]: null, cal);
- if (newStart > 0) {
- return newStart;
+ if (newStart > 0) {
+ return newStart;
}
}
// count == 4 failed, now try count == 3
@@ -3769,6 +3839,9 @@
} else {
number = parseInt(text, pos, allowNegative,currentNumberFormat);
}
+ if (!isLenient() && pos.getIndex() < start + count) {
+ return -start;
+ }
if (number != null) {
if (patternCharIndex != DateFormat.RELATED_YEAR) {
cal.set(field, number.intValue());
diff --git a/android_icu4j/src/main/java/android/icu/text/SimplePersonName.java b/android_icu4j/src/main/java/android/icu/text/SimplePersonName.java
new file mode 100644
index 0000000..5c2cfd9
--- /dev/null
+++ b/android_icu4j/src/main/java/android/icu/text/SimplePersonName.java
@@ -0,0 +1,228 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+// © 2022 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html
+package android.icu.text;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.StringTokenizer;
+import java.util.TreeSet;
+
+/**
+ * A concrete implementation of PersonNameFormatter.PersonName that simply stores the field
+ * values in a Map.
+ *
+ * A caller can store both raw field values (such as "given") and modified field values (such as "given-informal")
+ * in a SimplePersonName. But beyond storing and returning modified field values provided to it by the caller,
+ * SimplePersonName relies on the PersonNameFormatter's default handling of field modifiers.
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+@Deprecated
+public class SimplePersonName implements PersonName {
+ /**
+ * A utility class for constructing a SimplePersonName. Use SimplePersonName.builder()
+ * to get a new Builder instance.
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public static class Builder {
+ /**
+ * Set the locale for the new name object.
+ * @param locale The locale for the new name object. Can be null, which indicates the
+ * name's locale is unknown.
+ * @return This builder.
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Builder setLocale(Locale locale) {
+ this.locale = locale;
+ return this;
+ }
+
+ /**
+ * Sets the value for one field (with optional modifiers) in the new name object.
+ * @param field A NameField object specifying the field to set.
+ * @param modifiers A collection of FieldModifier objects for any modifiers that apply
+ * to this field value. May be null, which is the same as the empty set.
+ * @param value The value for this field.
+ * @return This builder.
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Builder addField(NameField field,
+ Collection<FieldModifier> modifiers,
+ String value) {
+ // generate the modifiers' internal names, and sort them alphabetically
+ Set<String> modifierNames = new TreeSet<>();
+ if (modifiers != null) {
+ for (FieldModifier modifier : modifiers) {
+ modifierNames.add(modifier.toString());
+ }
+ }
+
+ // construct the modified field name, which is the field name, with all the modifier names attached in
+ // alphabetical order, delimited by hyphens
+ StringBuilder fieldName = new StringBuilder();
+ fieldName.append(field.toString());
+ for (String modifierName : modifierNames) {
+ fieldName.append("-");
+ fieldName.append(modifierName);
+ }
+
+ fieldValues.put(fieldName.toString(), value);
+ return this;
+ }
+
+ /**
+ * Returns a SimplePersonName with the field values and name locale that were passed to this builder.
+ * @return A SimplePersonName with the field values and name locale that were passed to this builder.
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public SimplePersonName build() {
+ // special-case code for the "surname" field -- if it isn't specified, but "surname-prefix" and
+ // "surname-core" both are, let "surname" be the other two fields joined with a space
+ if (fieldValues.get("surname") == null) {
+ String surnamePrefix = fieldValues.get("surname-prefix");
+ String surnameCore = fieldValues.get("surname-core");
+ if (surnamePrefix != null && surnameCore != null) {
+ fieldValues.put("surname", surnamePrefix + " " + surnameCore);
+ }
+ }
+
+ return new SimplePersonName(locale, fieldValues);
+ }
+
+ private Builder() {
+ locale = null;
+ fieldValues = new HashMap<>();
+ }
+
+ private Locale locale;
+ private Map<String, String> fieldValues;
+ }
+
+ /**
+ * Returns a Builder object that can be used to construct a new SimplePersonName object.
+ * @return A Builder object that can be used to construct a new SimplePersonName object.
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Internal constructor used by the Builder object.
+ */
+ private SimplePersonName(Locale nameLocale, Map<String, String> fieldValues) {
+ this.nameLocale = nameLocale;
+ this.fieldValues = new HashMap<>(fieldValues);
+ }
+
+ /**
+ * Returns the locale of the name-- that is, the language or country of origin for the person being named.
+ * @return The name's locale, or null if it's unknown.
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Override
+ @Deprecated
+ public Locale getNameLocale() {
+ return nameLocale;
+ }
+
+ /**
+ * Returns one field of the name, possibly in a modified form. This class can store modified versions of fields,
+ * provided at construction time, and this function will return them. Otherwise, it ignores modifiers and
+ * relies on PersonNameFormat's default modifier handling.
+ * @param nameField The identifier of the requested field.
+ * @param modifiers An **IN/OUT** parameter that specifies modifiers to apply to the basic field value.
+ * On return, this list will contain any modifiers that this object didn't handle. This class
+ * will always return this set unmodified, unless a modified version of the requested field
+ * was provided at construction time.
+ * @return The value of the requested field, optionally modified by some or all of the requested modifiers, or
+ * null if the requested field isn't present in the name.
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Override
+ @Deprecated
+ public String getFieldValue(NameField nameField, Set<FieldModifier> modifiers) {
+ // first look for the fully modified name in the internal table
+ String fieldName = nameField.toString();
+ String result = fieldValues.get(makeModifiedFieldName(nameField, modifiers));
+ if (result != null) {
+ modifiers.clear();
+ return result;
+ }
+
+ // if we don't find it, check the fully unmodified name. If it's not there, nothing else will be
+ result = fieldValues.get(fieldName);
+ if (result == null) {
+ return null;
+ } else if (modifiers.size() == 1) {
+ // and if it IS there and there's only one modifier, we're done
+ return result;
+ }
+
+ // but if there are two or more modifiers, then we have to go through the whole list of fields and look for the best match
+ String winningKey = fieldName;
+ int winningScore = 0;
+ for (String key : fieldValues.keySet()) {
+ if (key.startsWith(fieldName)) {
+ Set<FieldModifier> keyModifiers = makeModifiersFromName(key);
+ if (modifiers.containsAll(keyModifiers)) {
+ if (keyModifiers.size() > winningScore || (keyModifiers.size() == winningScore && key.compareTo(winningKey) < 0)) {
+ winningKey = key;
+ winningScore = keyModifiers.size();
+ }
+ }
+ }
+ }
+ result = fieldValues.get(winningKey);
+ modifiers.removeAll(makeModifiersFromName(winningKey));
+ return result;
+ }
+
+ private static String makeModifiedFieldName(NameField fieldName,
+ Collection<FieldModifier> modifiers) {
+ StringBuilder result = new StringBuilder();
+ result.append(fieldName);
+
+ TreeSet<String> sortedModifierNames = new TreeSet<>();
+ for (FieldModifier modifier : modifiers) {
+ sortedModifierNames.add(modifier.toString());
+ }
+ for (String modifierName : sortedModifierNames) {
+ result.append("-");
+ result.append(modifierName);
+ }
+ return result.toString();
+ }
+
+ private static Set<FieldModifier> makeModifiersFromName(String modifiedName) {
+ StringTokenizer tok = new StringTokenizer(modifiedName, "-");
+ Set<FieldModifier> result = new HashSet<>();
+ String fieldName = tok.nextToken(); // throw away the field name
+ while (tok.hasMoreTokens()) {
+ result.add(PersonName.FieldModifier.forString(tok.nextToken()));
+ }
+ return result;
+ }
+
+ private final Locale nameLocale;
+ private final Map<String, String> fieldValues;
+}
\ No newline at end of file
diff --git a/android_icu4j/src/main/java/android/icu/text/SpoofChecker.java b/android_icu4j/src/main/java/android/icu/text/SpoofChecker.java
index b0859c5..701a94f 100644
--- a/android_icu4j/src/main/java/android/icu/text/SpoofChecker.java
+++ b/android_icu4j/src/main/java/android/icu/text/SpoofChecker.java
@@ -260,8 +260,8 @@
* Security Profile constant from UTS 39 for use in {@link SpoofChecker.Builder#setAllowedChars}.
*/
public static final UnicodeSet INCLUSION = new UnicodeSet(
- "['\\-.\\:\\u00B7\\u0375\\u058A\\u05F3\\u05F4\\u06FD\\u06FE\\u0F0B\\u200C"
- + "\\u200D\\u2010\\u2019\\u2027\\u30A0\\u30FB]"
+ "['\\-.\\:\\u00B7\\u0375\\u058A\\u05F3\\u05F4\\u06FD\\u06FE\\u0F0B\\u2010"
+ + "\\u2019\\u2027\\u30A0\\u30FB]"
).freeze();
// Note: data from IdentifierStatus.txt & IdentifierType.txt
// There is tooling to generate this constant in the unicodetools project:
@@ -303,14 +303,14 @@
+ "\\u0C56\\u0C5D\\u0C60\\u0C61\\u0C66-\\u0C6F\\u0C80\\u0C82\\u0C83\\u0C85-"
+ "\\u0C8C\\u0C8E-\\u0C90\\u0C92-\\u0CA8\\u0CAA-\\u0CB3\\u0CB5-\\u0CB9\\u0CBC-"
+ "\\u0CC4\\u0CC6-\\u0CC8\\u0CCA-\\u0CCD\\u0CD5\\u0CD6\\u0CDD\\u0CE0-\\u0CE3"
- + "\\u0CE6-\\u0CEF\\u0CF1\\u0CF2\\u0D00\\u0D02\\u0D03\\u0D05-\\u0D0C\\u0D0E-"
+ + "\\u0CE6-\\u0CEF\\u0CF1-\\u0CF3\\u0D00\\u0D02\\u0D03\\u0D05-\\u0D0C\\u0D0E-"
+ "\\u0D10\\u0D12-\\u0D3A\\u0D3D-\\u0D43\\u0D46-\\u0D48\\u0D4A-\\u0D4E\\u0D54-"
+ "\\u0D57\\u0D60\\u0D61\\u0D66-\\u0D6F\\u0D7A-\\u0D7F\\u0D82\\u0D83\\u0D85-"
+ "\\u0D8E\\u0D91-\\u0D96\\u0D9A-\\u0DA5\\u0DA7-\\u0DB1\\u0DB3-\\u0DBB\\u0DBD"
+ "\\u0DC0-\\u0DC6\\u0DCA\\u0DCF-\\u0DD4\\u0DD6\\u0DD8-\\u0DDE\\u0DF2\\u0E01-"
+ "\\u0E32\\u0E34-\\u0E3A\\u0E40-\\u0E4E\\u0E50-\\u0E59\\u0E81\\u0E82\\u0E84"
+ "\\u0E86-\\u0E8A\\u0E8C-\\u0EA3\\u0EA5\\u0EA7-\\u0EB2\\u0EB4-\\u0EBD\\u0EC0-"
- + "\\u0EC4\\u0EC6\\u0EC8-\\u0ECD\\u0ED0-\\u0ED9\\u0EDE\\u0EDF\\u0F00\\u0F20-"
+ + "\\u0EC4\\u0EC6\\u0EC8-\\u0ECE\\u0ED0-\\u0ED9\\u0EDE\\u0EDF\\u0F00\\u0F20-"
+ "\\u0F29\\u0F35\\u0F37\\u0F3E-\\u0F42\\u0F44-\\u0F47\\u0F49-\\u0F4C\\u0F4E-"
+ "\\u0F51\\u0F53-\\u0F56\\u0F58-\\u0F5B\\u0F5D-\\u0F68\\u0F6A-\\u0F6C\\u0F71"
+ "\\u0F72\\u0F74\\u0F7A-\\u0F80\\u0F82-\\u0F84\\u0F86-\\u0F92\\u0F94-\\u0F97"
@@ -331,17 +331,18 @@
+ "\\u2DBE\\u2DC0-\\u2DC6\\u2DC8-\\u2DCE\\u2DD0-\\u2DD6\\u2DD8-\\u2DDE\\u3005-"
+ "\\u3007\\u3041-\\u3096\\u3099\\u309A\\u309D\\u309E\\u30A1-\\u30FA\\u30FC-"
+ "\\u30FE\\u3105-\\u312D\\u312F\\u31A0-\\u31BF\\u3400-\\u4DBF\\u4E00-\\u9FFF"
- + "\\uA67F\\uA717-\\uA71F\\uA788\\uA78D\\uA792\\uA793\\uA7AA\\uA7AE\\uA7B8"
- + "\\uA7B9\\uA7C0-\\uA7CA\\uA7D0\\uA7D1\\uA7D3\\uA7D5-\\uA7D9\\uA9E7-\\uA9FE"
- + "\\uAA60-\\uAA76\\uAA7A-\\uAA7F\\uAB01-\\uAB06\\uAB09-\\uAB0E\\uAB11-\\uAB16"
- + "\\uAB20-\\uAB26\\uAB28-\\uAB2E\\uAB66\\uAB67\\uAC00-\\uD7A3\\uFA0E\\uFA0F"
- + "\\uFA11\\uFA13\\uFA14\\uFA1F\\uFA21\\uFA23\\uFA24\\uFA27-\\uFA29\\U00011301"
- + "\\U00011303\\U0001133B\\U0001133C\\U00016FF0\\U00016FF1\\U0001B11F-"
- + "\\U0001B122\\U0001B150-\\U0001B152\\U0001B164-\\U0001B167\\U0001DF00-"
- + "\\U0001DF1E\\U0001E7E0-\\U0001E7E6\\U0001E7E8-\\U0001E7EB\\U0001E7ED"
- + "\\U0001E7EE\\U0001E7F0-\\U0001E7FE\\U00020000-\\U0002A6DF\\U0002A700-"
- + "\\U0002B738\\U0002B740-\\U0002B81D\\U0002B820-\\U0002CEA1\\U0002CEB0-"
- + "\\U0002EBE0\\U00030000-\\U0003134A]"
+ + "\\uA67F\\uA717-\\uA71F\\uA788\\uA78D\\uA792\\uA793\\uA7AA\\uA7C0-\\uA7CA"
+ + "\\uA7D0\\uA7D1\\uA7D3\\uA7D5-\\uA7D9\\uA9E7-\\uA9FE\\uAA60-\\uAA76\\uAA7A-"
+ + "\\uAA7F\\uAB01-\\uAB06\\uAB09-\\uAB0E\\uAB11-\\uAB16\\uAB20-\\uAB26\\uAB28-"
+ + "\\uAB2E\\uAB66\\uAB67\\uAC00-\\uD7A3\\uFA0E\\uFA0F\\uFA11\\uFA13\\uFA14"
+ + "\\uFA1F\\uFA21\\uFA23\\uFA24\\uFA27-\\uFA29\\U00011301\\U00011303"
+ + "\\U0001133B\\U0001133C\\U00016FF0\\U00016FF1\\U0001B11F-\\U0001B122"
+ + "\\U0001B132\\U0001B150-\\U0001B152\\U0001B155\\U0001B164-\\U0001B167"
+ + "\\U0001DF00-\\U0001DF1E\\U0001DF25-\\U0001DF2A\\U0001E08F\\U0001E7E0-"
+ + "\\U0001E7E6\\U0001E7E8-\\U0001E7EB\\U0001E7ED\\U0001E7EE\\U0001E7F0-"
+ + "\\U0001E7FE\\U00020000-\\U0002A6DF\\U0002A700-\\U0002B739\\U0002B740-"
+ + "\\U0002B81D\\U0002B820-\\U0002CEA1\\U0002CEB0-\\U0002EBE0\\U00030000-"
+ + "\\U0003134A\\U00031350-\\U000323AF]"
).freeze();
// Note: data from IdentifierStatus.txt & IdentifierType.txt
// There is tooling to generate this constant in the unicodetools project:
diff --git a/android_icu4j/src/main/java/android/icu/text/StringSearch.java b/android_icu4j/src/main/java/android/icu/text/StringSearch.java
index cd3f8d8..68522d3 100644
--- a/android_icu4j/src/main/java/android/icu/text/StringSearch.java
+++ b/android_icu4j/src/main/java/android/icu/text/StringSearch.java
@@ -850,7 +850,7 @@
* Checks for identical match
* @param start offset of possible match
* @param end offset of possible match
- * @return TRUE if identical match is found
+ * @return true if identical match is found
*/
private boolean checkIdentical(int start, int end) {
if (strength_ != Collator.IDENTICAL) {
@@ -898,7 +898,7 @@
}
/*
- * Returns TRUE if index is on a break boundary. If the UStringSearch
+ * Returns true if index is on a break boundary. If the UStringSearch
* has an external break iterator, test using that, otherwise test
* using the internal character break iterator.
*/
diff --git a/android_icu4j/src/main/java/android/icu/text/TransliterationRule.java b/android_icu4j/src/main/java/android/icu/text/TransliterationRule.java
index da8b112..6a8d112 100644
--- a/android_icu4j/src/main/java/android/icu/text/TransliterationRule.java
+++ b/android_icu4j/src/main/java/android/icu/text/TransliterationRule.java
@@ -361,11 +361,11 @@
*
* @param text the text
* @param pos the position indices
- * @param incremental if TRUE, test for partial matches that may
+ * @param incremental if true, test for partial matches that may
* be completed by additional text inserted at pos.limit.
* @return one of <code>U_MISMATCH</code>,
* <code>U_PARTIAL_MATCH</code>, or <code>U_MATCH</code>. If
- * incremental is FALSE then U_PARTIAL_MATCH will not be returned.
+ * incremental is false then U_PARTIAL_MATCH will not be returned.
*/
public int matchAndReplace(Replaceable text,
Transliterator.Position pos,
diff --git a/android_icu4j/src/main/java/android/icu/text/TransliterationRuleSet.java b/android_icu4j/src/main/java/android/icu/text/TransliterationRuleSet.java
index c2a0309..1001791 100644
--- a/android_icu4j/src/main/java/android/icu/text/TransliterationRuleSet.java
+++ b/android_icu4j/src/main/java/android/icu/text/TransliterationRuleSet.java
@@ -177,14 +177,14 @@
/**
* Transliterate the given text with the given UTransPosition
- * indices. Return TRUE if the transliteration should continue
- * or FALSE if it should halt (because of a U_PARTIAL_MATCH match).
- * Note that FALSE is only ever returned if isIncremental is TRUE.
+ * indices. Return true if the transliteration should continue
+ * or false if it should halt (because of a U_PARTIAL_MATCH match).
+ * Note that false is only ever returned if isIncremental is true.
* @param text the text to be transliterated
* @param pos the position indices, which will be updated
- * @param incremental if TRUE, assume new text may be inserted
- * at index.limit, and return FALSE if there is a partial match.
- * @return TRUE unless a U_PARTIAL_MATCH has been obtained,
+ * @param incremental if true, assume new text may be inserted
+ * at index.limit, and return false if there is a partial match.
+ * @return true unless a U_PARTIAL_MATCH has been obtained,
* indicating that transliteration should stop until more text
* arrives.
*/
diff --git a/android_icu4j/src/main/java/android/icu/text/Transliterator.java b/android_icu4j/src/main/java/android/icu/text/Transliterator.java
index d9dd4d5..77b9fa0 100644
--- a/android_icu4j/src/main/java/android/icu/text/Transliterator.java
+++ b/android_icu4j/src/main/java/android/icu/text/Transliterator.java
@@ -975,14 +975,14 @@
* Top-level transliteration method, handling filtering, incremental and
* non-incremental transliteration, and rollback. All transliteration
* public API methods eventually call this method with a rollback argument
- * of TRUE. Other entities may call this method but rollback should be
- * FALSE.
+ * of true. Other entities may call this method but rollback should be
+ * false.
*
* <p>If this transliterator has a filter, break up the input text into runs
* of unfiltered characters. Pass each run to
* <subclass>.handleTransliterate().
*
- * <p>In incremental mode, if rollback is TRUE, perform a special
+ * <p>In incremental mode, if rollback is true, perform a special
* incremental procedure in which several passes are made over the input
* text, adding one character at a time, and committing successful
* transliterations as they occur. Unsuccessful transliterations are rolled
@@ -990,12 +990,12 @@
*
* @param text the text to be transliterated
* @param index the position indices
- * @param incremental if TRUE, then assume more characters may be inserted
+ * @param incremental if true, then assume more characters may be inserted
* at index.limit, and postpone processing to accommodate future incoming
* characters
- * @param rollback if TRUE and if incremental is TRUE, then perform special
+ * @param rollback if true and if incremental is true, then perform special
* incremental processing, as described above, and undo partial
- * transliterations where necessary. If incremental is FALSE then this
+ * transliterations where necessary. If incremental is false then this
* parameter is ignored.
*/
private void filteredTransliterate(Replaceable text,
@@ -1080,7 +1080,7 @@
// Is this run incremental? If there is additional
// filtered text (if limit < globalLimit) then we pass in
- // an incremental value of FALSE to force the subclass to
+ // an incremental value of false to force the subclass to
// complete the transliteration for this run.
boolean isIncrementalRun =
(index.limit < globalLimit ? false : incremental);
@@ -1307,7 +1307,7 @@
* another transliterator.
* @param text the text to be transliterated
* @param index the position indices
- * @param incremental if TRUE, then assume more characters may be inserted
+ * @param incremental if true, then assume more characters may be inserted
* at index.limit, and postpone processing to accommodate future incoming
* characters
*/
diff --git a/android_icu4j/src/main/java/android/icu/text/TransliteratorRegistry.java b/android_icu4j/src/main/java/android/icu/text/TransliteratorRegistry.java
index 6039bc0..d7b2dbe 100644
--- a/android_icu4j/src/main/java/android/icu/text/TransliteratorRegistry.java
+++ b/android_icu4j/src/main/java/android/icu/text/TransliteratorRegistry.java
@@ -17,11 +17,13 @@
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
+import java.util.Set;
import android.icu.impl.ICUData;
import android.icu.impl.ICUResourceBundle;
@@ -70,7 +72,7 @@
/**
* Vector of public full IDs (CaseInsensitiveString objects).
*/
- private List<CaseInsensitiveString> availableIDs;
+ private final Set<CaseInsensitiveString> availableIDs;
//----------------------------------------------------------------------
// class Spec
@@ -97,8 +99,8 @@
private String spec; // current spec
private String n