[automerger skipped] DO NOT MERGE Track tzdb 2023d update. [Q CTS] am: c4f7385936 -s ours am: 0883de6a83 -s ours am: 4e4b803b0d -s ours am: 1231038eda -s ours
am skip reason: contains skip directive
Original change: https://android-review.googlesource.com/c/platform/external/icu/+/2967636
Change-Id: Ib7da6a6733bc99332dba8ec7cdb713f8f56462b4
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/Android.bp b/Android.bp
index a45866e..d7fb36b 100644
--- a/Android.bp
+++ b/Android.bp
@@ -67,7 +67,6 @@
"-Wno-unused-const-variable",
"-Wno-unneeded-internal-declaration",
"-Wno-deprecated-declarations",
- "-Wno-ignored-attributes",
],
// Upstream requires C++11 as the minimum version.
cppflags: [
diff --git a/README.version b/README.version
index 39c819c..70eadc7 100644
--- a/README.version
+++ b/README.version
@@ -1,3 +1,3 @@
URL: https://github.com/unicode-org/icu
-Version: 68.2
+Version: 70.1
BugComponent: 23970
diff --git a/android_icu4j/Android.bp b/android_icu4j/Android.bp
index 5018ef3..dabd918 100644
--- a/android_icu4j/Android.bp
+++ b/android_icu4j/Android.bp
@@ -129,7 +129,9 @@
permitted_packages: [
"android.icu",
"com.android.icu",
+ "com.android.i18n.system",
"com.android.i18n.timezone",
+ "com.android.i18n.util",
],
installable: true,
hostdex: false,
@@ -168,9 +170,9 @@
java_sdk_library {
name: "i18n.module.public.api",
visibility: [
+ "//build/soong/java/core-libraries",
"//frameworks/base",
"//frameworks/base/api",
- "//libcore",
"//packages/modules/RuntimeI18n/apex",
// Visibility for prebuilt i18n-module-sdk from the prebuilt of
// this module.
@@ -189,8 +191,23 @@
"-Xep:MissingOverride:OFF",
],
},
+
+ public: {
+ enabled: true,
+ },
+ system: {
+ enabled: true,
+ },
+ module_lib: {
+ enabled: true,
+ },
+
api_dir: "api/public",
api_only: true,
+
+ // Emit nullability annotations from the source to the stub files.
+ annotations_enabled: true,
+
sdk_version: "none",
system_modules: "art-module-public-api-stubs-system-modules",
@@ -233,7 +250,6 @@
droiddoc_options: [
"--hide-annotation libcore.api.Hide ",
"--show-single-annotation libcore.api.IntraCoreApi ",
- "--skip-annotation-instance-methods=false ",
],
// Don't copy any output files to the dist.
@@ -329,7 +345,7 @@
":android_icu4j_repackaged_src_files",
],
visibility: [
- "//libcore:__subpackages__",
+ "//build/soong/java/core-libraries",
"//packages/modules/RuntimeI18n/apex",
// Visibility for prebuilt i18n-module-sdk from the prebuilt of
// this module.
@@ -346,7 +362,6 @@
droiddoc_options: [
"--hide-annotation libcore.api.Hide ",
"--show-single-annotation libcore.api.CorePlatformApi ",
- "--skip-annotation-instance-methods=false ",
],
// Don't copy any output files to the dist.
@@ -360,7 +375,7 @@
":android_icu4j_repackaged_src_files",
],
visibility: [
- "//libcore:__subpackages__",
+ "//build/soong/java/core-libraries",
"//packages/modules/RuntimeI18n/apex",
// Visibility for prebuilt i18n-module-sdk from the prebuilt of
// this module.
@@ -377,7 +392,6 @@
droiddoc_options: [
"--hide-annotation libcore.api.Hide ",
"--show-single-annotation libcore.api.CorePlatformApi\\(status=libcore.api.CorePlatformApi.Status.STABLE\\)",
- "--skip-annotation-instance-methods=false ",
],
// Don't copy any output files to the dist.
@@ -417,4 +431,8 @@
patch_module: "java.base",
sdk_version: "none",
system_modules: "art-module-intra-core-api-stubs-system-modules",
+
+ errorprone: {
+ javacflags: ["-Xep:EqualsNull:WARN"],
+ },
}
diff --git a/android_icu4j/api/legacy_platform/current.txt b/android_icu4j/api/legacy_platform/current.txt
index 692b2b1..337a712 100644
--- a/android_icu4j/api/legacy_platform/current.txt
+++ b/android_icu4j/api/legacy_platform/current.txt
@@ -1,12 +1,4 @@
// Signature format: 2.0
-package android.icu.impl {
-
- public class TimeZoneAdapter extends java.util.TimeZone {
- method public static java.util.TimeZone wrap(android.icu.util.TimeZone);
- }
-
-}
-
package android.icu.text {
public class DateFormatSymbols implements java.lang.Cloneable java.io.Serializable {
@@ -26,32 +18,6 @@
}
-package android.icu.util {
-
- public abstract class BasicTimeZone extends android.icu.util.TimeZone {
- method public abstract android.icu.util.TimeZoneTransition getNextTransition(long, boolean);
- }
-
- public class Region implements java.lang.Comparable<android.icu.util.Region> {
- method public static java.util.Set<android.icu.util.Region> getAvailable(android.icu.util.Region.RegionType);
- }
-
- public enum Region.RegionType {
- enum_constant public static final android.icu.util.Region.RegionType TERRITORY;
- }
-
- public abstract class TimeZoneRule implements java.io.Serializable {
- method public int getDSTSavings();
- }
-
- public class TimeZoneTransition {
- method public android.icu.util.TimeZoneRule getFrom();
- method public long getTime();
- method public android.icu.util.TimeZoneRule getTo();
- }
-
-}
-
package com.android.i18n.timezone {
public final class CountryTimeZones {
diff --git a/android_icu4j/api/public/current.txt b/android_icu4j/api/public/current.txt
index 5b3fc5c..93473d7 100644
--- a/android_icu4j/api/public/current.txt
+++ b/android_icu4j/api/public/current.txt
@@ -23,6 +23,7 @@
method public static int getCharFromExtendedName(String);
method public static int getCharFromName(String);
method public static int getCharFromNameAlias(String);
+ method public static int getCodePoint(int, int);
method public static int getCodePoint(char, char);
method public static int getCodePoint(char);
method public static int getCombiningClass(int);
@@ -53,6 +54,7 @@
method public static boolean isBaseForm(int);
method public static boolean isDefined(int);
method public static boolean isDigit(int);
+ method public static boolean isHighSurrogate(int);
method public static boolean isHighSurrogate(char);
method public static boolean isISOControl(int);
method public static boolean isIdentifierIgnorable(int);
@@ -62,6 +64,7 @@
method public static boolean isLegal(String);
method public static boolean isLetter(int);
method public static boolean isLetterOrDigit(int);
+ method public static boolean isLowSurrogate(int);
method public static boolean isLowSurrogate(char);
method public static boolean isLowerCase(int);
method public static boolean isMirrored(int);
@@ -69,6 +72,7 @@
method public static boolean isSpaceChar(int);
method public static boolean isSupplementary(int);
method public static boolean isSupplementaryCodePoint(int);
+ method public static boolean isSurrogatePair(int, int);
method public static boolean isSurrogatePair(char, char);
method public static boolean isTitleCase(int);
method public static boolean isUAlphabetic(int);
@@ -84,6 +88,7 @@
method public static int offsetByCodePoints(char[], int, int, int, int);
method public static int toChars(int, char[], int);
method public static char[] toChars(int);
+ method public static int toCodePoint(int, int);
method public static int toCodePoint(char, char);
method public static int toLowerCase(int);
method public static String toLowerCase(String);
@@ -342,6 +347,8 @@
field public static final int TEH_MARBUTA = 42; // 0x2a
field public static final int TEH_MARBUTA_GOAL = 14; // 0xe
field public static final int TETH = 43; // 0x2b
+ field public static final int THIN_YEH = 102; // 0x66
+ field public static final int VERTICAL_TAIL = 103; // 0x67
field public static final int WAW = 44; // 0x2c
field public static final int YEH = 45; // 0x2d
field public static final int YEH_BARREE = 46; // 0x2e
@@ -459,6 +466,8 @@
field public static final android.icu.lang.UCharacter.UnicodeBlock ARABIC;
field public static final android.icu.lang.UCharacter.UnicodeBlock ARABIC_EXTENDED_A;
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 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
@@ -582,6 +591,8 @@
field public static final int CURRENCY_SYMBOLS_ID = 42; // 0x2a
field public static final android.icu.lang.UCharacter.UnicodeBlock CYPRIOT_SYLLABARY;
field public static final int CYPRIOT_SYLLABARY_ID = 123; // 0x7b
+ field public static final android.icu.lang.UCharacter.UnicodeBlock CYPRO_MINOAN;
+ field public static final int CYPRO_MINOAN_ID = 310; // 0x136
field public static final android.icu.lang.UCharacter.UnicodeBlock CYRILLIC;
field public static final android.icu.lang.UCharacter.UnicodeBlock CYRILLIC_EXTENDED_A;
field public static final int CYRILLIC_EXTENDED_A_ID = 158; // 0x9e
@@ -634,6 +645,8 @@
field public static final android.icu.lang.UCharacter.UnicodeBlock ETHIOPIC_EXTENDED;
field public static final android.icu.lang.UCharacter.UnicodeBlock ETHIOPIC_EXTENDED_A;
field public static final int ETHIOPIC_EXTENDED_A_ID = 200; // 0xc8
+ field public static final android.icu.lang.UCharacter.UnicodeBlock ETHIOPIC_EXTENDED_B;
+ field public static final int ETHIOPIC_EXTENDED_B_ID = 311; // 0x137
field public static final int ETHIOPIC_EXTENDED_ID = 133; // 0x85
field public static final int ETHIOPIC_ID = 31; // 0x1f
field public static final android.icu.lang.UCharacter.UnicodeBlock ETHIOPIC_SUPPLEMENT;
@@ -716,6 +729,8 @@
field public static final int KAITHI_ID = 193; // 0xc1
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;
+ field public static final int KANA_EXTENDED_B_ID = 312; // 0x138
field public static final android.icu.lang.UCharacter.UnicodeBlock KANA_SUPPLEMENT;
field public static final int KANA_SUPPLEMENT_ID = 203; // 0xcb
field public static final android.icu.lang.UCharacter.UnicodeBlock KANBUN;
@@ -758,6 +773,10 @@
field public static final int LATIN_EXTENDED_D_ID = 149; // 0x95
field public static final android.icu.lang.UCharacter.UnicodeBlock LATIN_EXTENDED_E;
field public static final int LATIN_EXTENDED_E_ID = 231; // 0xe7
+ field public static final android.icu.lang.UCharacter.UnicodeBlock LATIN_EXTENDED_F;
+ field public static final int LATIN_EXTENDED_F_ID = 313; // 0x139
+ field public static final android.icu.lang.UCharacter.UnicodeBlock LATIN_EXTENDED_G;
+ field public static final int LATIN_EXTENDED_G_ID = 314; // 0x13a
field public static final android.icu.lang.UCharacter.UnicodeBlock LEPCHA;
field public static final int LEPCHA_ID = 156; // 0x9c
field public static final android.icu.lang.UCharacter.UnicodeBlock LETTERLIKE_SYMBOLS;
@@ -883,6 +902,8 @@
field public static final int OLD_SOUTH_ARABIAN_ID = 187; // 0xbb
field public static final android.icu.lang.UCharacter.UnicodeBlock OLD_TURKIC;
field public static final int OLD_TURKIC_ID = 191; // 0xbf
+ field public static final android.icu.lang.UCharacter.UnicodeBlock OLD_UYGHUR;
+ field public static final int OLD_UYGHUR_ID = 315; // 0x13b
field public static final android.icu.lang.UCharacter.UnicodeBlock OL_CHIKI;
field public static final int OL_CHIKI_ID = 157; // 0x9d
field public static final android.icu.lang.UCharacter.UnicodeBlock OPTICAL_CHARACTER_RECOGNITION;
@@ -1011,6 +1032,8 @@
field public static final int TAMIL_ID = 20; // 0x14
field public static final android.icu.lang.UCharacter.UnicodeBlock TAMIL_SUPPLEMENT;
field public static final int TAMIL_SUPPLEMENT_ID = 299; // 0x12b
+ field public static final android.icu.lang.UCharacter.UnicodeBlock TANGSA;
+ field public static final int TANGSA_ID = 316; // 0x13c
field public static final android.icu.lang.UCharacter.UnicodeBlock TANGUT;
field public static final android.icu.lang.UCharacter.UnicodeBlock TANGUT_COMPONENTS;
field public static final int TANGUT_COMPONENTS_ID = 273; // 0x111
@@ -1029,12 +1052,16 @@
field public static final int TIFINAGH_ID = 144; // 0x90
field public static final android.icu.lang.UCharacter.UnicodeBlock TIRHUTA;
field public static final int TIRHUTA_ID = 251; // 0xfb
+ field public static final android.icu.lang.UCharacter.UnicodeBlock TOTO;
+ field public static final int TOTO_ID = 317; // 0x13d
field public static final android.icu.lang.UCharacter.UnicodeBlock TRANSPORT_AND_MAP_SYMBOLS;
field public static final int TRANSPORT_AND_MAP_SYMBOLS_ID = 207; // 0xcf
field public static final android.icu.lang.UCharacter.UnicodeBlock UGARITIC;
field public static final int UGARITIC_ID = 120; // 0x78
field public static final android.icu.lang.UCharacter.UnicodeBlock UNIFIED_CANADIAN_ABORIGINAL_SYLLABICS;
field public static final android.icu.lang.UCharacter.UnicodeBlock UNIFIED_CANADIAN_ABORIGINAL_SYLLABICS_EXTENDED;
+ field public static final android.icu.lang.UCharacter.UnicodeBlock UNIFIED_CANADIAN_ABORIGINAL_SYLLABICS_EXTENDED_A;
+ field public static final int UNIFIED_CANADIAN_ABORIGINAL_SYLLABICS_EXTENDED_A_ID = 318; // 0x13e
field public static final int UNIFIED_CANADIAN_ABORIGINAL_SYLLABICS_EXTENDED_ID = 173; // 0xad
field public static final int UNIFIED_CANADIAN_ABORIGINAL_SYLLABICS_ID = 33; // 0x21
field public static final android.icu.lang.UCharacter.UnicodeBlock VAI;
@@ -1047,6 +1074,8 @@
field public static final int VEDIC_EXTENSIONS_ID = 175; // 0xaf
field public static final android.icu.lang.UCharacter.UnicodeBlock VERTICAL_FORMS;
field public static final int VERTICAL_FORMS_ID = 145; // 0x91
+ field public static final android.icu.lang.UCharacter.UnicodeBlock VITHKUQI;
+ field public static final int VITHKUQI_ID = 319; // 0x13f
field public static final android.icu.lang.UCharacter.UnicodeBlock WANCHO;
field public static final int WANCHO_ID = 300; // 0x12c
field public static final android.icu.lang.UCharacter.UnicodeBlock WARANG_CITI;
@@ -1061,6 +1090,8 @@
field public static final int YI_SYLLABLES_ID = 72; // 0x48
field public static final android.icu.lang.UCharacter.UnicodeBlock ZANABAZAR_SQUARE;
field public static final int ZANABAZAR_SQUARE_ID = 280; // 0x118
+ field public static final android.icu.lang.UCharacter.UnicodeBlock ZNAMENNY_MUSICAL_NOTATION;
+ field public static final int ZNAMENNY_MUSICAL_NOTATION_ID = 320; // 0x140
}
public static interface UCharacter.VerticalOrientation {
@@ -1355,6 +1386,7 @@
field public static final int COPTIC = 7; // 0x7
field public static final int CUNEIFORM = 101; // 0x65
field public static final int CYPRIOT = 47; // 0x2f
+ field public static final int CYPRO_MINOAN = 193; // 0xc1
field public static final int CYRILLIC = 8; // 0x8
field public static final int DEMOTIC_EGYPTIAN = 69; // 0x45
field public static final int DESERET = 9; // 0x9
@@ -1461,6 +1493,7 @@
field public static final int OLD_PERSIAN = 61; // 0x3d
field public static final int OLD_SOGDIAN = 184; // 0xb8
field public static final int OLD_SOUTH_ARABIAN = 133; // 0x85
+ field public static final int OLD_UYGHUR = 194; // 0xc2
field public static final int OL_CHIKI = 109; // 0x6d
field public static final int ORIYA = 31; // 0x1f
field public static final int ORKHON = 88; // 0x58
@@ -1500,6 +1533,7 @@
field public static final int TAI_VIET = 127; // 0x7f
field public static final int TAKRI = 153; // 0x99
field public static final int TAMIL = 35; // 0x23
+ field public static final int TANGSA = 195; // 0xc3
field public static final int TANGUT = 154; // 0x9a
field public static final int TELUGU = 36; // 0x24
field public static final int TENGWAR = 98; // 0x62
@@ -1508,6 +1542,7 @@
field public static final int TIBETAN = 39; // 0x27
field public static final int TIFINAGH = 60; // 0x3c
field public static final int TIRHUTA = 158; // 0x9e
+ field public static final int TOTO = 196; // 0xc4
field public static final int TRADITIONAL_HAN = 74; // 0x4a
field public static final int UCAS = 40; // 0x28
field public static final int UGARITIC = 53; // 0x35
@@ -1515,6 +1550,7 @@
field public static final int UNWRITTEN_LANGUAGES = 102; // 0x66
field public static final int VAI = 99; // 0x63
field public static final int VISIBLE_SPEECH = 100; // 0x64
+ field public static final int VITHKUQI = 197; // 0xc5
field public static final int WANCHO = 188; // 0xbc
field public static final int WARANG_CITI = 146; // 0x92
field public static final int WESTERN_SYRIAC = 96; // 0x60
@@ -1649,6 +1685,7 @@
public class FormattedNumber implements android.icu.text.FormattedValue {
method public <A extends java.lang.Appendable> A appendTo(A);
method public char charAt(int);
+ method public android.icu.util.MeasureUnit getOutputUnit();
method public int length();
method public boolean nextPosition(android.icu.text.ConstrainedFieldPosition);
method public CharSequence subSequence(int, int);
@@ -1730,11 +1767,13 @@
}
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;
enum_constant public static final android.icu.number.NumberFormatter.UnitWidth HIDDEN;
enum_constant public static final android.icu.number.NumberFormatter.UnitWidth ISO_CODE;
enum_constant public static final android.icu.number.NumberFormatter.UnitWidth NARROW;
enum_constant public static final android.icu.number.NumberFormatter.UnitWidth SHORT;
+ enum_constant public static final android.icu.number.NumberFormatter.UnitWidth VARIANT;
}
public abstract class NumberFormatterSettings<T extends android.icu.number.NumberFormatterSettings<?>> {
@@ -1751,6 +1790,7 @@
method public T symbols(android.icu.text.NumberingSystem);
method public T unit(android.icu.util.MeasureUnit);
method public T unitWidth(android.icu.number.NumberFormatter.UnitWidth);
+ method public T usage(String);
}
public abstract class NumberRangeFormatter {
@@ -2241,6 +2281,7 @@
method public static final android.icu.text.DateFormat getDateTimeInstance(android.icu.util.Calendar, int, int);
method public static final android.icu.text.DateFormat getInstance();
method public static final android.icu.text.DateFormat getInstance(android.icu.util.Calendar, java.util.Locale);
+ method public static final android.icu.text.DateFormat getInstance(android.icu.util.Calendar, android.icu.util.ULocale);
method public static final android.icu.text.DateFormat getInstance(android.icu.util.Calendar);
method public static final android.icu.text.DateFormat getInstanceForSkeleton(String);
method public static final android.icu.text.DateFormat getInstanceForSkeleton(String, java.util.Locale);
@@ -2407,6 +2448,13 @@
field public static final android.icu.text.DateFormat.Field YEAR_WOY;
}
+ public enum DateFormat.HourCycle {
+ enum_constant public static final android.icu.text.DateFormat.HourCycle HOUR_CYCLE_11;
+ enum_constant public static final android.icu.text.DateFormat.HourCycle HOUR_CYCLE_12;
+ enum_constant public static final android.icu.text.DateFormat.HourCycle HOUR_CYCLE_23;
+ enum_constant public static final android.icu.text.DateFormat.HourCycle HOUR_CYCLE_24;
+ }
+
public class DateFormatSymbols implements java.lang.Cloneable java.io.Serializable {
ctor public DateFormatSymbols();
ctor public DateFormatSymbols(java.util.Locale);
@@ -2467,6 +2515,7 @@
method public final StringBuffer format(android.icu.util.Calendar, android.icu.util.Calendar, StringBuffer, java.text.FieldPosition);
method public android.icu.text.DateIntervalFormat.FormattedDateInterval formatToValue(android.icu.util.DateInterval);
method public android.icu.text.DateIntervalFormat.FormattedDateInterval formatToValue(android.icu.util.Calendar, android.icu.util.Calendar);
+ method public android.icu.text.DisplayContext getContext(android.icu.text.DisplayContext.Type);
method public android.icu.text.DateFormat getDateFormat();
method public android.icu.text.DateIntervalInfo getDateIntervalInfo();
method public static final android.icu.text.DateIntervalFormat getInstance(String);
@@ -2477,6 +2526,7 @@
method public static final android.icu.text.DateIntervalFormat getInstance(String, android.icu.util.ULocale, android.icu.text.DateIntervalInfo);
method public android.icu.util.TimeZone getTimeZone();
method @Deprecated public Object parseObject(String, java.text.ParsePosition);
+ method public void setContext(android.icu.text.DisplayContext);
method public void setDateIntervalInfo(android.icu.text.DateIntervalInfo);
method public void setTimeZone(android.icu.util.TimeZone);
}
@@ -2525,6 +2575,7 @@
method public String getBestPattern(String, int);
method public String getDateTimeFormat();
method public String getDecimal();
+ method public android.icu.text.DateFormat.HourCycle getDefaultHourCycle();
method public static android.icu.text.DateTimePatternGenerator getEmptyInstance();
method public String getFieldDisplayName(int, android.icu.text.DateTimePatternGenerator.DisplayWidth);
method public static android.icu.text.DateTimePatternGenerator getInstance();
@@ -2652,6 +2703,8 @@
method public void setSignificantDigitsUsed(boolean);
method public String toLocalizedPattern();
method public String toPattern();
+ field public static final int MINIMUM_GROUPING_DIGITS_AUTO = -2; // 0xfffffffe
+ field public static final int MINIMUM_GROUPING_DIGITS_MIN2 = -3; // 0xfffffffd
field public static final int PAD_AFTER_PREFIX = 1; // 0x1
field public static final int PAD_AFTER_SUFFIX = 3; // 0x3
field public static final int PAD_BEFORE_PREFIX = 0; // 0x0
@@ -2666,6 +2719,7 @@
method public static android.icu.text.DecimalFormatSymbols forNumberingSystem(java.util.Locale, android.icu.text.NumberingSystem);
method public static android.icu.text.DecimalFormatSymbols forNumberingSystem(android.icu.util.ULocale, android.icu.text.NumberingSystem);
method public static java.util.Locale[] getAvailableLocales();
+ method public static android.icu.util.ULocale[] getAvailableULocales();
method public android.icu.util.Currency getCurrency();
method public String getCurrencySymbol();
method public char getDecimalSeparator();
@@ -2838,12 +2892,37 @@
public final class ListFormatter {
method public String format(java.lang.Object...);
method public String format(java.util.Collection<?>);
+ method public android.icu.text.ListFormatter.FormattedList formatToValue(java.lang.Object...);
+ method public android.icu.text.ListFormatter.FormattedList formatToValue(java.util.Collection<?>);
+ method public static android.icu.text.ListFormatter getInstance(android.icu.util.ULocale, android.icu.text.ListFormatter.Type, android.icu.text.ListFormatter.Width);
+ method public static android.icu.text.ListFormatter getInstance(java.util.Locale, android.icu.text.ListFormatter.Type, android.icu.text.ListFormatter.Width);
method public static android.icu.text.ListFormatter getInstance(android.icu.util.ULocale);
method public static android.icu.text.ListFormatter getInstance(java.util.Locale);
method public static android.icu.text.ListFormatter getInstance();
method public String getPatternForNumItems(int);
}
+ public static final class ListFormatter.FormattedList implements android.icu.text.FormattedValue {
+ method public <A extends java.lang.Appendable> A appendTo(A);
+ method public char charAt(int);
+ method public int length();
+ method public boolean nextPosition(android.icu.text.ConstrainedFieldPosition);
+ method public CharSequence subSequence(int, int);
+ method public java.text.AttributedCharacterIterator toCharacterIterator();
+ }
+
+ public enum ListFormatter.Type {
+ enum_constant public static final android.icu.text.ListFormatter.Type AND;
+ enum_constant public static final android.icu.text.ListFormatter.Type OR;
+ enum_constant public static final android.icu.text.ListFormatter.Type UNITS;
+ }
+
+ public enum ListFormatter.Width {
+ enum_constant public static final android.icu.text.ListFormatter.Width NARROW;
+ enum_constant public static final android.icu.text.ListFormatter.Width SHORT;
+ enum_constant public static final android.icu.text.ListFormatter.Width WIDE;
+ }
+
public abstract class LocaleDisplayNames {
method public abstract android.icu.text.DisplayContext getContext(android.icu.text.DisplayContext.Type);
method public abstract android.icu.text.LocaleDisplayNames.DialectHandling getDialectHandling();
@@ -3224,6 +3303,7 @@
method public static android.icu.text.PluralRules parseDescription(String) throws java.text.ParseException;
method public String select(double);
method public String select(android.icu.number.FormattedNumber);
+ method public String select(android.icu.number.FormattedNumberRange);
field public static final android.icu.text.PluralRules DEFAULT;
field public static final String KEYWORD_FEW = "few";
field public static final String KEYWORD_MANY = "many";
@@ -4152,10 +4232,12 @@
method public String getSymbol(android.icu.util.ULocale);
method public static boolean isAvailable(String, java.util.Date, java.util.Date);
method public java.util.Currency toJavaCurrency();
+ field public static final int FORMAL_SYMBOL_NAME = 4; // 0x4
field public static final int LONG_NAME = 1; // 0x1
field public static final int NARROW_SYMBOL_NAME = 3; // 0x3
field public static final int PLURAL_LONG_NAME = 2; // 0x2
field public static final int SYMBOL_NAME = 0; // 0x0
+ field public static final int VARIANT_SYMBOL_NAME = 5; // 0x5
}
public enum Currency.CurrencyUsage {
@@ -4394,11 +4476,19 @@
}
public class MeasureUnit implements java.io.Serializable {
+ method public static android.icu.util.MeasureUnit forIdentifier(String);
method public static java.util.Set<android.icu.util.MeasureUnit> getAvailable(String);
method public static java.util.Set<android.icu.util.MeasureUnit> getAvailable();
method public static java.util.Set<java.lang.String> getAvailableTypes();
+ method public android.icu.util.MeasureUnit.Complexity getComplexity();
+ method public int getDimensionality();
+ method public String getIdentifier();
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);
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;
@@ -4410,6 +4500,7 @@
field public static final android.icu.util.MeasureUnit BUSHEL;
field public static final android.icu.util.MeasureUnit BYTE;
field public static final android.icu.util.MeasureUnit CALORIE;
+ field public static final android.icu.util.MeasureUnit CANDELA;
field public static final android.icu.util.MeasureUnit CARAT;
field public static final android.icu.util.MeasureUnit CELSIUS;
field public static final android.icu.util.MeasureUnit CENTILITER;
@@ -4429,6 +4520,7 @@
field public static final android.icu.util.MeasureUnit DECILITER;
field public static final android.icu.util.MeasureUnit DECIMETER;
field public static final android.icu.util.MeasureUnit DEGREE;
+ field public static final android.icu.util.MeasureUnit DOT;
field public static final android.icu.util.MeasureUnit DOT_PER_CENTIMETER;
field public static final android.icu.util.MeasureUnit DOT_PER_INCH;
field public static final android.icu.util.MeasureUnit EM;
@@ -4473,6 +4565,7 @@
field public static final android.icu.util.MeasureUnit LITER;
field public static final android.icu.util.MeasureUnit LITER_PER_100KILOMETERS;
field public static final android.icu.util.MeasureUnit LITER_PER_KILOMETER;
+ field public static final android.icu.util.MeasureUnit LUMEN;
field public static final android.icu.util.MeasureUnit LUX;
field public static final android.icu.util.MeasureUnit MEGABIT;
field public static final android.icu.util.MeasureUnit MEGABYTE;
@@ -4548,6 +4641,12 @@
field public static final android.icu.util.TimeUnit YEAR;
}
+ public enum MeasureUnit.Complexity {
+ enum_constant public static final android.icu.util.MeasureUnit.Complexity COMPOUND;
+ enum_constant public static final android.icu.util.MeasureUnit.Complexity MIXED;
+ enum_constant public static final android.icu.util.MeasureUnit.Complexity SINGLE;
+ }
+
public class Output<T> {
ctor public Output();
ctor public Output(T);
@@ -4658,6 +4757,7 @@
method public Object clone();
method public int compareTo(android.icu.util.ULocale);
method public static android.icu.util.ULocale createCanonical(String);
+ method public static android.icu.util.ULocale createCanonical(android.icu.util.ULocale);
method public static android.icu.util.ULocale forLanguageTag(String);
method public static android.icu.util.ULocale forLocale(java.util.Locale);
method public static android.icu.util.ULocale[] getAvailableLocales();
@@ -4854,6 +4954,7 @@
field public static final android.icu.util.VersionInfo UNICODE_12_0;
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_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/api/public/module-lib-current.txt b/android_icu4j/api/public/module-lib-current.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/android_icu4j/api/public/module-lib-current.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/android_icu4j/api/public/module-lib-removed.txt b/android_icu4j/api/public/module-lib-removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/android_icu4j/api/public/module-lib-removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/android_icu4j/api/public/system-current.txt b/android_icu4j/api/public/system-current.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/android_icu4j/api/public/system-current.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/android_icu4j/api/public/system-removed.txt b/android_icu4j/api/public/system-removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/android_icu4j/api/public/system-removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/CountryTimeZones.java b/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/CountryTimeZones.java
index 2640c9c..0528b10 100644
--- a/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/CountryTimeZones.java
+++ b/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/CountryTimeZones.java
@@ -366,7 +366,7 @@
* otherwise an arbitrary match is returned based on the {@link
* #getEffectiveTimeZoneMappingsAt(long)} ordering.
*
- * @param whenMillis the UTC time to match against
+ * @param whenMillis the Unix epoch time to match against
* @param bias the time zone to prefer, can be {@code null} to indicate there is no preference
* @param totalOffsetMillis the offset from UTC at {@code whenMillis}
* @param isDst the Daylight Savings Time state at {@code whenMillis}. {@code true} means DST,
@@ -386,7 +386,7 @@
* otherwise an arbitrary match is returned based on the {@link
* #getEffectiveTimeZoneMappingsAt(long)} ordering.
*
- * @param whenMillis the UTC time to match against
+ * @param whenMillis the Unix epoch time to match against
* @param bias the time zone to prefer, can be {@code null} to indicate there is no preference
* @param totalOffsetMillis the offset from UTC at {@code whenMillis}
* @return an {@link OffsetResult} with information about a matching zone, or {@code null} if
@@ -457,7 +457,7 @@
* otherwise an arbitrary match is returned based on the {@link
* #getEffectiveTimeZoneMappingsAt(long)} ordering.
*
- * @param whenMillis the UTC time to match against
+ * @param whenMillis the Unix epoch time to match against
* @param bias the time zone to prefer, can be {@code null}
* @param totalOffsetMillis the offset from UTC at {@code whenMillis}
* @param isDst the Daylight Savings Time state at {@code whenMillis}. {@code true} means DST,
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 ac73b37..5af6d45 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 = 5; // Android S
+ public static final int CURRENT_FORMAT_MAJOR_VERSION = 6; // Android T
/**
* Returns the major tz data format version supported by this device.
diff --git a/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/ZoneInfoData.java b/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/ZoneInfoData.java
index 92c0c43..0d419a9 100644
--- a/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/ZoneInfoData.java
+++ b/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/ZoneInfoData.java
@@ -85,8 +85,9 @@
private final int mEarliestRawOffset;
/**
- * The times (in seconds) at which the offsets changes for any reason, whether that is a change
- * in the offset from UTC or a change in the DST.
+ * The times (in Unix epoch time, seconds since 1st Jan 1970 00:00:00 UTC) at which the offsets
+ * changes for any reason, whether that is a change in the offset from UTC or a change in the
+ * DST.
*
* <p>These times are pre-calculated externally from a set of rules (both historical and
* future) and stored in a file from which {@link ZoneInfoData#readTimeZone(String,
@@ -563,21 +564,21 @@
/**
* Get the raw and DST offsets for the specified time in milliseconds since
- * 1st Jan 1970 00:00:00.000 UTC.
+ * 1st Jan 1970 00:00:00 UTC.
*
* <p>The raw offset, i.e. that part of the total offset which is not due to DST, is stored at
* index 0 of the {@code offsets} array and the DST offset, i.e. that part of the offset which
* is due to DST is stored at index 1.
*
- * @param utcTimeInMillis the UTC time in milliseconds.
+ * @param unixEpochTimeInMillis the Unix epoch time in milliseconds.
* @param offsets the array whose length must be greater than or equal to 2.
* @return the total offset which is the sum of the raw and DST offsets.
*
* @hide
*/
@libcore.api.IntraCoreApi
- public int getOffsetsByUtcTime(long utcTimeInMillis, @NonNull int[] offsets) {
- int transitionIndex = findTransitionIndex(roundDownMillisToSeconds(utcTimeInMillis));
+ public int getOffsetsByUtcTime(long unixEpochTimeInMillis, @NonNull int[] offsets) {
+ int transitionIndex = findTransitionIndex(roundDownMillisToSeconds(unixEpochTimeInMillis));
int totalOffset;
int rawOffset;
int dstOffset;
@@ -624,15 +625,15 @@
}
/**
- * Returns the offset from UTC in milliseconds at the specified time {@when}.
+ * Returns the offset from UTC in milliseconds at the specified time {@code whenMillis}.
*
- * @param when the number of milliseconds since January 1, 1970, 00:00:00 GMT
+ * @param whenMillis the Unix epoch time in milliseconds since 1st Jan 1970, 00:00:00 UTC
*
* @hide
*/
@libcore.api.IntraCoreApi
- public int getOffset(long when) {
- int offsetIndex = findOffsetIndexForTimeInMilliseconds(when);
+ public int getOffset(long whenMillis) {
+ int offsetIndex = findOffsetIndexForTimeInMilliseconds(whenMillis);
if (offsetIndex == -1) {
// Assume that all times before our first transition correspond to the
// oldest-known non-daylight offset. The obvious alternative would be to
@@ -643,15 +644,15 @@
}
/**
- * Returns whether the given {@code when} is in daylight saving time in this time zone.
+ * Returns whether the given {@code whenMillis} is in daylight saving time in this time zone.
*
- * @param when the number of milliseconds since January 1, 1970, 00:00:00 GMT
+ * @param whenMillis the Unix epoch time in milliseconds since 1st Jan 1970, 00:00:00 UTC
*
* @hide
*/
@libcore.api.IntraCoreApi
- public boolean isInDaylightTime(long when) {
- int offsetIndex = findOffsetIndexForTimeInMilliseconds(when);
+ public boolean isInDaylightTime(long whenMillis) {
+ int offsetIndex = findOffsetIndexForTimeInMilliseconds(whenMillis);
if (offsetIndex == -1) {
// Assume that all times before our first transition are non-daylight.
// Transition data tends to start with a transition to daylight, so just
@@ -674,15 +675,15 @@
/**
* Returns the offset of daylight saving in milliseconds in the latest Daylight Savings Time
- * after the time {@code when}. If no known DST occurs after {@code when}, it returns
- * {@code null}.
+ * after the time {@code whenMillis}. If no known DST occurs after {@code whenMillis}, it
+ * returns {@code null}.
*
- * @param when the number of milliseconds since January 1, 1970, 00:00:00 GMT
+ * @param whenMillis the Unix epoch time in milliseconds since 1st Jan 1970, 00:00:00 UTC
*
* @hide
*/
@libcore.api.IntraCoreApi
- public @Nullable Integer getLatestDstSavingsMillis(long when) {
+ public @Nullable Integer getLatestDstSavingsMillis(long whenMillis) {
// Find the latest daylight and standard offsets (if any).
int lastStdTransitionIndex = -1;
int lastDstTransitionIndex = -1;
@@ -707,7 +708,7 @@
// time in milliseconds into seconds in order to compare with transition time this
// rounds up rather than down. It does that because this is interested in what
// transitions apply in future
- long currentUnixTimeSeconds = roundUpMillisToSeconds(when);
+ long currentUnixTimeSeconds = roundUpMillisToSeconds(whenMillis);
// Is this zone observing DST currently or in the future?
// We don't care if they've historically used it: most places have at least once.
@@ -811,8 +812,9 @@
}
/**
- * Returns the times (in seconds) at which the offsets changes for any reason, whether that is a
- * change in the offset from UTC or a change in the DST.
+ * Returns the Unix epoch times (in seconds since 1st Jan 1970 00:00:00 UTC) at which the
+ * offsets changes for any reason, whether that is a change in the offset from UTC or a change
+ * in the DST.
*
* WARNING: This API is exposed only for app compat usage in @link libcore.util.ZoneInfo}.
*
diff --git a/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/internal/Memory.java b/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/internal/Memory.java
index 846044a..d61b99d 100644
--- a/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/internal/Memory.java
+++ b/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/internal/Memory.java
@@ -153,12 +153,19 @@
}
@FastNative
private static native short peekShortNative(long address);
+ @FastNative
public static native void peekByteArray(long address, byte[] dst, int dstOffset, int byteCount);
+ @FastNative
public static native void peekCharArray(long address, char[] dst, int dstOffset, int charCount, boolean swap);
+ @FastNative
public static native void peekDoubleArray(long address, double[] dst, int dstOffset, int doubleCount, boolean swap);
+ @FastNative
public static native void peekFloatArray(long address, float[] dst, int dstOffset, int floatCount, boolean swap);
+ @FastNative
public static native void peekIntArray(long address, int[] dst, int dstOffset, int intCount, boolean swap);
+ @FastNative
public static native void peekLongArray(long address, long[] dst, int dstOffset, int longCount, boolean swap);
+ @FastNative
public static native void peekShortArray(long address, short[] dst, int dstOffset, int shortCount, boolean swap);
@FastNative
public static native void pokeByte(long address, byte value);
diff --git a/android_icu4j/libcore_bridge/src/java/com/android/icu/util/ExtendedTimeZone.java b/android_icu4j/libcore_bridge/src/java/com/android/icu/util/ExtendedTimeZone.java
index aaa8563..d0ec593 100644
--- a/android_icu4j/libcore_bridge/src/java/com/android/icu/util/ExtendedTimeZone.java
+++ b/android_icu4j/libcore_bridge/src/java/com/android/icu/util/ExtendedTimeZone.java
@@ -217,7 +217,7 @@
AnnualTimeZoneRule firstAnnualRule =
(AnnualTimeZoneRule) firstTransitionToAnnualRule.getTo();
AnnualTimeZoneRule currentTimeZoneRule = firstAnnualRule;
- long currentUtcTime = firstTransitionToAnnualRule.getTime();
+ long currentUnixEpochTime = firstTransitionToAnnualRule.getTime();
do {
annualTimeZoneRules.add(currentTimeZoneRule);
@@ -231,7 +231,7 @@
if (!lastStandardOffset.equals(ruleStandardOffset)) {
standardOffsetTransitionList.add(
ZoneOffsetTransition.of(
- localDateTime(currentUtcTime, lastStandardOffset),
+ localDateTime(currentUnixEpochTime, lastStandardOffset),
lastStandardOffset,
ruleStandardOffset
));
@@ -239,7 +239,7 @@
}
int currentYear =
- Instant.ofEpochMilli(currentUtcTime).atOffset(lastWallOffset).getYear();
+ Instant.ofEpochMilli(currentUnixEpochTime).atOffset(lastWallOffset).getYear();
ZoneOffsetTransition recurringRuleTransition =
createZoneOffsetTransitionRule(
currentTimeZoneRule,
@@ -250,20 +250,20 @@
// After introduction of first annual rule wall offset may not change.
if (!lastWallOffset.equals(recurringRuleTransition.getOffsetAfter())) {
transitionList.add(ZoneOffsetTransition.of(
- localDateTime(currentUtcTime, lastWallOffset),
+ localDateTime(currentUnixEpochTime, lastWallOffset),
lastWallOffset,
recurringRuleTransition.getOffsetAfter()));
lastWallOffset = recurringRuleTransition.getOffsetAfter();
}
TimeZoneTransition nextTransition =
- basicTimeZone.getNextTransition(currentUtcTime, false /* inclusive */);
- currentUtcTime = nextTransition.getTime();
+ basicTimeZone.getNextTransition(currentUnixEpochTime, false /* inclusive */);
+ currentUnixEpochTime = nextTransition.getTime();
currentTimeZoneRule = (AnnualTimeZoneRule) nextTransition.getTo();
if (currentTimeZoneRule == null) {
throw zoneRulesException("No transitions after "
- + currentUtcTime + " for a timezone with recurring rules");
+ + currentUnixEpochTime + " for a timezone with recurring rules");
}
} while (!currentTimeZoneRule.isEquivalentTo(firstAnnualRule));
diff --git a/android_icu4j/libcore_bridge/src/native/com_android_i18n_timezone_internal_Memory.cpp b/android_icu4j/libcore_bridge/src/native/com_android_i18n_timezone_internal_Memory.cpp
index f97b332..0027a43 100644
--- a/android_icu4j/libcore_bridge/src/native/com_android_i18n_timezone_internal_Memory.cpp
+++ b/android_icu4j/libcore_bridge/src/native/com_android_i18n_timezone_internal_Memory.cpp
@@ -1,3 +1,4 @@
+
/*
* Copyright (C) 2007 The Android Open Source Project
*
@@ -222,16 +223,16 @@
static JNINativeMethod gMethods[] = {
FAST_NATIVE_METHOD(Memory, peekByte, "(J)B"),
- NATIVE_METHOD(Memory, peekByteArray, "(J[BII)V"),
- NATIVE_METHOD(Memory, peekCharArray, "(J[CIIZ)V"),
- NATIVE_METHOD(Memory, peekDoubleArray, "(J[DIIZ)V"),
- NATIVE_METHOD(Memory, peekFloatArray, "(J[FIIZ)V"),
+ FAST_NATIVE_METHOD(Memory, peekByteArray, "(J[BII)V"),
+ FAST_NATIVE_METHOD(Memory, peekCharArray, "(J[CIIZ)V"),
+ FAST_NATIVE_METHOD(Memory, peekDoubleArray, "(J[DIIZ)V"),
+ FAST_NATIVE_METHOD(Memory, peekFloatArray, "(J[FIIZ)V"),
FAST_NATIVE_METHOD(Memory, peekIntNative, "(J)I"),
- NATIVE_METHOD(Memory, peekIntArray, "(J[IIIZ)V"),
+ FAST_NATIVE_METHOD(Memory, peekIntArray, "(J[IIIZ)V"),
FAST_NATIVE_METHOD(Memory, peekLongNative, "(J)J"),
- NATIVE_METHOD(Memory, peekLongArray, "(J[JIIZ)V"),
+ FAST_NATIVE_METHOD(Memory, peekLongArray, "(J[JIIZ)V"),
FAST_NATIVE_METHOD(Memory, peekShortNative, "(J)S"),
- NATIVE_METHOD(Memory, peekShortArray, "(J[SIIZ)V"),
+ FAST_NATIVE_METHOD(Memory, peekShortArray, "(J[SIIZ)V"),
FAST_NATIVE_METHOD(Memory, pokeByte, "(JB)V"),
NATIVE_METHOD(Memory, pokeByteArray, "(J[BII)V"),
NATIVE_METHOD(Memory, pokeCharArray, "(J[CIIZ)V"),
diff --git a/android_icu4j/src/main/java/android/icu/impl/CalendarAstronomer.java b/android_icu4j/src/main/java/android/icu/impl/CalendarAstronomer.java
index 2ea9ebd..088100d 100644
--- a/android_icu4j/src/main/java/android/icu/impl/CalendarAstronomer.java
+++ b/android_icu4j/src/main/java/android/icu/impl/CalendarAstronomer.java
@@ -363,7 +363,7 @@
*/
public double getGreenwichSidereal() {
if (siderealTime == INVALID) {
- // See page 86 of "Practial Astronomy with your Calculator",
+ // See page 86 of "Practical Astronomy with your Calculator",
// by Peter Duffet-Smith, for details on the algorithm.
double UT = normalize((double)time/HOUR_MS, 24);
@@ -441,7 +441,7 @@
*/
public final Equatorial eclipticToEquatorial(double eclipLong, double eclipLat)
{
- // See page 42 of "Practial Astronomy with your Calculator",
+ // See page 42 of "Practical Astronomy with your Calculator",
// by Peter Duffet-Smith, for details on the algorithm.
double obliq = eclipticObliquity();
@@ -569,7 +569,7 @@
*/
public double getSunLongitude()
{
- // See page 86 of "Practial Astronomy with your Calculator",
+ // See page 86 of "Practical Astronomy with your Calculator",
// by Peter Duffet-Smith, for details on the algorithm.
if (sunLongitude == INVALID) {
@@ -585,7 +585,7 @@
*/
/*public*/ double[] getSunLongitude(double julian)
{
- // See page 86 of "Practial Astronomy with your Calculator",
+ // See page 86 of "Practical Astronomy with your Calculator",
// by Peter Duffet-Smith, for details on the algorithm.
double day = julian - JD_EPOCH; // Days since epoch
@@ -1007,7 +1007,7 @@
public Equatorial getMoonPosition()
{
//
- // See page 142 of "Practial Astronomy with your Calculator",
+ // See page 142 of "Practical Astronomy with your Calculator",
// by Peter Duffet-Smith, for details on the algorithm.
//
if (moonPosition == null) {
@@ -1093,7 +1093,7 @@
* @hide draft / provisional / internal are hidden on Android
*/
public double getMoonAge() {
- // See page 147 of "Practial Astronomy with your Calculator",
+ // See page 147 of "Practical Astronomy with your Calculator",
// by Peter Duffet-Smith, for details on the algorithm.
//
// Force the moon's position to be calculated. We're going to use
@@ -1119,7 +1119,7 @@
* @hide draft / provisional / internal are hidden on Android
*/
public double getMoonPhase() {
- // See page 147 of "Practial Astronomy with your Calculator",
+ // See page 147 of "Practical Astronomy with your Calculator",
// by Peter Duffet-Smith, for details on the algorithm.
return 0.5 * (1 - Math.cos(getMoonAge()));
}
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 137c4b9..7925b3c 100644
--- a/android_icu4j/src/main/java/android/icu/impl/CaseMapImpl.java
+++ b/android_icu4j/src/main/java/android/icu/impl/CaseMapImpl.java
@@ -834,7 +834,7 @@
private static final int AFTER_VOWEL_WITH_ACCENT = 2;
// Data generated by prototype code, see
- // http://site.icu-project.org/design/case/greek-upper
+ // https://icu.unicode.org/design/case/greek-upper
// TODO: Move this data into ucase.icu.
private static final char[] data0370 = {
// U+0370..03FF
diff --git a/android_icu4j/src/main/java/android/icu/impl/CharacterIteration.java b/android_icu4j/src/main/java/android/icu/impl/CharacterIteration.java
index 7a52a00..affff65 100644
--- a/android_icu4j/src/main/java/android/icu/impl/CharacterIteration.java
+++ b/android_icu4j/src/main/java/android/icu/impl/CharacterIteration.java
@@ -37,30 +37,30 @@
// which leaves it in position for underlying iterator's next() to work.
int c = ci.current();
if (c >= UTF16.LEAD_SURROGATE_MIN_VALUE && c<=UTF16.LEAD_SURROGATE_MAX_VALUE) {
- c = ci.next();
+ c = ci.next();
if (c<UTF16.TRAIL_SURROGATE_MIN_VALUE || c>UTF16.TRAIL_SURROGATE_MAX_VALUE) {
- ci.previous();
+ ci.previous();
}
}
// For BMP chars, this next() is the real deal.
c = ci.next();
-
- // If we might have a lead surrogate, we need to peak ahead to get the trail
+
+ // If we might have a lead surrogate, we need to peak ahead to get the trail
// even though we don't want to really be positioned there.
if (c >= UTF16.LEAD_SURROGATE_MIN_VALUE) {
- c = nextTrail32(ci, c);
+ c = nextTrail32(ci, c);
}
-
+
if (c >= UTF16.SUPPLEMENTARY_MIN_VALUE && c != DONE32) {
- // We got a supplementary char. Back the iterator up to the postion
+ // We got a supplementary char. Back the iterator up to the position
// of the lead surrogate.
- ci.previous();
+ ci.previous();
}
return c;
}
-
+
// Out-of-line portion of the in-line Next32 code.
// The call site does an initial ci.next() and calls this function
// if the 16 bit value it gets is >= LEAD_SURROGATE_MIN_VALUE.
@@ -85,36 +85,36 @@
}
return retVal;
}
-
+
public static int previous32(CharacterIterator ci) {
if (ci.getIndex() <= ci.getBeginIndex()) {
- return DONE32;
+ return DONE32;
}
char trail = ci.previous();
int retVal = trail;
if (UTF16.isTrailSurrogate(trail) && ci.getIndex()>ci.getBeginIndex()) {
char lead = ci.previous();
if (UTF16.isLeadSurrogate(lead)) {
- retVal = (((int)lead - UTF16.LEAD_SURROGATE_MIN_VALUE) << 10) +
- ((int)trail - UTF16.TRAIL_SURROGATE_MIN_VALUE) +
+ retVal = ((lead - UTF16.LEAD_SURROGATE_MIN_VALUE) << 10) +
+ (trail - UTF16.TRAIL_SURROGATE_MIN_VALUE) +
UTF16.SUPPLEMENTARY_MIN_VALUE;
} else {
ci.next();
- }
+ }
}
return retVal;
}
-
+
public static int current32(CharacterIterator ci) {
char lead = ci.current();
int retVal = lead;
if (retVal < UTF16.LEAD_SURROGATE_MIN_VALUE) {
- return retVal;
+ return retVal;
}
if (UTF16.isLeadSurrogate(lead)) {
- int trail = (int)ci.next();
+ int trail = ci.next();
ci.previous();
- if (UTF16.isTrailSurrogate((char)trail)) {
+ if (UTF16.isTrailSurrogate(trail)) {
retVal = ((lead - UTF16.LEAD_SURROGATE_MIN_VALUE) << 10) +
(trail - UTF16.TRAIL_SURROGATE_MIN_VALUE) +
UTF16.SUPPLEMENTARY_MIN_VALUE;
@@ -122,7 +122,7 @@
} else {
if (lead == CharacterIterator.DONE) {
if (ci.getIndex() >= ci.getEndIndex()) {
- retVal = DONE32;
+ retVal = DONE32;
}
}
}
diff --git a/android_icu4j/src/main/java/android/icu/impl/CharacterPropertiesImpl.java b/android_icu4j/src/main/java/android/icu/impl/CharacterPropertiesImpl.java
index 7a6bd9b..ffbef2c 100644
--- a/android_icu4j/src/main/java/android/icu/impl/CharacterPropertiesImpl.java
+++ b/android_icu4j/src/main/java/android/icu/impl/CharacterPropertiesImpl.java
@@ -72,6 +72,10 @@
case UCharacterProperty.SRC_VO:
UCharacterProperty.ulayout_addPropertyStarts(src, incl);
break;
+ case UCharacterProperty.SRC_EMOJI: {
+ EmojiProps.INSTANCE.addPropertyStarts(incl);
+ break;
+ }
default:
throw new IllegalStateException("getInclusions(unknown src " + src + ")");
}
diff --git a/android_icu4j/src/main/java/android/icu/impl/DateNumberFormat.java b/android_icu4j/src/main/java/android/icu/impl/DateNumberFormat.java
index 6bf6589..f160e78 100644
--- a/android_icu4j/src/main/java/android/icu/impl/DateNumberFormat.java
+++ b/android_icu4j/src/main/java/android/icu/impl/DateNumberFormat.java
@@ -144,7 +144,7 @@
@Override
public StringBuffer format(double number, StringBuffer toAppendTo,
FieldPosition pos) {
- throw new UnsupportedOperationException("StringBuffer format(double, StringBuffer, FieldPostion) is not implemented");
+ throw new UnsupportedOperationException("StringBuffer format(double, StringBuffer, FieldPosition) is not implemented");
}
@Override
@@ -190,19 +190,19 @@
@Override
public StringBuffer format(BigInteger number, StringBuffer toAppendTo,
FieldPosition pos) {
- throw new UnsupportedOperationException("StringBuffer format(BigInteger, StringBuffer, FieldPostion) is not implemented");
+ throw new UnsupportedOperationException("StringBuffer format(BigInteger, StringBuffer, FieldPosition) is not implemented");
}
@Override
public StringBuffer format(java.math.BigDecimal number, StringBuffer toAppendTo,
FieldPosition pos) {
- throw new UnsupportedOperationException("StringBuffer format(BigDecimal, StringBuffer, FieldPostion) is not implemented");
+ throw new UnsupportedOperationException("StringBuffer format(BigDecimal, StringBuffer, FieldPosition) is not implemented");
}
@Override
public StringBuffer format(BigDecimal number,
StringBuffer toAppendTo, FieldPosition pos) {
- throw new UnsupportedOperationException("StringBuffer format(BigDecimal, StringBuffer, FieldPostion) is not implemented");
+ throw new UnsupportedOperationException("StringBuffer format(BigDecimal, StringBuffer, FieldPosition) is not implemented");
}
/*
diff --git a/android_icu4j/src/main/java/android/icu/impl/EmojiProps.java b/android_icu4j/src/main/java/android/icu/impl/EmojiProps.java
new file mode 100644
index 0000000..d114201
--- /dev/null
+++ b/android_icu4j/src/main/java/android/icu/impl/EmojiProps.java
@@ -0,0 +1,201 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+// © 2021 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html
+
+// emojiprops.h
+// created: 2021sep06 Markus W. Scherer
+
+package android.icu.impl;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import android.icu.lang.UProperty;
+import android.icu.text.UnicodeSet;
+import android.icu.util.BytesTrie;
+import android.icu.util.CharsTrie;
+import android.icu.util.CodePointMap;
+import android.icu.util.CodePointTrie;
+import android.icu.util.ICUUncheckedIOException;
+
+/**
+ * @hide Only a subset of ICU is exposed in Android
+ */
+public final class EmojiProps {
+ private static final class IsAcceptable implements ICUBinary.Authenticate {
+ @Override
+ public boolean isDataVersionAcceptable(byte version[]) {
+ return version[0] == 1;
+ }
+ }
+ private static final IsAcceptable IS_ACCEPTABLE = new IsAcceptable();
+ private static final int DATA_FORMAT = 0x456d6f6a; // "Emoj"
+
+ // Byte offsets from the start of the data, after the generic header,
+ // in ascending order.
+ // UCPTrie=CodePointTrie, follows the indexes
+ private static final int IX_CPTRIE_OFFSET = 0;
+
+ // UCharsTrie=CharsTrie
+ private static final int IX_BASIC_EMOJI_TRIE_OFFSET = 4;
+ //ivate static final int IX_EMOJI_KEYCAP_SEQUENCE_TRIE_OFFSET = 5;
+ //ivate static final int IX_RGI_EMOJI_MODIFIER_SEQUENCE_TRIE_OFFSET = 6;
+ //ivate static final int IX_RGI_EMOJI_FLAG_SEQUENCE_TRIE_OFFSET = 7;
+ //ivate static final int IX_RGI_EMOJI_TAG_SEQUENCE_TRIE_OFFSET = 8;
+ private static final int IX_RGI_EMOJI_ZWJ_SEQUENCE_TRIE_OFFSET = 9;
+
+ // Properties in the code point trie.
+ // https://www.unicode.org/reports/tr51/#Emoji_Properties
+ private static final int BIT_EMOJI = 0;
+ private static final int BIT_EMOJI_PRESENTATION = 1;
+ private static final int BIT_EMOJI_MODIFIER = 2;
+ private static final int BIT_EMOJI_MODIFIER_BASE = 3;
+ private static final int BIT_EMOJI_COMPONENT = 4;
+ private static final int BIT_EXTENDED_PICTOGRAPHIC = 5;
+ // https://www.unicode.org/reports/tr51/#Emoji_Sets
+ private static final int BIT_BASIC_EMOJI = 6;
+
+ public static final EmojiProps INSTANCE = new EmojiProps();
+
+ private CodePointTrie.Fast8 cpTrie = null;
+ private String stringTries[] = new String[6];
+
+ /** Input i: One of the IX_..._TRIE_OFFSET indexes into the data file indexes[] array. */
+ private static int getStringTrieIndex(int i) {
+ return i - IX_BASIC_EMOJI_TRIE_OFFSET;
+ }
+
+ private EmojiProps() {
+ ByteBuffer bytes = ICUBinary.getRequiredData("uemoji.icu");
+ try {
+ ICUBinary.readHeaderAndDataVersion(bytes, DATA_FORMAT, IS_ACCEPTABLE);
+ int startPos = bytes.position();
+
+ int cpTrieOffset = bytes.getInt(); // inIndexes[IX_CPTRIE_OFFSET]
+ int indexesLength = cpTrieOffset / 4;
+ if (indexesLength <= IX_RGI_EMOJI_ZWJ_SEQUENCE_TRIE_OFFSET) {
+ throw new ICUUncheckedIOException(
+ "Emoji properties data: not enough indexes");
+ }
+
+ int[] inIndexes = new int[indexesLength];
+ inIndexes[0] = cpTrieOffset;
+ for (int i = 1; i < indexesLength; ++i) {
+ inIndexes[i] = bytes.getInt();
+ }
+
+ int i = IX_CPTRIE_OFFSET;
+ int offset = inIndexes[i++];
+ int nextOffset = inIndexes[i];
+ cpTrie = CodePointTrie.Fast8.fromBinary(bytes);
+ int pos = bytes.position() - startPos;
+ assert nextOffset >= pos;
+ ICUBinary.skipBytes(bytes, nextOffset - pos); // skip padding after trie bytes
+
+ offset = nextOffset;
+ nextOffset = inIndexes[IX_BASIC_EMOJI_TRIE_OFFSET];
+ ICUBinary.skipBytes(bytes, nextOffset - offset); // skip unknown bytes
+
+ for (i = IX_BASIC_EMOJI_TRIE_OFFSET; i <= IX_RGI_EMOJI_ZWJ_SEQUENCE_TRIE_OFFSET; ++i) {
+ offset = inIndexes[i];
+ nextOffset = inIndexes[i + 1];
+ // Set/leave null if there is no CharsTrie.
+ if (nextOffset > offset) {
+ stringTries[getStringTrieIndex(i)] =
+ ICUBinary.getString(bytes, (nextOffset - offset) / 2, 0);
+ }
+ }
+ } catch(IOException e) {
+ throw new ICUUncheckedIOException(e);
+ }
+ }
+
+ public UnicodeSet addPropertyStarts(UnicodeSet set) {
+ // Add the start code point of each same-value range of the trie.
+ CodePointMap.Range range = new CodePointMap.Range();
+ int start = 0;
+ while (cpTrie.getRange(start, null, range)) {
+ set.add(start);
+ start = range.getEnd() + 1;
+ }
+ return set;
+ }
+
+ // Note: REGIONAL_INDICATOR is a single, hardcoded range implemented elsewhere.
+ private static final byte[] bitFlags = {
+ BIT_EMOJI, // UCHAR_EMOJI=57
+ BIT_EMOJI_PRESENTATION, // UCHAR_EMOJI_PRESENTATION=58
+ BIT_EMOJI_MODIFIER, // UCHAR_EMOJI_MODIFIER=59
+ BIT_EMOJI_MODIFIER_BASE, // UCHAR_EMOJI_MODIFIER_BASE=60
+ BIT_EMOJI_COMPONENT, // UCHAR_EMOJI_COMPONENT=61
+ -1, // UCHAR_REGIONAL_INDICATOR=62
+ -1, // UCHAR_PREPENDED_CONCATENATION_MARK=63
+ BIT_EXTENDED_PICTOGRAPHIC, // UCHAR_EXTENDED_PICTOGRAPHIC=64
+ BIT_BASIC_EMOJI, // UCHAR_BASIC_EMOJI=65
+ -1, // UCHAR_EMOJI_KEYCAP_SEQUENCE=66
+ -1, // UCHAR_RGI_EMOJI_MODIFIER_SEQUENCE=67
+ -1, // UCHAR_RGI_EMOJI_FLAG_SEQUENCE=68
+ -1, // UCHAR_RGI_EMOJI_TAG_SEQUENCE=69
+ -1, // UCHAR_RGI_EMOJI_ZWJ_SEQUENCE=70
+ BIT_BASIC_EMOJI, // UCHAR_RGI_EMOJI=71
+ };
+
+ public boolean hasBinaryProperty(int c, int which) {
+ if (which < UProperty.EMOJI || UProperty.RGI_EMOJI < which) {
+ return false;
+ }
+ int bit = bitFlags[which - UProperty.EMOJI];
+ if (bit < 0) {
+ return false; // not a property that we support in this function
+ }
+ int bits = cpTrie.get(c);
+ return ((bits >> bit) & 1) != 0;
+ }
+
+ public boolean hasBinaryProperty(CharSequence s, int which) {
+ int length = s.length();
+ if (length == 0) { return false; } // empty string
+ // The caller should have delegated single code points to hasBinaryProperty(c, which).
+ if (which < UProperty.BASIC_EMOJI || UProperty.RGI_EMOJI < which) {
+ return false;
+ }
+ int firstProp = which, lastProp = which;
+ if (which == UProperty.RGI_EMOJI) {
+ // RGI_Emoji is the union of the other emoji properties of strings.
+ firstProp = UProperty.BASIC_EMOJI;
+ lastProp = UProperty.RGI_EMOJI_ZWJ_SEQUENCE;
+ }
+ for (int prop = firstProp; prop <= lastProp; ++prop) {
+ String trieUChars = stringTries[prop - UProperty.BASIC_EMOJI];
+ if (trieUChars != null) {
+ CharsTrie trie = new CharsTrie(trieUChars, 0);
+ BytesTrie.Result result = trie.next(s, 0, length);
+ if (result.hasValue()) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ public void addStrings(int which, UnicodeSet set) {
+ if (which < UProperty.BASIC_EMOJI || UProperty.RGI_EMOJI < which) {
+ return;
+ }
+ int firstProp = which, lastProp = which;
+ if (which == UProperty.RGI_EMOJI) {
+ // RGI_Emoji is the union of the other emoji properties of strings.
+ firstProp = UProperty.BASIC_EMOJI;
+ lastProp = UProperty.RGI_EMOJI_ZWJ_SEQUENCE;
+ }
+ for (int prop = firstProp; prop <= lastProp; ++prop) {
+ String trieUChars = stringTries[prop - UProperty.BASIC_EMOJI];
+ if (trieUChars != null) {
+ CharsTrie trie = new CharsTrie(trieUChars, 0);
+ for (CharsTrie.Entry entry : trie) {
+ set.add(entry.chars);
+ }
+ }
+ }
+ }
+}
diff --git a/android_icu4j/src/main/java/android/icu/impl/EraRules.java b/android_icu4j/src/main/java/android/icu/impl/EraRules.java
index 3bb2c54..34532f0 100644
--- a/android_icu4j/src/main/java/android/icu/impl/EraRules.java
+++ b/android_icu4j/src/main/java/android/icu/impl/EraRules.java
@@ -56,7 +56,7 @@
try {
eraIdx = Integer.parseInt(eraIdxStr);
} catch (NumberFormatException e) {
- throw new ICUException("Invald era rule key:" + eraIdxStr + " in era rule data for " + calType.getId());
+ throw new ICUException("Invalid era rule key:" + eraIdxStr + " in era rule data for " + calType.getId());
}
if (eraIdx < 0 || eraIdx >= numEras) {
throw new ICUException("Era rule key:" + eraIdxStr + " in era rule data for " + calType.getId()
@@ -64,7 +64,7 @@
}
if (isSet(startDates[eraIdx])) {
throw new ICUException(
- "Dupulicated era rule for rule key:" + eraIdxStr + " in era rule data for " + calType.getId());
+ "Duplicated era rule for rule key:" + eraIdxStr + " in era rule data for " + calType.getId());
}
boolean hasName = true;
diff --git a/android_icu4j/src/main/java/android/icu/impl/FormattedStringBuilder.java b/android_icu4j/src/main/java/android/icu/impl/FormattedStringBuilder.java
index 6c971d2..616101e 100644
--- a/android_icu4j/src/main/java/android/icu/impl/FormattedStringBuilder.java
+++ b/android_icu4j/src/main/java/android/icu/impl/FormattedStringBuilder.java
@@ -27,6 +27,25 @@
*/
public class FormattedStringBuilder implements CharSequence, Appendable {
+ /**
+ * @hide Only a subset of ICU is exposed in Android
+ */
+ public static interface FieldWrapper {
+ java.text.Format.Field unwrap();
+ }
+
+ public static java.text.Format.Field unwrapField(Object field) {
+ if (field == null) {
+ return null;
+ } else if (field instanceof FieldWrapper) {
+ return ((FieldWrapper) field).unwrap();
+ } else if (field instanceof java.text.Format.Field) {
+ return (java.text.Format.Field) field;
+ } else {
+ throw new AssertionError("Not a field: " + field);
+ }
+ }
+
/** A constant, empty FormattedStringBuilder. Do NOT call mutative operations on this. */
public static final FormattedStringBuilder EMPTY = new FormattedStringBuilder();
@@ -536,10 +555,12 @@
if (fields.length != length)
return false;
for (int i = 0; i < length; i++) {
- if (this.chars[zero + i] != chars[i])
+ if (this.chars[zero + i] != chars[i]) {
return false;
- if (this.fields[zero + i] != fields[i])
+ }
+ if (unwrapField(this.fields[zero + i]) != unwrapField(fields[i])) {
return false;
+ }
}
return true;
}
@@ -553,7 +574,10 @@
if (length != other.length)
return false;
for (int i = 0; i < length; i++) {
- if (charAt(i) != other.charAt(i) || fieldAt(i) != other.fieldAt(i)) {
+ if (charAt(i) != other.charAt(i)) {
+ return false;
+ }
+ if (unwrapField(fieldAt(i)) != unwrapField(other.fieldAt(i))) {
return false;
}
}
diff --git a/android_icu4j/src/main/java/android/icu/impl/FormattedValueStringBuilderImpl.java b/android_icu4j/src/main/java/android/icu/impl/FormattedValueStringBuilderImpl.java
index c5e8954..bb26a17 100644
--- a/android_icu4j/src/main/java/android/icu/impl/FormattedValueStringBuilderImpl.java
+++ b/android_icu4j/src/main/java/android/icu/impl/FormattedValueStringBuilderImpl.java
@@ -33,10 +33,16 @@
* Does not currently support nested fields beyond one level.
* @hide Only a subset of ICU is exposed in Android
*/
- public static class SpanFieldPlaceholder {
+ public static class SpanFieldPlaceholder implements FormattedStringBuilder.FieldWrapper {
public UFormat.SpanField spanField;
public Field normalField;
public Object value;
+ public int start;
+ public int length;
+
+ public Field unwrap() {
+ return normalField;
+ }
}
/**
@@ -57,6 +63,29 @@
return -1;
}
+ /**
+ * Upgrade a range of a string to a span field.
+ *
+ * Similar to appendSpanInfo in ICU4C.
+ */
+ public static void applySpanRange(
+ FormattedStringBuilder self,
+ UFormat.SpanField spanField,
+ Object value,
+ int start,
+ int end) {
+ for (int i = start + self.zero; i < end + self.zero; i++) {
+ Object oldField = self.fields[i];
+ SpanFieldPlaceholder newField = new SpanFieldPlaceholder();
+ newField.spanField = spanField;
+ newField.normalField = (java.text.Format.Field) oldField;
+ newField.value = value;
+ newField.start = start;
+ newField.length = end - start;
+ self.fields[i] = newField;
+ }
+ }
+
public static boolean nextFieldPosition(FormattedStringBuilder self, FieldPosition fp) {
java.text.Format.Field rawField = fp.getFieldAttribute();
@@ -139,18 +168,23 @@
public static boolean nextPosition(FormattedStringBuilder self, ConstrainedFieldPosition cfpos, Field numericField) {
int fieldStart = -1;
Object currField = null;
+ boolean prevIsSpan = false;
+ if (cfpos.getLimit() > 0) {
+ prevIsSpan = cfpos.getField() instanceof UFormat.SpanField
+ && cfpos.getStart() < cfpos.getLimit();
+ }
+ boolean prevIsNumeric = false;
+ if (numericField != null) {
+ prevIsNumeric = cfpos.getField() == numericField;
+ }
+ boolean prevIsInteger = cfpos.getField() == NumberFormat.Field.INTEGER;
+
for (int i = self.zero + cfpos.getLimit(); i <= self.zero + self.length; i++) {
Object _field = (i < self.zero + self.length) ? self.fields[i] : NullField.END;
// Case 1: currently scanning a field.
if (currField != null) {
if (currField != _field) {
int end = i - self.zero;
- // Handle span fields; don't trim them
- if (currField instanceof SpanFieldPlaceholder) {
- boolean handleResult = handleSpan(currField, cfpos, fieldStart, end);
- assert handleResult;
- return true;
- }
// Grouping separators can be whitespace; don't throw them out!
if (isTrimmable(currField)) {
end = trimBack(self, end);
@@ -171,11 +205,30 @@
}
continue;
}
+ // Special case: emit normalField if we are pointing at the end of spanField.
+ if (i > self.zero && prevIsSpan) {
+ assert self.fields[i-1] instanceof SpanFieldPlaceholder;
+ SpanFieldPlaceholder ph = (SpanFieldPlaceholder) self.fields[i-1];
+ if (ph.normalField == ListFormatter.Field.ELEMENT) {
+ // Special handling for ULISTFMT_ELEMENT_FIELD
+ if (cfpos.matchesField(ListFormatter.Field.ELEMENT, null)) {
+ fieldStart = i - self.zero - ph.length;
+ int end = fieldStart + ph.length;
+ cfpos.setState(ListFormatter.Field.ELEMENT, null, fieldStart, end);
+ return true;
+ }
+ } else {
+ // Re-wind, since there may be multiple fields in the span.
+ i -= ph.length;
+ assert i >= self.zero;
+ _field = ((SpanFieldPlaceholder) self.fields[i]).normalField;
+ }
+ }
// Special case: coalesce the INTEGER if we are pointing at the end of the INTEGER.
if (cfpos.matchesField(NumberFormat.Field.INTEGER, null)
&& i > self.zero
- // don't return the same field twice in a row:
- && i - self.zero > cfpos.getLimit()
+ && !prevIsInteger
+ && !prevIsNumeric
&& isIntOrGroup(self.fields[i - 1])
&& !isIntOrGroup(_field)) {
int j = i - 1;
@@ -187,46 +240,58 @@
if (numericField != null
&& cfpos.matchesField(numericField, null)
&& i > self.zero
- // don't return the same field twice in a row:
- && (i - self.zero > cfpos.getLimit() || cfpos.getField() != numericField)
+ && !prevIsNumeric
&& isNumericField(self.fields[i - 1])
&& !isNumericField(_field)) {
+ // Re-wind to the beginning of the field and then emit it
int j = i - 1;
for (; j >= self.zero && isNumericField(self.fields[j]); j--) {}
cfpos.setState(numericField, null, j - self.zero + 1, i - self.zero);
return true;
}
- // Special case: emit normalField if we are pointing at the end of spanField.
- if (i > self.zero
- && self.fields[i-1] instanceof SpanFieldPlaceholder) {
- int j = i - 1;
- for (; j >= self.zero && self.fields[j] == self.fields[i-1]; j--) {}
- if (handleSpan(self.fields[i-1], cfpos, j - self.zero + 1, i - self.zero)) {
+ // Check for span field
+ SpanFieldPlaceholder ph = null;
+ if (_field instanceof SpanFieldPlaceholder) {
+ ph = (SpanFieldPlaceholder) _field;
+ _field = ph.normalField;
+ }
+ if (ph != null && (ph.start == -1 || ph.start == i - self.zero)) {
+ if (cfpos.matchesField(ph.spanField, ph.value)) {
+ fieldStart = i - self.zero;
+ int end = fieldStart + ph.length;
+ cfpos.setState(ph.spanField, ph.value, fieldStart, end);
return true;
+ } else if (ph.normalField == ListFormatter.Field.ELEMENT) {
+ // Special handling for ListFormatter.Field.ELEMENT
+ if (cfpos.matchesField(ListFormatter.Field.ELEMENT, null)) {
+ fieldStart = i - self.zero;
+ int end = fieldStart + ph.length;
+ cfpos.setState(ListFormatter.Field.ELEMENT, null, fieldStart, end);
+ return true;
+ } else {
+ // Failed to match; jump ahead
+ i += ph.length - 1;
+ // goto loopend
+ }
}
}
// Special case: skip over INTEGER; will be coalesced later.
- if (_field == NumberFormat.Field.INTEGER) {
+ else if (_field == NumberFormat.Field.INTEGER) {
_field = null;
}
- // Case 2: no field starting at this position.
- if (_field == null || _field == NullField.END) {
- continue;
+ // No field starting at this position.
+ else if (_field == null || _field == NullField.END) {
+ // goto loopend
}
- // Case 3: check for field starting at this position
- // Case 3a: SpanField placeholder
- if (_field instanceof SpanFieldPlaceholder) {
- SpanFieldPlaceholder ph = (SpanFieldPlaceholder) _field;
- if (cfpos.matchesField(ph.normalField, null) || cfpos.matchesField(ph.spanField, ph.value)) {
- fieldStart = i - self.zero;
- currField = _field;
- }
- }
- // Case 3b: No SpanField
+ // No SpanField
else if (cfpos.matchesField((Field) _field, null)) {
fieldStart = i - self.zero;
currField = _field;
}
+ // loopend:
+ prevIsSpan = false;
+ prevIsNumeric = false;
+ prevIsInteger = false;
}
assert currField == null;
@@ -240,10 +305,12 @@
}
private static boolean isIntOrGroup(Object field) {
+ field = FormattedStringBuilder.unwrapField(field);
return field == NumberFormat.Field.INTEGER || field == NumberFormat.Field.GROUPING_SEPARATOR;
}
private static boolean isNumericField(Object field) {
+ field = FormattedStringBuilder.unwrapField(field);
return field == null || NumberFormat.Field.class.isAssignableFrom(field.getClass());
}
@@ -261,19 +328,4 @@
return StaticUnicodeSets.get(StaticUnicodeSets.Key.DEFAULT_IGNORABLES)
.span(self, start, UnicodeSet.SpanCondition.CONTAINED);
}
-
- private static boolean handleSpan(Object field, ConstrainedFieldPosition cfpos, int start, int limit) {
- SpanFieldPlaceholder ph = (SpanFieldPlaceholder) field;
- if (cfpos.matchesField(ph.spanField, ph.value)
- && cfpos.getLimit() < limit) {
- cfpos.setState(ph.spanField, ph.value, start, limit);
- return true;
- }
- if (cfpos.matchesField(ph.normalField, null)
- && (cfpos.getLimit() < limit || cfpos.getField() != ph.normalField)) {
- cfpos.setState(ph.normalField, null, start, limit);
- return true;
- }
- return false;
- }
}
diff --git a/android_icu4j/src/main/java/android/icu/impl/ICUDebug.java b/android_icu4j/src/main/java/android/icu/impl/ICUDebug.java
index e3a7ae1..804c749 100644
--- a/android_icu4j/src/main/java/android/icu/impl/ICUDebug.java
+++ b/android_icu4j/src/main/java/android/icu/impl/ICUDebug.java
@@ -9,8 +9,6 @@
*/
package android.icu.impl;
-import android.icu.util.VersionInfo;
-
/**
* @hide Only a subset of ICU is exposed in Android
*/
@@ -32,55 +30,6 @@
}
}
- public static final String javaVersionString = System.getProperty("java.version", "0");
- public static final boolean isJDK14OrHigher;
- public static final VersionInfo javaVersion;
-
- public static VersionInfo getInstanceLenient(String s) {
- // Extracting ASCII numbers up to 4 delimited by
- // any non digit characters
- int[] ver = new int[4];
- boolean numeric = false;
- int i = 0, vidx = 0;
- while (i < s.length()) {
- char c = s.charAt(i++);
- if (c < '0' || c > '9') {
- if (numeric) {
- if (vidx == 3) {
- // up to 4 numbers
- break;
- }
- numeric = false;
- vidx++;
- }
- } else {
- if (numeric) {
- ver[vidx] = ver[vidx] * 10 + (c - '0');
- if (ver[vidx] > 255) {
- // VersionInfo does not support numbers
- // greater than 255. In such case, we
- // ignore the number and the rest
- ver[vidx] = 0;
- break;
- }
- } else {
- numeric = true;
- ver[vidx] = c - '0';
- }
- }
- }
-
- return VersionInfo.getInstance(ver[0], ver[1], ver[2], ver[3]);
- }
-
- static {
- javaVersion = getInstanceLenient(javaVersionString);
-
- VersionInfo java14Version = VersionInfo.getInstance("1.4.0");
-
- isJDK14OrHigher = javaVersion.compareTo(java14Version) >= 0;
- }
-
public static boolean enabled() {
return debug;
}
@@ -113,23 +62,4 @@
}
return result;
}
-
-// static public void main(String[] args) {
-// // test
-// String[] tests = {
-// "1.3.0",
-// "1.3.0_02",
-// "1.3.1ea",
-// "1.4.1b43",
-// "___41___5",
-// "x1.4.51xx89ea.7f",
-// "1.6_2009",
-// "10-100-1000-10000",
-// "beta",
-// "0",
-// };
-// for (int i = 0; i < tests.length; ++i) {
-// System.out.println(tests[i] + " => " + getInstanceLenient(tests[i]));
-// }
-// }
}
diff --git a/android_icu4j/src/main/java/android/icu/impl/ICULocaleService.java b/android_icu4j/src/main/java/android/icu/impl/ICULocaleService.java
index 035225f..7db1ab4 100644
--- a/android_icu4j/src/main/java/android/icu/impl/ICULocaleService.java
+++ b/android_icu4j/src/main/java/android/icu/impl/ICULocaleService.java
@@ -620,9 +620,9 @@
if (loc != fallbackLocale) {
synchronized (this) {
if (loc != fallbackLocale) {
- fallbackLocale = loc;
fallbackLocaleName = loc.getBaseName();
clearServiceCache();
+ fallbackLocale = loc;
}
}
}
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 fe64de1..7c4d92e 100644
--- a/android_icu4j/src/main/java/android/icu/impl/ICUResourceBundle.java
+++ b/android_icu4j/src/main/java/android/icu/impl/ICUResourceBundle.java
@@ -400,6 +400,13 @@
}
}
+ /**
+ * Locates the resource specified by `path` in this resource bundle (performing any necessary fallback and
+ * following any aliases) and calls the specified `sink`'s `put()` method with that resource. Then walks the
+ * bundle's parent chain, calling `put()` on the sink for each item in the parent chain.
+ * @param path The path of the desired resource
+ * @param sink A `UResource.Sink` that gets called for each resource in the parent chain
+ */
public void getAllItemsWithFallback(String path, UResource.Sink sink)
throws MissingResourceException {
// Collect existing and parsed key objects into an array of keys,
@@ -426,6 +433,47 @@
rb.getAllItemsWithFallback(key, readerValue, sink);
}
+ /**
+ * Locates the resource specified by `path` in this resource bundle (performing any necessary fallback and
+ * following any aliases) and, if the resource is a table resource, iterates over its immediate child resources (again,
+ * following any aliases to get the individual resource values), and calls the specified `sink`'s `put()` method
+ * for each child resource (passing it that resource's key and either its actual value or, if that value is an
+ * alias, the value you get by following the alias). Then walks back over the bundle's parent chain,
+ * similarly iterating over each parent table resource's child resources.
+ * Does not descend beyond one level of table children.
+ * @param path The path of the desired resource
+ * @param sink A `UResource.Sink` that gets called for each child resource of the specified resource (and each child
+ * of the resources in its parent chain).
+ */
+ public void getAllChildrenWithFallback(final String path, final UResource.Sink sink)
+ throws MissingResourceException {
+ class AllChildrenSink extends UResource.Sink {
+ @Override
+ public void put(UResource.Key key, UResource.Value value, boolean noFallback) {
+ UResource.Table itemsTable = value.getTable();
+ for (int i = 0; itemsTable.getKeyAndValue(i, key, value); ++i) {
+ if (value.getType() == ALIAS) {
+ // if the current entry in the table is an alias, re-fetch it using getAliasedResource():
+ // this will follow the alias (and any aliases it points to) and bring back the real value
+ String aliasPath = value.getAliasString();
+ ICUResourceBundle aliasedResource = getAliasedResource(aliasPath, wholeBundle.loader,
+ "", null, 0, null,
+ null, ICUResourceBundle.this);
+ ICUResourceBundleImpl aliasedResourceImpl = (ICUResourceBundleImpl)aliasedResource;
+ ReaderValue aliasedValue = new ReaderValue();
+ aliasedValue.reader = aliasedResourceImpl.wholeBundle.reader;
+ aliasedValue.res = aliasedResourceImpl.getResource();
+ sink.put(key, aliasedValue, noFallback);
+ } else {
+ sink.put(key, value, noFallback);
+ }
+ }
+ }
+ }
+
+ getAllItemsWithFallback(path, new AllChildrenSink());
+ }
+
private void getAllItemsWithFallback(
UResource.Key key, ReaderValue readerValue, UResource.Sink sink) {
// We recursively enumerate child-first,
@@ -1490,10 +1538,27 @@
UResourceBundle requested) {
WholeBundle wholeBundle = base.wholeBundle;
ClassLoader loaderToUse = wholeBundle.loader;
+ String rpath = wholeBundle.reader.getAlias(_resource);
+ String baseName = wholeBundle.baseName;
+
+ // TODO: We need not build the baseKeyPath array if the rpath includes a keyPath
+ // (except for an exception message string).
+ // Try to avoid unnecessary work+allocation.
+ int baseDepth = base.getResDepth();
+ String[] baseKeyPath = new String[baseDepth + 1];
+ base.getResPathKeys(baseKeyPath, baseDepth);
+ baseKeyPath[baseDepth] = key;
+ return getAliasedResource(rpath, loaderToUse, baseName, keys, depth, baseKeyPath, aliasesVisited, requested);
+ }
+
+ protected static ICUResourceBundle getAliasedResource(
+ String rpath, ClassLoader loaderToUse, String baseName,
+ String[] keys, int depth, String[] baseKeyPath,
+ HashMap<String, String> aliasesVisited,
+ UResourceBundle requested) {
String locale;
String keyPath = null;
String bundleName;
- String rpath = wholeBundle.reader.getAlias(_resource);
if (aliasesVisited == null) {
aliasesVisited = new HashMap<>();
}
@@ -1532,12 +1597,12 @@
} else {
locale = rpath;
}
- bundleName = wholeBundle.baseName;
+ bundleName = baseName;
}
ICUResourceBundle bundle = null;
ICUResourceBundle sub = null;
if(bundleName.equals(LOCALE)){
- bundleName = wholeBundle.baseName;
+ bundleName = baseName;
keyPath = rpath.substring(LOCALE.length() + 2/* prepending and appending / */, rpath.length());
// Get the top bundle of the requested bundle
@@ -1559,11 +1624,8 @@
} else if (keys != null) {
numKeys = depth;
} else {
- depth = base.getResDepth();
- numKeys = depth + 1;
- keys = new String[numKeys];
- base.getResPathKeys(keys, depth);
- keys[depth] = key;
+ keys = baseKeyPath;
+ numKeys = baseKeyPath.length;
}
if (numKeys > 0) {
sub = bundle;
@@ -1573,7 +1635,7 @@
}
}
if (sub == null) {
- throw new MissingResourceException(wholeBundle.localeID, wholeBundle.baseName, key);
+ throw new MissingResourceException(locale, baseName, baseKeyPath[baseKeyPath.length - 1]);
}
// TODO: If we know that sub is not cached,
// then we should set its container and key to the alias' location,
diff --git a/android_icu4j/src/main/java/android/icu/impl/ICUService.java b/android_icu4j/src/main/java/android/icu/impl/ICUService.java
index 5ee1620..4ece905 100644
--- a/android_icu4j/src/main/java/android/icu/impl/ICUService.java
+++ b/android_icu4j/src/main/java/android/icu/impl/ICUService.java
@@ -202,7 +202,7 @@
* If the key has a fallback, modify the key and return true,
* otherwise return false. The current ID will change if there
* is a fallback. No currentIDs should be repeated, and fallback
- * must eventually return false. This implmentation has no fallbacks
+ * must eventually return false. This implementation has no fallbacks
* and always returns false.
*/
public boolean fallback() {
@@ -918,7 +918,7 @@
/**
* ServiceListener is the listener that ICUService provides by default.
- * ICUService will notifiy this listener when factories are added to
+ * ICUService will notify this listener when factories are added to
* or removed from the service. Subclasses can provide
* different listener interfaces that extend EventListener, and modify
* acceptsListener and notifyListener as appropriate.
diff --git a/android_icu4j/src/main/java/android/icu/impl/IDNA2003.java b/android_icu4j/src/main/java/android/icu/impl/IDNA2003.java
index f4ee5ae..478b11e 100644
--- a/android_icu4j/src/main/java/android/icu/impl/IDNA2003.java
+++ b/android_icu4j/src/main/java/android/icu/impl/IDNA2003.java
@@ -183,7 +183,7 @@
int poLen = processOut.length();
if(poLen==0){
- throw new StringPrepParseException("Found zero length lable after NamePrep.",StringPrepParseException.ZERO_LENGTH_LABEL);
+ throw new StringPrepParseException("Found zero length label after NamePrep.",StringPrepParseException.ZERO_LENGTH_LABEL);
}
StringBuffer dest = new StringBuffer();
@@ -411,7 +411,7 @@
sepIndex = getSeparatorIndex(srcArr,sepIndex,srcArr.length);
String label = new String(srcArr,oldSepIndex,sepIndex-oldSepIndex);
if(label.length()==0 && sepIndex!=srcArr.length ){
- throw new StringPrepParseException("Found zero length lable after NamePrep.",StringPrepParseException.ZERO_LENGTH_LABEL);
+ throw new StringPrepParseException("Found zero length label after NamePrep.",StringPrepParseException.ZERO_LENGTH_LABEL);
}
UCharacterIterator iter = UCharacterIterator.getInstance(label);
result.append(convertToUnicode(iter,options));
diff --git a/android_icu4j/src/main/java/android/icu/impl/IntTrieBuilder.java b/android_icu4j/src/main/java/android/icu/impl/IntTrieBuilder.java
index d2ee3cc..c0b5c6e 100644
--- a/android_icu4j/src/main/java/android/icu/impl/IntTrieBuilder.java
+++ b/android_icu4j/src/main/java/android/icu/impl/IntTrieBuilder.java
@@ -56,7 +56,7 @@
* Constructs a build table
* @param aliasdata data to be filled into table
* @param maxdatalength maximum data length allowed in table
- * @param initialvalue inital data value
+ * @param initialvalue initial data value
* @param latin1linear is latin 1 to be linear
*/
public IntTrieBuilder(int aliasdata[], int maxdatalength,
diff --git a/android_icu4j/src/main/java/android/icu/impl/LocaleDisplayNamesImpl.java b/android_icu4j/src/main/java/android/icu/impl/LocaleDisplayNamesImpl.java
index 8ee53be..1715ad4 100644
--- a/android_icu4j/src/main/java/android/icu/impl/LocaleDisplayNamesImpl.java
+++ b/android_icu4j/src/main/java/android/icu/impl/LocaleDisplayNamesImpl.java
@@ -430,13 +430,28 @@
}
private String localeIdName(String localeId) {
+ String locIdName;
if (nameLength == DisplayContext.LENGTH_SHORT) {
- String locIdName = langData.get("Languages%short", localeId);
+ locIdName = langData.get("Languages%short", localeId);
if (locIdName != null && !locIdName.equals(localeId)) {
return locIdName;
}
}
- return langData.get("Languages", localeId);
+ locIdName = langData.get("Languages", localeId);
+ if ((locIdName == null || locIdName.equals(localeId)) && localeId.indexOf('_') < 0) {
+ // Canonicalize lang and try again, ICU-20870
+ // (only for language codes without script or region)
+ ULocale canonLocale = ULocale.createCanonical(localeId);
+ String canonLocId = canonLocale.getName();
+ if (nameLength == DisplayContext.LENGTH_SHORT) {
+ locIdName = langData.get("Languages%short", canonLocId);
+ if (locIdName != null && !locIdName.equals(canonLocId)) {
+ return locIdName;
+ }
+ }
+ locIdName = langData.get("Languages", canonLocId);
+ }
+ return locIdName;
}
@Override
@@ -445,13 +460,27 @@
if (lang.equals("root") || lang.indexOf('_') != -1) {
return substituteHandling == DisplayContext.SUBSTITUTE ? lang : null;
}
+ String langName;
if (nameLength == DisplayContext.LENGTH_SHORT) {
- String langName = langData.get("Languages%short", lang);
+ langName = langData.get("Languages%short", lang);
if (langName != null && !langName.equals(lang)) {
return adjustForUsageAndContext(CapitalizationContextUsage.LANGUAGE, langName);
}
}
- return adjustForUsageAndContext(CapitalizationContextUsage.LANGUAGE, langData.get("Languages", lang));
+ langName = langData.get("Languages", lang);
+ if (langName == null || langName.equals(lang)) {
+ // Canonicalize lang and try again, ICU-20870
+ ULocale canonLocale = ULocale.createCanonical(lang);
+ String canonLocId = canonLocale.getName();
+ if (nameLength == DisplayContext.LENGTH_SHORT) {
+ langName = langData.get("Languages%short", canonLocId);
+ if (langName != null && !langName.equals(canonLocId)) {
+ return adjustForUsageAndContext(CapitalizationContextUsage.LANGUAGE, langName);
+ }
+ }
+ langName = langData.get("Languages", canonLocId);
+ }
+ return adjustForUsageAndContext(CapitalizationContextUsage.LANGUAGE, langName);
}
@Override
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 15cfe36..f1c0ac5 100644
--- a/android_icu4j/src/main/java/android/icu/impl/LocaleIDs.java
+++ b/android_icu4j/src/main/java/android/icu/impl/LocaleIDs.java
@@ -227,11 +227,11 @@
"zxx", "zza" };
private static final String[] _replacementLanguages = {
- "id", "he", "yi", "jv", "sr", "nb",/* replacement language codes */
+ "id", "he", "yi", "jv", "sr", /* replacement language codes */
};
private static final String[] _obsoleteLanguages = {
- "in", "iw", "ji", "jw", "sh", "no", /* obsolete language codes */
+ "in", "iw", "ji", "jw", "sh", /* obsolete language codes */
};
/* This list MUST contain a three-letter code for every two-letter code in the
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 b2b4085..8cf25cc 100644
--- a/android_icu4j/src/main/java/android/icu/impl/Normalizer2Impl.java
+++ b/android_icu4j/src/main/java/android/icu/impl/Normalizer2Impl.java
@@ -26,7 +26,7 @@
* Low-level implementation of the Unicode Normalization Algorithm.
* For the data structure and details see the documentation at the end of
* C++ normalizer2impl.h and in the design doc at
- * http://site.icu-project.org/design/normalization/custom
+ * https://icu.unicode.org/design/normalization/custom
* @hide Only a subset of ICU is exposed in Android
*/
public final class Normalizer2Impl {
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 0e8a20b..1d200a9 100644
--- a/android_icu4j/src/main/java/android/icu/impl/OlsonTimeZone.java
+++ b/android_icu4j/src/main/java/android/icu/impl/OlsonTimeZone.java
@@ -282,11 +282,11 @@
*/
@Override
public void getOffsetFromLocal(long date,
- int nonExistingTimeOpt, int duplicatedTimeOpt, int[] offsets) {
+ LocalOption nonExistingTimeOpt, LocalOption duplicatedTimeOpt, int[] offsets) {
if (finalZone != null && date >= finalStartMillis) {
finalZone.getOffsetFromLocal(date, nonExistingTimeOpt, duplicatedTimeOpt, offsets);
} else {
- getHistoricalOffset(date, true, nonExistingTimeOpt, duplicatedTimeOpt, offsets);
+ getHistoricalOffset(date, true, getLocalOptionValue(nonExistingTimeOpt), getLocalOptionValue(duplicatedTimeOpt), offsets);
}
}
@@ -1298,4 +1298,4 @@
tz.isFrozen = false;
return tz;
}
-}
+}
\ No newline at end of file
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 bf3448c..5bdb37c 100644
--- a/android_icu4j/src/main/java/android/icu/impl/PatternProps.java
+++ b/android_icu4j/src/main/java/android/icu/impl/PatternProps.java
@@ -122,6 +122,29 @@
}
/**
+ * @return s except with leading and trailing SpaceChar characters removed.
+ */
+ public static String trimSpaceChar(String s) {
+ if (s.length() == 0 ||
+ (!Character.isSpaceChar(s.charAt(0)) && !Character.isSpaceChar(s.charAt(s.length() - 1)))) {
+ return s;
+ }
+ int start = 0;
+ int limit = s.length();
+ while (start < limit && Character.isSpaceChar(s.charAt(start))) {
+ ++start;
+ }
+ if (start < limit) {
+ // There is non-SpaceChar at start; we will not move limit below that,
+ // so we need not test start<limit in the loop.
+ while (isWhiteSpace(s.charAt(limit - 1))) {
+ --limit;
+ }
+ }
+ return s.substring(start, limit);
+ }
+
+ /**
* Tests whether the CharSequence contains a "pattern identifier", that is,
* whether it contains only non-Pattern_White_Space, non-Pattern_Syntax characters.
* @return true if there are no Pattern_White_Space or Pattern_Syntax characters in s.
diff --git a/android_icu4j/src/main/java/android/icu/impl/ReplaceableUCharacterIterator.java b/android_icu4j/src/main/java/android/icu/impl/ReplaceableUCharacterIterator.java
index 2c820a9..69b6858 100644
--- a/android_icu4j/src/main/java/android/icu/impl/ReplaceableUCharacterIterator.java
+++ b/android_icu4j/src/main/java/android/icu/impl/ReplaceableUCharacterIterator.java
@@ -104,7 +104,7 @@
// trail surrogate, check for surrogates
int ch = current();
- if(UTF16.isLeadSurrogate((char)ch)){
+ if(UTF16.isLeadSurrogate(ch)){
// advance the index to get the next code point
next();
// due to post increment semantics current() after next()
@@ -113,7 +113,7 @@
// current should never change the current index so back off
previous();
- if(UTF16.isTrailSurrogate((char)ch2)){
+ if(UTF16.isTrailSurrogate(ch2)){
// we found a surrogate pair
return Character.toCodePoint((char)ch, (char)ch2);
}
diff --git a/android_icu4j/src/main/java/android/icu/impl/RuleCharacterIterator.java b/android_icu4j/src/main/java/android/icu/impl/RuleCharacterIterator.java
index 7a824a3..5b7c16a 100644
--- a/android_icu4j/src/main/java/android/icu/impl/RuleCharacterIterator.java
+++ b/android_icu4j/src/main/java/android/icu/impl/RuleCharacterIterator.java
@@ -53,7 +53,7 @@
/**
* Current variable expansion, or null if none.
*/
- private char[] buf;
+ private String buf;
/**
* Position within buf[]. Meaningless if buf == null.
@@ -80,7 +80,7 @@
/**
* Bitmask option to enable parsing of escape sequences. If (options &
* PARSE_ESCAPES) != 0, then an embedded escape sequence will be expanded
- * to its value. Escapes are parsed using Utility.unescapeAt().
+ * to its value. Escapes are parsed using Utility.unescapeAndLengthAt().
*/
public static final int PARSE_ESCAPES = 2;
@@ -91,12 +91,20 @@
*/
public static final int SKIP_WHITESPACE = 4;
+ /** For use with {@link #getPos(Position)} & {@link #setPos(Position)}.
+ * @hide Only a subset of ICU is exposed in Android*/
+ public static final class Position {
+ private String buf;
+ private int bufPos;
+ private int posIndex;
+ };
+
/**
* Constructs an iterator over the given text, starting at the given
* position.
* @param text the text to be iterated
* @param sym the symbol table, or null if there is none. If sym is null,
- * then variables will not be deferenced, even if the PARSE_VARIABLES
+ * then variables will not be dereferenced, even if the PARSE_VARIABLES
* option is set.
* @param pos upon input, the index of the next character to return. If a
* variable has been dereferenced, then pos will <em>not</em> increment as
@@ -145,15 +153,17 @@
break;
}
bufPos = 0;
- buf = sym.lookup(name);
- if (buf == null) {
+ char[] chars = sym.lookup(name);
+ if (chars == null) {
+ buf = null;
throw new IllegalArgumentException(
"Undefined variable: " + name);
}
// Handle empty variable value
- if (buf.length == 0) {
+ if (chars.length == 0) {
buf = null;
}
+ buf = new String(chars);
continue;
}
@@ -163,13 +173,14 @@
}
if (c == '\\' && (options & PARSE_ESCAPES) != 0) {
- int offset[] = new int[] { 0 };
- c = Utility.unescapeAt(lookahead(), offset);
- jumpahead(offset[0]);
- isEscaped = true;
- if (c < 0) {
+ int cpAndLength = Utility.unescapeAndLengthAt(
+ getCurrentBuffer(), getCurrentBufferPos());
+ if (cpAndLength < 0) {
throw new IllegalArgumentException("Invalid escape");
}
+ c = Utility.cpFromCodePointAndLength(cpAndLength);
+ jumpahead(Utility.lengthFromCodePointAndLength(cpAndLength));
+ isEscaped = true;
}
break;
@@ -200,7 +211,7 @@
* restore this iterator's position. Usage idiom:
*
* RuleCharacterIterator iterator = ...;
- * Object pos = iterator.getPos(null); // allocate position object
+ * Position pos = iterator.getPos(null); // allocate position object
* for (;;) {
* pos = iterator.getPos(pos); // reuse position object
* int c = iterator.next(...);
@@ -214,15 +225,13 @@
* @return a position object which may be passed to setPos(),
* either `p,' or if `p' == null, a newly-allocated object
*/
- public Object getPos(Object p) {
+ public Position getPos(Position p) {
if (p == null) {
- return new Object[] {buf, new int[] {pos.getIndex(), bufPos}};
+ p = new Position();
}
- Object[] a = (Object[]) p;
- a[0] = buf;
- int[] v = (int[]) a[1];
- v[0] = pos.getIndex();
- v[1] = bufPos;
+ p.buf = buf;
+ p.bufPos = bufPos;
+ p.posIndex = pos.getIndex();
return p;
}
@@ -231,12 +240,10 @@
* returned the given object.
* @param p a position object previously returned by getPos()
*/
- public void setPos(Object p) {
- Object[] a = (Object[]) p;
- buf = (char[]) a[0];
- int[] v = (int[]) a[1];
- pos.setIndex(v[0]);
- bufPos = v[1];
+ public void setPos(Position p) {
+ buf = p.buf;
+ pos.setIndex(p.posIndex);
+ bufPos = p.bufPos;
}
/**
@@ -261,25 +268,35 @@
* Returns a string containing the remainder of the characters to be
* returned by this iterator, without any option processing. If the
* iterator is currently within a variable expansion, this will only
- * extend to the end of the variable expansion. This method is provided
- * so that iterators may interoperate with string-based APIs. The typical
- * sequence of calls is to call skipIgnored(), then call lookahead(), then
- * parse the string returned by lookahead(), then call jumpahead() to
+ * extend to the end of the variable expansion.
+ * This method, together with getCurrentBufferPos() (which replace the former lookahead()),
+ * is provided so that iterators may interoperate with string-based APIs. The typical
+ * sequence of calls is to call skipIgnored(), then call these methods, then
+ * parse that substring, then call jumpahead() to
* resynchronize the iterator.
* @return a string containing the characters to be returned by future
* calls to next()
*/
- public String lookahead() {
+ public String getCurrentBuffer() {
if (buf != null) {
- return new String(buf, bufPos, buf.length - bufPos);
+ return buf;
} else {
- return text.substring(pos.getIndex());
+ return text;
+ }
+ }
+
+ public int getCurrentBufferPos() {
+ if (buf != null) {
+ return bufPos;
+ } else {
+ return pos.getIndex();
}
}
/**
* Advances the position by the given number of 16-bit code units.
- * This is useful in conjunction with the lookahead() method.
+ * This is useful in conjunction with getCurrentBuffer()+getCurrentBufferPos()
+ * (formerly lookahead()).
* @param count the number of 16-bit code units to jump over
*/
public void jumpahead(int count) {
@@ -288,10 +305,10 @@
}
if (buf != null) {
bufPos += count;
- if (bufPos > buf.length) {
+ if (bufPos > buf.length()) {
throw new IllegalArgumentException();
}
- if (bufPos == buf.length) {
+ if (bufPos == buf.length()) {
buf = null;
}
} else {
@@ -322,7 +339,7 @@
*/
private int _current() {
if (buf != null) {
- return UTF16.charAt(buf, 0, buf.length, bufPos);
+ return UTF16.charAt(buf, bufPos);
} else {
int i = pos.getIndex();
return (i < text.length()) ? UTF16.charAt(text, i) : DONE;
@@ -336,7 +353,7 @@
private void _advance(int count) {
if (buf != null) {
bufPos += count;
- if (bufPos == buf.length) {
+ if (bufPos == buf.length()) {
buf = null;
}
} else {
diff --git a/android_icu4j/src/main/java/android/icu/impl/SimpleFilteredSentenceBreakIterator.java b/android_icu4j/src/main/java/android/icu/impl/SimpleFilteredSentenceBreakIterator.java
index c8bfae7..382003c 100644
--- a/android_icu4j/src/main/java/android/icu/impl/SimpleFilteredSentenceBreakIterator.java
+++ b/android_icu4j/src/main/java/android/icu/impl/SimpleFilteredSentenceBreakIterator.java
@@ -20,6 +20,7 @@
import android.icu.util.BytesTrie;
import android.icu.util.CharsTrie;
import android.icu.util.CharsTrieBuilder;
+import android.icu.util.ICUCloneNotSupportedException;
import android.icu.util.StringTrieBuilder;
import android.icu.util.ULocale;
@@ -74,8 +75,6 @@
backwardsTrie.reset();
int uch;
-
-
// Assume a space is following the '.' (so we handle the case: "Mr. /Brown")
if ((uch = text.previousCodePoint()) == ' ') { // TODO: skip a class of chars here??
// TODO only do this the 1st time?
@@ -83,20 +82,17 @@
uch = text.nextCodePoint();
}
- BytesTrie.Result r = BytesTrie.Result.INTERMEDIATE_VALUE;
-
- while ((uch = text.previousCodePoint()) != UCharacterIterator.DONE && // more to consume backwards and..
- ((r = backwardsTrie.nextForCodePoint(uch)).hasNext())) {// more in the trie
+ while ((uch = text.previousCodePoint()) >= 0) { // more to consume backwards
+ BytesTrie.Result r = backwardsTrie.nextForCodePoint(uch);
if (r.hasValue()) { // remember the best match so far
bestPosn = text.getIndex();
bestValue = backwardsTrie.getValue();
}
+ if (!r.hasNext()) {
+ break;
+ }
}
-
- if (r.matches()) { // exact match?
- bestValue = backwardsTrie.getValue();
- bestPosn = text.getIndex();
- }
+ backwardsTrie.reset(); // for equals() & hashCode()
if (bestPosn >= 0) {
if (bestValue == Builder.MATCH) { // exact match!
@@ -112,6 +108,7 @@
while ((uch = text.nextCodePoint()) != BreakIterator.DONE
&& ((rfwd = forwardsPartialTrie.nextForCodePoint(uch)).hasNext())) {
}
+ forwardsPartialTrie.reset(); // for equals() & hashCode()
if (rfwd.matches()) {
// Exception here
return true;
@@ -188,18 +185,39 @@
if (getClass() != obj.getClass())
return false;
SimpleFilteredSentenceBreakIterator other = (SimpleFilteredSentenceBreakIterator) obj;
- return delegate.equals(other.delegate) && text.equals(other.text) && backwardsTrie.equals(other.backwardsTrie)
+ // TODO(ICU-21575): CharsTrie.equals() is not defined.
+ // Should compare the underlying data, and can then stop resetting after iteration.
+ return delegate.equals(other.delegate) && text.equals(other.text)
+ && backwardsTrie.equals(other.backwardsTrie)
&& forwardsPartialTrie.equals(other.forwardsPartialTrie);
}
@Override
public int hashCode() {
- return (forwardsPartialTrie.hashCode() * 39) + (backwardsTrie.hashCode() * 11) + delegate.hashCode();
+ // TODO(ICU-21575): CharsTrie.hashCode() is not defined.
+ return (forwardsPartialTrie.hashCode() * 39) + (backwardsTrie.hashCode() * 11)
+ + delegate.hashCode();
}
@Override
public Object clone() {
SimpleFilteredSentenceBreakIterator other = (SimpleFilteredSentenceBreakIterator) super.clone();
+ try {
+ if (delegate != null) {
+ other.delegate = (BreakIterator) delegate.clone();
+ }
+ if (text != null) {
+ other.text = (UCharacterIterator) text.clone();
+ }
+ if (backwardsTrie != null) {
+ other.backwardsTrie = backwardsTrie.clone();
+ }
+ if (forwardsPartialTrie != null) {
+ other.forwardsPartialTrie = forwardsPartialTrie.clone();
+ }
+ } catch (CloneNotSupportedException e) {
+ throw new ICUCloneNotSupportedException(e);
+ }
return other;
}
@@ -278,7 +296,7 @@
/**
* filter set to store all exceptions
*/
- private HashSet<CharSequence> filterSet = new HashSet<CharSequence>();
+ private HashSet<CharSequence> filterSet = new HashSet<>();
static final int PARTIAL = (1 << 0); // < partial - need to run through forward trie
static final int MATCH = (1 << 1); // < exact match - skip this one.
diff --git a/android_icu4j/src/main/java/android/icu/impl/StandardPlural.java b/android_icu4j/src/main/java/android/icu/impl/StandardPlural.java
index f84c24b..04f40d2 100644
--- a/android_icu4j/src/main/java/android/icu/impl/StandardPlural.java
+++ b/android_icu4j/src/main/java/android/icu/impl/StandardPlural.java
@@ -24,7 +24,9 @@
TWO("two"),
FEW("few"),
MANY("many"),
- OTHER("other");
+ OTHER("other"),
+ EQ_0("=0"),
+ EQ_1("=1");
/**
* Numeric index of OTHER, same as OTHER.ordinal().
@@ -62,6 +64,20 @@
*/
public static final StandardPlural orNullFromString(CharSequence keyword) {
switch (keyword.length()) {
+ case 1:
+ if (keyword.charAt(0) == '0') {
+ return EQ_0;
+ } else if (keyword.charAt(0) == '1') {
+ return EQ_1;
+ }
+ break;
+ case 2:
+ if ("=0".contentEquals(keyword)) {
+ return EQ_0;
+ } else if ("=1".contentEquals(keyword)) {
+ return EQ_1;
+ }
+ break;
case 3:
if ("one".contentEquals(keyword)) {
return ONE;
diff --git a/android_icu4j/src/main/java/android/icu/impl/StaticUnicodeSets.java b/android_icu4j/src/main/java/android/icu/impl/StaticUnicodeSets.java
index 028339c..56e04f3 100644
--- a/android_icu4j/src/main/java/android/icu/impl/StaticUnicodeSets.java
+++ b/android_icu4j/src/main/java/android/icu/impl/StaticUnicodeSets.java
@@ -3,8 +3,6 @@
// License & terms of use: http://www.unicode.org/copyright.html
package android.icu.impl;
-import static android.icu.impl.number.parse.ParsingUtils.safeContains;
-
import java.util.EnumMap;
import java.util.Map;
@@ -100,7 +98,7 @@
* @return key1 if the set contains str, or COUNT if not.
*/
public static Key chooseFrom(String str, Key key1) {
- return safeContains(get(key1), str) ? key1 : null;
+ return get(key1).contains(str) ? key1 : null;
}
/**
@@ -118,7 +116,7 @@
* contains str.
*/
public static Key chooseFrom(String str, Key key1, Key key2) {
- return safeContains(get(key1), str) ? key1 : chooseFrom(str, key2);
+ return get(key1).contains(str) ? key1 : chooseFrom(str, key2);
}
/**
diff --git a/android_icu4j/src/main/java/android/icu/impl/StringSegment.java b/android_icu4j/src/main/java/android/icu/impl/StringSegment.java
index 5704c98..b27cbb1 100644
--- a/android_icu4j/src/main/java/android/icu/impl/StringSegment.java
+++ b/android_icu4j/src/main/java/android/icu/impl/StringSegment.java
@@ -204,23 +204,13 @@
}
/**
- * Equals any CharSequence with the same chars as this segment.
+ * Returns true if this segment contains the same characters as the other CharSequence.
*
- * <p>
- * This method does not perform case folding; if you want case-insensitive equality, use
+ * <p>This method does not perform case folding; if you want case-insensitive equality, use
* {@link #getCommonPrefixLength}.
*/
- @Override
- public boolean equals(Object other) {
- if (!(other instanceof CharSequence))
- return false;
- return Utility.charSequenceEquals(this, (CharSequence) other);
- }
-
- /** Returns a hash code equivalent to calling .toString().hashCode() */
- @Override
- public int hashCode() {
- return Utility.charSequenceHashCode(this);
+ public boolean contentEquals(CharSequence other) {
+ return Utility.charSequenceEquals(this, other);
}
/** Returns a string representation useful for debugging. */
diff --git a/android_icu4j/src/main/java/android/icu/impl/TextTrieMap.java b/android_icu4j/src/main/java/android/icu/impl/TextTrieMap.java
index 0c7df93..3149681 100644
--- a/android_icu4j/src/main/java/android/icu/impl/TextTrieMap.java
+++ b/android_icu4j/src/main/java/android/icu/impl/TextTrieMap.java
@@ -187,7 +187,7 @@
*/
@Override
public void remove() {
- throw new UnsupportedOperationException("remove() not supproted");
+ throw new UnsupportedOperationException("remove() not supported");
}
public int nextIndex() {
diff --git a/android_icu4j/src/main/java/android/icu/impl/TimeZoneAdapter.java b/android_icu4j/src/main/java/android/icu/impl/TimeZoneAdapter.java
index 37ae2b5..e265ae6 100644
--- a/android_icu4j/src/main/java/android/icu/impl/TimeZoneAdapter.java
+++ b/android_icu4j/src/main/java/android/icu/impl/TimeZoneAdapter.java
@@ -30,7 +30,6 @@
* @author Alan Liu
* @hide Only a subset of ICU is exposed in Android
*/
-@libcore.api.CorePlatformApi
public class TimeZoneAdapter extends java.util.TimeZone {
// Generated by serialver from JDK 1.4.1_01
@@ -46,7 +45,6 @@
* Given a java.util.TimeZone, wrap it in the appropriate adapter
* subclass of android.icu.util.TimeZone and return the adapter.
*/
- @libcore.api.CorePlatformApi
public static java.util.TimeZone wrap(android.icu.util.TimeZone tz) {
return new TimeZoneAdapter(tz);
}
diff --git a/android_icu4j/src/main/java/android/icu/impl/TimeZoneGenericNames.java b/android_icu4j/src/main/java/android/icu/impl/TimeZoneGenericNames.java
index 17db218..39d7af8 100644
--- a/android_icu4j/src/main/java/android/icu/impl/TimeZoneGenericNames.java
+++ b/android_icu4j/src/main/java/android/icu/impl/TimeZoneGenericNames.java
@@ -466,7 +466,7 @@
/**
* Private method returning LocaleDisplayNames instance for the locale of this
* instance. Because LocaleDisplayNames is only used for generic
- * location formant and partial location format, the LocaleDisplayNames
+ * location format and partial location format, the LocaleDisplayNames
* is instantiated lazily.
*
* @return the instance of LocaleDisplayNames for the locale of this object.
diff --git a/android_icu4j/src/main/java/android/icu/impl/TrieIterator.java b/android_icu4j/src/main/java/android/icu/impl/TrieIterator.java
index 1bf2090..86736f4 100644
--- a/android_icu4j/src/main/java/android/icu/impl/TrieIterator.java
+++ b/android_icu4j/src/main/java/android/icu/impl/TrieIterator.java
@@ -50,7 +50,7 @@
* <p>There are basically 3 usage scenarios for porting:</p>
* <p>1) UTrieEnumValue is the only implemented callback then just implement a
* subclass of TrieIterator and override the extract(int) method. The
- * extract(int) method is analogus to UTrieEnumValue callback.
+ * extract(int) method is analogous to UTrieEnumValue callback.
* </p>
* <p>2) UTrieEnumValue and UTrieEnumRange both are implemented then implement
* a subclass of TrieIterator, override the extract method and iterate, e.g
@@ -115,7 +115,7 @@
* <p>Returns true if we are not at the end of the iteration, false
* otherwise.</p>
* <p>The next set of codepoints with the same value type will be
- * calculated during this call and returned in the arguement element.</p>
+ * calculated during this call and returned in the argument element.</p>
* @param element return result
* @return true if we are not at the end of the iteration, false otherwise.
* @exception NoSuchElementException - if no more elements exist.
diff --git a/android_icu4j/src/main/java/android/icu/impl/UCaseProps.java b/android_icu4j/src/main/java/android/icu/impl/UCaseProps.java
index 8301e1c..1c6875b 100644
--- a/android_icu4j/src/main/java/android/icu/impl/UCaseProps.java
+++ b/android_icu4j/src/main/java/android/icu/impl/UCaseProps.java
@@ -404,7 +404,7 @@
if(max==0 || unfold[unfoldOffset]==0) {
return 0; /* equal to length of both strings */
} else {
- return -max; /* return lengh difference */
+ return -max; /* return length difference */
}
}
@@ -569,7 +569,7 @@
* - In [CoreProps], C has one of the properties Uppercase, or Lowercase
* - Given D = NFD(C), then it is not the case that:
* D = UCD_lower(D) = UCD_upper(D) = UCD_title(D)
- * (This third criterium does not add any characters to the list
+ * (This third criterion does not add any characters to the list
* for Unicode 3.2. Ignored.)
*
* D2. A character C is defined to be case-ignorable
diff --git a/android_icu4j/src/main/java/android/icu/impl/UCharacterName.java b/android_icu4j/src/main/java/android/icu/impl/UCharacterName.java
index 60aa8d1..a02daa1 100644
--- a/android_icu4j/src/main/java/android/icu/impl/UCharacterName.java
+++ b/android_icu4j/src/main/java/android/icu/impl/UCharacterName.java
@@ -23,8 +23,8 @@
/**
* Internal class to manage character names.
* Since data for names are stored
-* in an array of char, by default indexes used in this class is refering to
-* a 2 byte count, unless otherwise stated. Cases where the index is refering
+* in an array of char, by default indexes used in this class is referring to
+* a 2 byte count, unless otherwise stated. Cases where the index is referring
* to a byte count, the index is halved and depending on whether the index is
* even or odd, the MSB or LSB of the result char at the halved index is
* returned. For indexes to an array of int, the index is multiplied by 2,
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 dafeb79..7acb688 100644
--- a/android_icu4j/src/main/java/android/icu/impl/UCharacterProperty.java
+++ b/android_icu4j/src/main/java/android/icu/impl/UCharacterProperty.java
@@ -109,8 +109,9 @@
public static final int SRC_INPC=12;
public static final int SRC_INSC=13;
public static final int SRC_VO=14;
+ public static final int SRC_EMOJI=15;
/** One more than the highest UPropertySource (SRC_) constant. */
- public static final int SRC_COUNT=15;
+ public static final int SRC_COUNT=16;
private static final class LayoutProps {
private static final class IsAcceptable implements ICUBinary.Authenticate {
@@ -353,6 +354,18 @@
}
}
+ private class EmojiBinaryProperty extends BinaryProperty {
+ int which;
+ EmojiBinaryProperty(int which) {
+ super(SRC_EMOJI);
+ this.which=which;
+ }
+ @Override
+ boolean contains(int c) {
+ return EmojiProps.INSTANCE.hasBinaryProperty(c, which);
+ }
+ }
+
private class NormInertBinaryProperty extends BinaryProperty { // UCHAR_NF*_INERT properties
int which;
NormInertBinaryProperty(int source, int which) {
@@ -535,11 +548,11 @@
return !Normalizer2Impl.UTF16Plus.equal(dest, src);
}
},
- new BinaryProperty(2, 1<<PROPS_2_EMOJI),
- new BinaryProperty(2, 1<<PROPS_2_EMOJI_PRESENTATION),
- new BinaryProperty(2, 1<<PROPS_2_EMOJI_MODIFIER),
- new BinaryProperty(2, 1<<PROPS_2_EMOJI_MODIFIER_BASE),
- new BinaryProperty(2, 1<<PROPS_2_EMOJI_COMPONENT),
+ new EmojiBinaryProperty(UProperty.EMOJI),
+ new EmojiBinaryProperty(UProperty.EMOJI_PRESENTATION),
+ new EmojiBinaryProperty(UProperty.EMOJI_MODIFIER),
+ new EmojiBinaryProperty(UProperty.EMOJI_MODIFIER_BASE),
+ new EmojiBinaryProperty(UProperty.EMOJI_COMPONENT),
new BinaryProperty(SRC_PROPSVEC) { // REGIONAL_INDICATOR
// Property starts are a subset of lb=RI etc.
@Override
@@ -548,7 +561,14 @@
}
},
new BinaryProperty(1, 1<<PREPENDED_CONCATENATION_MARK),
- new BinaryProperty(2, 1<<PROPS_2_EXTENDED_PICTOGRAPHIC),
+ new EmojiBinaryProperty(UProperty.EXTENDED_PICTOGRAPHIC),
+ new EmojiBinaryProperty(UProperty.BASIC_EMOJI),
+ new EmojiBinaryProperty(UProperty.EMOJI_KEYCAP_SEQUENCE),
+ new EmojiBinaryProperty(UProperty.RGI_EMOJI_MODIFIER_SEQUENCE),
+ new EmojiBinaryProperty(UProperty.RGI_EMOJI_FLAG_SEQUENCE),
+ new EmojiBinaryProperty(UProperty.RGI_EMOJI_TAG_SEQUENCE),
+ new EmojiBinaryProperty(UProperty.RGI_EMOJI_ZWJ_SEQUENCE),
+ new EmojiBinaryProperty(UProperty.RGI_EMOJI),
};
public boolean hasBinaryProperty(int c, int which) {
@@ -1366,19 +1386,20 @@
/*
* Properties in vector word 2
* Bits
- * 31..26 http://www.unicode.org/reports/tr51/#Emoji_Properties
+ * 31..26 unused since ICU 70 added uemoji.icu;
+ * in ICU 57..69 stored emoji properties
* 25..20 Line Break
* 19..15 Sentence Break
* 14..10 Word Break
* 9.. 5 Grapheme Cluster Break
* 4.. 0 Decomposition Type
*/
- private static final int PROPS_2_EXTENDED_PICTOGRAPHIC=26;
- private static final int PROPS_2_EMOJI_COMPONENT = 27;
- private static final int PROPS_2_EMOJI = 28;
- private static final int PROPS_2_EMOJI_PRESENTATION = 29;
- private static final int PROPS_2_EMOJI_MODIFIER = 30;
- private static final int PROPS_2_EMOJI_MODIFIER_BASE = 31;
+ //ivate static final int PROPS_2_EXTENDED_PICTOGRAPHIC=26; // ICU 62..69
+ //ivate static final int PROPS_2_EMOJI_COMPONENT = 27; // ICU 60..69
+ //ivate static final int PROPS_2_EMOJI = 28; // ICU 57..69
+ //ivate static final int PROPS_2_EMOJI_PRESENTATION = 29; // ICU 57..69
+ //ivate static final int PROPS_2_EMOJI_MODIFIER = 30; // ICU 57..69
+ //ivate static final int PROPS_2_EMOJI_MODIFIER_BASE = 31; // ICU 57..69
private static final int LB_MASK = 0x03f00000;
private static final int LB_SHIFT = 20;
diff --git a/android_icu4j/src/main/java/android/icu/impl/UCharacterUtility.java b/android_icu4j/src/main/java/android/icu/impl/UCharacterUtility.java
index abf8b27..63a8c68 100644
--- a/android_icu4j/src/main/java/android/icu/impl/UCharacterUtility.java
+++ b/android_icu4j/src/main/java/android/icu/impl/UCharacterUtility.java
@@ -187,7 +187,7 @@
///CLOVER:OFF
/**
- * private constructor to avoid initialisation
+ * private constructor to avoid initialization
*/
private UCharacterUtility()
{
diff --git a/android_icu4j/src/main/java/android/icu/impl/UnicodeRegex.java b/android_icu4j/src/main/java/android/icu/impl/UnicodeRegex.java
index e0ecad7..e1ec959 100644
--- a/android_icu4j/src/main/java/android/icu/impl/UnicodeRegex.java
+++ b/android_icu4j/src/main/java/android/icu/impl/UnicodeRegex.java
@@ -42,6 +42,8 @@
* @hide Only a subset of ICU is exposed in Android
*/
public class UnicodeRegex implements Cloneable, Freezable<UnicodeRegex>, StringTransform {
+ private static final Pattern SUPP_ESCAPE = Pattern.compile("\\\\U00([0-9a-fA-F]{6})");
+
// Note: we don't currently have any state, but intend to in the future,
// particularly for the regex style supported.
@@ -77,7 +79,7 @@
* <p>Not thread-safe; create a separate copy for different threads.
* <p>In the future, we may extend this to support other regex packages.
*
- * @regex A modified Java regex pattern, as in the input to
+ * @param regex A modified Java regex pattern, as in the input to
* Pattern.compile(), except that all "character classes" are
* processed as if they were UnicodeSet patterns. Example:
* "abc[:bc=N:]. See UnicodeSet for the differences in syntax.
@@ -210,7 +212,7 @@
*/
public String compileBnf(List<String> lines) {
Map<String, String> variables = getVariables(lines);
- Set<String> unused = new LinkedHashSet<String>(variables.keySet());
+ Set<String> unused = new LinkedHashSet<>(variables.keySet());
// brute force replacement; do twice to allow for different order
// later on can optimize
for (int i = 0; i < 2; ++i) {
@@ -345,7 +347,12 @@
pos.setIndex(i);
UnicodeSet x = temp.clear().applyPattern(regex, pos, symbolTable, 0);
x.complement().complement(); // hack to fix toPattern
- result.append(x.toPattern(false));
+ String pattern = x.toPattern(false);
+ // Escaping of supplementary code points differs between ICU UnicodeSet and Java regex.
+ if (pattern.contains("\\U")) {
+ pattern = SUPP_ESCAPE.matcher(pattern).replaceAll("\\\\x{$1}");
+ }
+ result.append(pattern);
i = pos.getIndex() - 1; // allow for the loop increment
return i;
} catch (Exception e) {
@@ -372,7 +379,7 @@
};
private Map<String, String> getVariables(List<String> lines) {
- Map<String, String> variables = new TreeMap<String, String>(LongestFirst);
+ Map<String, String> variables = new TreeMap<>(LongestFirst);
String variable = null;
StringBuffer definition = new StringBuffer();
int count = 0;
diff --git a/android_icu4j/src/main/java/android/icu/impl/UnicodeSetStringSpan.java b/android_icu4j/src/main/java/android/icu/impl/UnicodeSetStringSpan.java
index c3ed68f..ee71171 100644
--- a/android_icu4j/src/main/java/android/icu/impl/UnicodeSetStringSpan.java
+++ b/android_icu4j/src/main/java/android/icu/impl/UnicodeSetStringSpan.java
@@ -114,9 +114,15 @@
int i, spanLength;
int maxLength16 = 0;
someRelevant = false;
- for (i = 0; i < stringsLength; ++i) {
+ for (i = 0; i < stringsLength;) {
String string = strings.get(i);
int length16 = string.length();
+ if (length16 == 0) {
+ // Remove the empty string.
+ strings.remove(i);
+ --stringsLength;
+ continue;
+ }
spanLength = spanSet.span(string, SpanCondition.CONTAINED);
if (spanLength < length16) { // Relevant string.
someRelevant = true;
@@ -124,6 +130,7 @@
if (/* (0 != (which & UTF16)) && */ length16 > maxLength16) {
maxLength16 = length16;
}
+ ++i;
}
this.maxLength16 = maxLength16;
if (!someRelevant && (which & WITH_COUNT) == 0) {
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 b10e705..110663d 100644
--- a/android_icu4j/src/main/java/android/icu/impl/Utility.java
+++ b/android_icu4j/src/main/java/android/icu/impl/Utility.java
@@ -781,35 +781,59 @@
/*v*/ 0x76, 0x0b
};
+ /* Convert one octal digit to a numeric value 0..7, or -1 on failure */
+ private static final int _digit8(int c) {
+ if (c >= '0' && c <= '7') {
+ return c - '0';
+ }
+ return -1;
+ }
+
+ /* Convert one hex digit to a numeric value 0..F, or -1 on failure */
+ private static final int _digit16(int c) {
+ if (c >= '0' && c <= '9') {
+ return c - '0';
+ }
+ if (c >= 'A' && c <= 'F') {
+ return c - ('A' - 10);
+ }
+ if (c >= 'a' && c <= 'f') {
+ return c - ('a' - 10);
+ }
+ return -1;
+ }
+
/**
- * Convert an escape to a 32-bit code point value. We attempt
+ * Converts an escape to a code point value. We attempt
* to parallel the icu4c unescapeAt() function.
- * @param offset16 an array containing offset to the character
- * <em>after</em> the backslash. Upon return offset16[0] will
- * be updated to point after the escape sequence.
- * @return character value from 0 to 10FFFF, or -1 on error.
+ * This function returns an integer with
+ * both the code point (bits 28..8) and the length of the escape sequence (bits 7..0).
+ * offset+length is the index after the escape sequence.
+ *
+ * @param offset the offset to the character <em>after</em> the backslash.
+ * @return the code point and length, or -1 on error.
*/
- public static int unescapeAt(String s, int[] offset16) {
- int c;
+ public static int unescapeAndLengthAt(CharSequence s, int offset) {
+ return unescapeAndLengthAt(s, offset, s.length());
+ }
+
+ private static int unescapeAndLengthAt(CharSequence s, int offset, int length) {
int result = 0;
int n = 0;
int minDig = 0;
int maxDig = 0;
int bitsPerDigit = 4;
int dig;
- int i;
boolean braces = false;
/* Check that offset is in range */
- int offset = offset16[0];
- int length = s.length();
if (offset < 0 || offset >= length) {
return -1;
}
+ int start = offset;
/* Fetch first UChar after '\\' */
- c = Character.codePointAt(s, offset);
- offset += UTF16.getCharCount(c);
+ int c = s.charAt(offset++);
/* Convert hexadecimal and octal escapes */
switch (c) {
@@ -821,7 +845,7 @@
break;
case 'x':
minDig = 1;
- if (offset < length && UTF16.charAt(s, offset) == 0x7B /*{*/) {
+ if (offset < length && s.charAt(offset) == '{') {
++offset;
braces = true;
maxDig = 8;
@@ -830,7 +854,7 @@
}
break;
default:
- dig = UCharacter.digit(c, 8);
+ dig = _digit8(c);
if (dig >= 0) {
minDig = 1;
maxDig = 3;
@@ -842,20 +866,20 @@
}
if (minDig != 0) {
while (offset < length && n < maxDig) {
- c = UTF16.charAt(s, offset);
- dig = UCharacter.digit(c, (bitsPerDigit == 3) ? 8 : 16);
+ c = s.charAt(offset);
+ dig = (bitsPerDigit == 3) ? _digit8(c) : _digit16(c);
if (dig < 0) {
break;
}
result = (result << bitsPerDigit) | dig;
- offset += UTF16.getCharCount(c);
+ ++offset;
++n;
}
if (n < minDig) {
return -1;
}
if (braces) {
- if (c != 0x7D /*}*/) {
+ if (c != '}') {
return -1;
}
++offset;
@@ -867,29 +891,35 @@
// if there is a trail surrogate after it, either as an
// escape or as a literal. If so, join them up into a
// supplementary.
- if (offset < length &&
- UTF16.isLeadSurrogate((char) result)) {
+ if (offset < length && UTF16.isLeadSurrogate(result)) {
int ahead = offset+1;
- c = s.charAt(offset); // [sic] get 16-bit code unit
+ c = s.charAt(offset);
if (c == '\\' && ahead < length) {
- int o[] = new int[] { ahead };
- c = unescapeAt(s, o);
- ahead = o[0];
+ // Calling ourselves recursively may cause a stack overflow if
+ // we have repeated escaped lead surrogates.
+ // Limit the length to 11 ("x{0000DFFF}") after ahead.
+ int tailLimit = ahead + 11;
+ if (tailLimit > length) {
+ tailLimit = length;
+ }
+ int cpAndLength = unescapeAndLengthAt(s, ahead, tailLimit);
+ if (cpAndLength >= 0) {
+ c = cpAndLength >> 8;
+ ahead += cpAndLength & 0xff;
+ }
}
- if (UTF16.isTrailSurrogate((char) c)) {
+ if (UTF16.isTrailSurrogate(c)) {
offset = ahead;
- result = Character.toCodePoint((char) result, (char) c);
+ result = UCharacter.toCodePoint(result, c);
}
}
- offset16[0] = offset;
- return result;
+ return codePointAndLength(result, start, offset);
}
/* Convert C-style escapes in table */
- for (i=0; i<UNESCAPE_MAP.length; i+=2) {
+ for (int i=0; i<UNESCAPE_MAP.length; i+=2) {
if (c == UNESCAPE_MAP[i]) {
- offset16[0] = offset;
- return UNESCAPE_MAP[i+1];
+ return codePointAndLength(UNESCAPE_MAP[i+1], start, offset);
} else if (c < UNESCAPE_MAP[i]) {
break;
}
@@ -897,65 +927,103 @@
/* Map \cX to control-X: X & 0x1F */
if (c == 'c' && offset < length) {
- c = UTF16.charAt(s, offset);
- offset16[0] = offset + UTF16.getCharCount(c);
- return 0x1F & c;
+ c = Character.codePointAt(s, offset);
+ return codePointAndLength(c & 0x1F, start, offset + Character.charCount(c));
}
/* If no special forms are recognized, then consider
- * the backslash to generically escape the next character. */
- offset16[0] = offset;
- return c;
+ * the backslash to generically escape the next character.
+ * Deal with surrogate pairs. */
+ if (UTF16.isLeadSurrogate(c) && offset < length) {
+ int c2 = s.charAt(offset);
+ if (UTF16.isTrailSurrogate(c2)) {
+ ++offset;
+ c = UCharacter.toCodePoint(c, c2);
+ }
+ }
+ return codePointAndLength(c, start, offset);
+ }
+
+ private static int codePointAndLength(int c, int length) {
+ assert 0 <= c && c <= 0x10ffff;
+ assert 0 <= length && length <= 0xff;
+ return c << 8 | length;
+ }
+
+ private static int codePointAndLength(int c, int start, int limit) {
+ return codePointAndLength(c, limit - start);
+ }
+
+ public static int cpFromCodePointAndLength(int cpAndLength) {
+ assert cpAndLength >= 0;
+ return cpAndLength >> 8;
+ }
+
+ public static int lengthFromCodePointAndLength(int cpAndLength) {
+ assert cpAndLength >= 0;
+ return cpAndLength & 0xff;
}
/**
- * Convert all escapes in a given string using unescapeAt().
+ * Convert all escapes in a given string using unescapeAndLengthAt().
* @exception IllegalArgumentException if an invalid escape is
* seen.
*/
- public static String unescape(String s) {
- StringBuilder buf = new StringBuilder();
- int[] pos = new int[1];
+ public static String unescape(CharSequence s) {
+ StringBuilder buf = null;
for (int i=0; i<s.length(); ) {
char c = s.charAt(i++);
if (c == '\\') {
- pos[0] = i;
- int e = unescapeAt(s, pos);
- if (e < 0) {
- throw new IllegalArgumentException("Invalid escape sequence " +
- s.substring(i-1, Math.min(i+8, s.length())));
+ if (buf == null) {
+ buf = new StringBuilder(s.length()).append(s, 0, i - 1);
}
- buf.appendCodePoint(e);
- i = pos[0];
- } else {
+ int cpAndLength = unescapeAndLengthAt(s, i);
+ if (cpAndLength < 0) {
+ throw new IllegalArgumentException("Invalid escape sequence " +
+ s.subSequence(i-1, Math.min(i+9, s.length())));
+ }
+ buf.appendCodePoint(cpAndLength >> 8);
+ i += cpAndLength & 0xff;
+ } else if (buf != null) {
+ // We could optimize this further by appending whole substrings between escapes.
buf.append(c);
}
}
+ if (buf == null) {
+ // No escapes in s.
+ return s.toString();
+ }
return buf.toString();
}
/**
- * Convert all escapes in a given string using unescapeAt().
+ * Convert all escapes in a given string using unescapeAndLengthAt().
* Leave invalid escape sequences unchanged.
*/
- public static String unescapeLeniently(String s) {
- StringBuilder buf = new StringBuilder();
- int[] pos = new int[1];
+ public static String unescapeLeniently(CharSequence s) {
+ StringBuilder buf = null;
for (int i=0; i<s.length(); ) {
char c = s.charAt(i++);
if (c == '\\') {
- pos[0] = i;
- int e = unescapeAt(s, pos);
- if (e < 0) {
+ if (buf == null) {
+ buf = new StringBuilder(s.length()).append(s, 0, i - 1);
+ }
+ int cpAndLength = unescapeAndLengthAt(s, i);
+ if (cpAndLength < 0) {
buf.append(c);
} else {
- buf.appendCodePoint(e);
- i = pos[0];
+ buf.appendCodePoint(cpAndLength >> 8);
+ i += cpAndLength & 0xff;
}
- } else {
+ } else if (buf != null) {
+ // We could optimize this further by appending whole substrings between escapes.
buf.append(c);
}
}
+ if (buf == null) {
+ // No escapes in s.
+ return s.toString();
+ }
return buf.toString();
}
@@ -1049,7 +1117,7 @@
* this character are not included in the output
* @param output an array to receive the substrings between
* instances of divider. It must be large enough on entry to
- * accomodate all output. Adjacent instances of the divider
+ * accommodate all output. Adjacent instances of the divider
* character will place empty strings into output. Before
* returning, output is padded out with empty strings.
*/
@@ -1315,7 +1383,7 @@
* position. Return the identifier, or null if there is no
* identifier.
* @param str the string to parse
- * @param pos INPUT-OUPUT parameter. On INPUT, pos[0] is the
+ * @param pos INPUT-OUTPUT parameter. On INPUT, pos[0] is the
* first character to examine. It must be less than str.length(),
* and it must not point to a whitespace character. That is, must
* have pos[0] < str.length(). On
@@ -1472,34 +1540,65 @@
}
/**
- * Escape unprintable characters using <backslash>uxxxx notation
+ * @return true for control codes and for surrogate and noncharacter code points
+ */
+ public static boolean shouldAlwaysBeEscaped(int c) {
+ if (c < 0x20) {
+ return true; // C0 control codes
+ } else if (c <= 0x7e) {
+ return false; // printable ASCII
+ } else if (c <= 0x9f) {
+ return true; // C1 control codes
+ } else if (c < 0xd800) {
+ return false; // most of the BMP
+ } else if (c <= 0xdfff || (0xfdd0 <= c && c <= 0xfdef) || (c & 0xfffe) == 0xfffe) {
+ return true; // surrogate or noncharacter code points
+ } else if (c <= 0x10ffff) {
+ return false; // all else
+ } else {
+ return true; // not a code point
+ }
+ }
+
+ /**
+ * 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.
*/
public static <T extends Appendable> boolean escapeUnprintable(T result, int c) {
+ if (isUnprintable(c)) {
+ escape(result, c);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Escapes one code point using <backslash>uxxxx notation
+ * for U+0000 to U+FFFF and <backslash>Uxxxxxxxx for U+10000 and above.
+ * @return result
+ */
+ public static <T extends Appendable> T escape(T result, int c) {
try {
- if (isUnprintable(c)) {
- result.append('\\');
- if ((c & ~0xFFFF) != 0) {
- result.append('U');
- result.append(DIGITS[0xF&(c>>28)]);
- result.append(DIGITS[0xF&(c>>24)]);
- result.append(DIGITS[0xF&(c>>20)]);
- result.append(DIGITS[0xF&(c>>16)]);
- } else {
- result.append('u');
- }
- result.append(DIGITS[0xF&(c>>12)]);
- result.append(DIGITS[0xF&(c>>8)]);
- result.append(DIGITS[0xF&(c>>4)]);
- result.append(DIGITS[0xF&c]);
- return true;
+ result.append('\\');
+ if ((c & ~0xFFFF) != 0) {
+ result.append('U');
+ result.append(DIGITS[0xF&(c>>28)]);
+ result.append(DIGITS[0xF&(c>>24)]);
+ result.append(DIGITS[0xF&(c>>20)]);
+ result.append(DIGITS[0xF&(c>>16)]);
+ } else {
+ result.append('u');
}
- return false;
+ result.append(DIGITS[0xF&(c>>12)]);
+ result.append(DIGITS[0xF&(c>>8)]);
+ result.append(DIGITS[0xF&(c>>4)]);
+ result.append(DIGITS[0xF&c]);
+ return result;
} catch (IOException e) {
- throw new IllegalIcuArgumentException(e);
+ throw new ICUUncheckedIOException(e);
}
}
diff --git a/android_icu4j/src/main/java/android/icu/text/BurmeseBreakEngine.java b/android_icu4j/src/main/java/android/icu/impl/breakiter/BurmeseBreakEngine.java
similarity index 87%
rename from android_icu4j/src/main/java/android/icu/text/BurmeseBreakEngine.java
rename to android_icu4j/src/main/java/android/icu/impl/breakiter/BurmeseBreakEngine.java
index f52f7e8..6b0bfbc 100644
--- a/android_icu4j/src/main/java/android/icu/text/BurmeseBreakEngine.java
+++ b/android_icu4j/src/main/java/android/icu/impl/breakiter/BurmeseBreakEngine.java
@@ -7,7 +7,7 @@
* others. All Rights Reserved. *
*******************************************************************************
*/
-package android.icu.text;
+package android.icu.impl.breakiter;
import java.io.IOException;
import java.text.CharacterIterator;
@@ -15,8 +15,13 @@
import android.icu.lang.UCharacter;
import android.icu.lang.UProperty;
import android.icu.lang.UScript;
+import android.icu.text.UnicodeSet;
-class BurmeseBreakEngine extends DictionaryBreakEngine {
+
+/**
+ * @hide Only a subset of ICU is exposed in Android
+ */
+public class BurmeseBreakEngine extends DictionaryBreakEngine {
// Constants for BurmeseBreakIterator
// How many words in a row are "good enough"?
@@ -30,24 +35,16 @@
private static final byte BURMESE_MIN_WORD = 2;
private DictionaryMatcher fDictionary;
- private static UnicodeSet fBurmeseWordSet;
- private static UnicodeSet fEndWordSet;
- private static UnicodeSet fBeginWordSet;
- private static UnicodeSet fMarkSet;
+ private UnicodeSet fEndWordSet;
+ private UnicodeSet fBeginWordSet;
+ private UnicodeSet fMarkSet;
- static {
+ public BurmeseBreakEngine() throws IOException {
// Initialize UnicodeSets
- fBurmeseWordSet = new UnicodeSet();
- fMarkSet = new UnicodeSet();
- fBeginWordSet = new UnicodeSet();
-
- fBurmeseWordSet.applyPattern("[[:Mymr:]&[:LineBreak=SA:]]");
- fBurmeseWordSet.compact();
-
- fMarkSet.applyPattern("[[:Mymr:]&[:LineBreak=SA:]&[:M:]]");
+ fBeginWordSet = new UnicodeSet(0x1000, 0x102A); // basic consonants and independent vowels
+ fEndWordSet = new UnicodeSet("[[:Mymr:]&[:LineBreak=SA:]]");
+ fMarkSet = new UnicodeSet("[[:Mymr:]&[:LineBreak=SA:]&[:M:]]");
fMarkSet.add(0x0020);
- fEndWordSet = new UnicodeSet(fBurmeseWordSet);
- fBeginWordSet.add(0x1000, 0x102A); // basic consonants and independent vowels
// Compact for caching
fMarkSet.compact();
@@ -55,14 +52,11 @@
fBeginWordSet.compact();
// Freeze the static UnicodeSet
- fBurmeseWordSet.freeze();
fMarkSet.freeze();
fEndWordSet.freeze();
fBeginWordSet.freeze();
- }
- public BurmeseBreakEngine() throws IOException {
- setCharacters(fBurmeseWordSet);
+ setCharacters(fEndWordSet);
// Initialize dictionary
fDictionary = DictionaryData.loadDictionaryFor("Mymr");
}
@@ -87,7 +81,7 @@
@Override
public int divideUpDictionaryRange(CharacterIterator fIter, int rangeStart, int rangeEnd,
- DequeI foundBreaks) {
+ DequeI foundBreaks, boolean isPhraseBreaking) {
if ((rangeEnd - rangeStart) < BURMESE_MIN_WORD) {
@@ -121,13 +115,9 @@
// If we're already at the end of the range, we're done
if (fIter.getIndex() < rangeEnd) {
do {
- int wordsMatched = 1;
if (words[(wordsFound+1)%BURMESE_LOOKAHEAD].candidates(fIter, fDictionary, rangeEnd) > 0) {
- if (wordsMatched < 2) {
- // Followed by another dictionary word; mark first word as a good candidate
- words[wordsFound%BURMESE_LOOKAHEAD].markCurrent();
- wordsMatched = 2;
- }
+ // Followed by another dictionary word; mark first word as a good candidate
+ words[wordsFound%BURMESE_LOOKAHEAD].markCurrent();
// If we're already at the end of the range, we're done
if (fIter.getIndex() >= rangeEnd) {
diff --git a/android_icu4j/src/main/java/android/icu/text/BytesDictionaryMatcher.java b/android_icu4j/src/main/java/android/icu/impl/breakiter/BytesDictionaryMatcher.java
similarity index 97%
rename from android_icu4j/src/main/java/android/icu/text/BytesDictionaryMatcher.java
rename to android_icu4j/src/main/java/android/icu/impl/breakiter/BytesDictionaryMatcher.java
index 91ed2d3..b9ada1e 100644
--- a/android_icu4j/src/main/java/android/icu/text/BytesDictionaryMatcher.java
+++ b/android_icu4j/src/main/java/android/icu/impl/breakiter/BytesDictionaryMatcher.java
@@ -7,11 +7,12 @@
* others. All Rights Reserved. *
*******************************************************************************
*/
-package android.icu.text;
+package android.icu.impl.breakiter;
import java.text.CharacterIterator;
import android.icu.impl.Assert;
+import android.icu.text.UCharacterIterator;
import android.icu.util.BytesTrie;
import android.icu.util.BytesTrie.Result;
diff --git a/android_icu4j/src/main/java/android/icu/text/CharsDictionaryMatcher.java b/android_icu4j/src/main/java/android/icu/impl/breakiter/CharsDictionaryMatcher.java
similarity index 96%
rename from android_icu4j/src/main/java/android/icu/text/CharsDictionaryMatcher.java
rename to android_icu4j/src/main/java/android/icu/impl/breakiter/CharsDictionaryMatcher.java
index cc50062..c20a8e9 100644
--- a/android_icu4j/src/main/java/android/icu/text/CharsDictionaryMatcher.java
+++ b/android_icu4j/src/main/java/android/icu/impl/breakiter/CharsDictionaryMatcher.java
@@ -7,10 +7,11 @@
* others. All Rights Reserved. *
*******************************************************************************
*/
-package android.icu.text;
+package android.icu.impl.breakiter;
import java.text.CharacterIterator;
+import android.icu.text.UCharacterIterator;
import android.icu.util.BytesTrie.Result;
import android.icu.util.CharsTrie;
diff --git a/android_icu4j/src/main/java/android/icu/text/CjkBreakEngine.java b/android_icu4j/src/main/java/android/icu/impl/breakiter/CjkBreakEngine.java
similarity index 62%
rename from android_icu4j/src/main/java/android/icu/text/CjkBreakEngine.java
rename to android_icu4j/src/main/java/android/icu/impl/breakiter/CjkBreakEngine.java
index 6e5d328..0a41f08 100644
--- a/android_icu4j/src/main/java/android/icu/text/CjkBreakEngine.java
+++ b/android_icu4j/src/main/java/android/icu/impl/breakiter/CjkBreakEngine.java
@@ -7,7 +7,7 @@
* others. All Rights Reserved. *
*******************************************************************************
*/
-package android.icu.text;
+package android.icu.impl.breakiter;
import static android.icu.impl.CharacterIteration.DONE32;
import static android.icu.impl.CharacterIteration.current32;
@@ -15,41 +15,68 @@
import java.io.IOException;
import java.text.CharacterIterator;
+import java.util.HashSet;
import android.icu.impl.Assert;
+import android.icu.impl.ICUData;
+import android.icu.text.Normalizer;
+import android.icu.text.UnicodeSet;
+import android.icu.text.UnicodeSetIterator;
+import android.icu.util.UResourceBundle;
+import android.icu.util.UResourceBundleIterator;
-class CjkBreakEngine extends DictionaryBreakEngine {
- private static final UnicodeSet fHangulWordSet = new UnicodeSet();
- private static final UnicodeSet fHanWordSet = new UnicodeSet();
- private static final UnicodeSet fKatakanaWordSet = new UnicodeSet();
- private static final UnicodeSet fHiraganaWordSet = new UnicodeSet();
- static {
- fHangulWordSet.applyPattern("[\\uac00-\\ud7a3]");
- fHanWordSet.applyPattern("[:Han:]");
- fKatakanaWordSet.applyPattern("[[:Katakana:]\\uff9e\\uff9f]");
- fHiraganaWordSet.applyPattern("[:Hiragana:]");
-
- // freeze them all
- fHangulWordSet.freeze();
- fHanWordSet.freeze();
- fKatakanaWordSet.freeze();
- fHiraganaWordSet.freeze();
- }
-
+/**
+ * @hide Only a subset of ICU is exposed in Android
+ */
+public class CjkBreakEngine extends DictionaryBreakEngine {
+ private UnicodeSet fHangulWordSet;
+ private UnicodeSet fDigitOrOpenPunctuationOrAlphabetSet;
+ private UnicodeSet fClosePunctuationSet;
private DictionaryMatcher fDictionary = null;
+ private HashSet<String> fSkipSet;
public CjkBreakEngine(boolean korean) throws IOException {
+ fHangulWordSet = new UnicodeSet("[\\uac00-\\ud7a3]");
+ fHangulWordSet.freeze();
+ // Digit, open punctuation and Alphabetic characters.
+ fDigitOrOpenPunctuationOrAlphabetSet = new UnicodeSet("[[:Nd:][:Pi:][:Ps:][:Alphabetic:]]");
+ fDigitOrOpenPunctuationOrAlphabetSet.freeze();
+
+ fClosePunctuationSet = new UnicodeSet("[[:Pc:][:Pd:][:Pe:][:Pf:][:Po:]]");
+ fClosePunctuationSet.freeze();
+ fSkipSet = new HashSet<String>();
+
fDictionary = DictionaryData.loadDictionaryFor("Hira");
if (korean) {
setCharacters(fHangulWordSet);
} else { //Chinese and Japanese
- UnicodeSet cjSet = new UnicodeSet();
- cjSet.addAll(fHanWordSet);
- cjSet.addAll(fKatakanaWordSet);
- cjSet.addAll(fHiraganaWordSet);
- cjSet.add(0xFF70); // HALFWIDTH KATAKANA-HIRAGANA PROLONGED SOUND MARK
- cjSet.add(0x30FC); // KATAKANA-HIRAGANA PROLONGED SOUND MARK
+ UnicodeSet cjSet = new UnicodeSet("[[:Han:][:Hiragana:][:Katakana:]\\u30fc\\uff70\\uff9e\\uff9f]");
setCharacters(cjSet);
+ initializeJapanesePhraseParamater();
+ }
+ }
+
+ private void initializeJapanesePhraseParamater() {
+ loadJapaneseExtensions();
+ loadHiragana();
+ }
+
+ private void loadJapaneseExtensions() {
+ UResourceBundle rb = UResourceBundle.getBundleInstance(ICUData.ICU_BRKITR_BASE_NAME, "ja");
+ final String tag = "extensions";
+ UResourceBundle bundle = rb.get(tag);
+ UResourceBundleIterator iterator = bundle.getIterator();
+ while (iterator.hasNext()) {
+ fSkipSet.add(iterator.nextString());
+ }
+ }
+
+ private void loadHiragana() {
+ UnicodeSet hiraganaWordSet = new UnicodeSet("[:Hiragana:]");
+ hiraganaWordSet.freeze();
+ UnicodeSetIterator iterator = new UnicodeSetIterator(hiraganaWordSet);
+ while (iterator.next()) {
+ fSkipSet.add(iterator.getString());
}
}
@@ -83,7 +110,7 @@
@Override
public int divideUpDictionaryRange(CharacterIterator inText, int startPos, int endPos,
- DequeI foundBreaks) {
+ DequeI foundBreaks, boolean isPhraseBreaking) {
if (startPos >= endPos) {
return 0;
}
@@ -213,6 +240,25 @@
if (bestSnlp[numCodePts] == kint32max) {
t_boundary[numBreaks] = numCodePts;
numBreaks++;
+ } else if (isPhraseBreaking) {
+ t_boundary[numBreaks] = numCodePts;
+ numBreaks++;
+ int prevIdx = numCodePts;
+ int codeUnitIdx = 0, length = 0;
+ for (int i = prev[numCodePts]; i > 0; i = prev[i]) {
+ codeUnitIdx = prenormstr.offsetByCodePoints(0, i);
+ length = prevIdx - i;
+ 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);
+ if (!fSkipSet.contains(pattern)
+ && (!isKatakana(current32(text)) || !isKatakana(next32(text)))) {
+ t_boundary[numBreaks] = i;
+ numBreaks++;
+ }
+ }
} else {
for (int i = numCodePts; i > 0; i = prev[i]) {
t_boundary[numBreaks] = i;
@@ -226,20 +272,54 @@
}
int correctedNumBreaks = 0;
+ int previous = -1;
for (int i = numBreaks - 1; i >= 0; i--) {
int pos = charPositions[t_boundary[i]] + startPos;
- if (!(foundBreaks.contains(pos) || pos == startPos)) {
- foundBreaks.push(charPositions[t_boundary[i]] + startPos);
- correctedNumBreaks++;
+ // In phrase breaking, there has to be a breakpoint between Cj character and close
+ // punctuation.
+ // E.g.[携帯電話]正しい選択 -> [携帯▁電話]▁正しい▁選択 -> breakpoint between ] and 正
+ if (pos > previous) {
+ if (pos != startPos
+ || (isPhraseBreaking && pos > 0
+ && fClosePunctuationSet.contains(inText.setIndex(pos - 1)))) {
+ foundBreaks.push(charPositions[t_boundary[i]] + startPos);
+ correctedNumBreaks++;
+ }
}
+ previous = pos;
}
if (!foundBreaks.isEmpty() && foundBreaks.peek() == endPos) {
- foundBreaks.pop();
- correctedNumBreaks--;
+ // 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
+ if (isPhraseBreaking) {
+ if (!fDigitOrOpenPunctuationOrAlphabetSet.contains(inText.setIndex(endPos))) {
+ foundBreaks.pop();
+ correctedNumBreaks--;
+ }
+ } else {
+ foundBreaks.pop();
+ correctedNumBreaks--;
+ }
}
if (!foundBreaks.isEmpty())
inText.setIndex(foundBreaks.peek());
return correctedNumBreaks;
}
+
+ private String getPatternFromText(CharacterIterator text, StringBuffer sb, int start,
+ int length) {
+ sb.setLength(0);
+ if(length > 0) {
+ text.setIndex(start);
+ sb.appendCodePoint(current32(text));
+ for (int j = 1; j < length; j++) {
+ sb.appendCodePoint(next32(text));
+ }
+ }
+ return sb.toString();
+ }
}
diff --git a/android_icu4j/src/main/java/android/icu/text/DictionaryBreakEngine.java b/android_icu4j/src/main/java/android/icu/impl/breakiter/DictionaryBreakEngine.java
similarity index 90%
rename from android_icu4j/src/main/java/android/icu/text/DictionaryBreakEngine.java
rename to android_icu4j/src/main/java/android/icu/impl/breakiter/DictionaryBreakEngine.java
index d0e5ec5..f90d93e 100644
--- a/android_icu4j/src/main/java/android/icu/text/DictionaryBreakEngine.java
+++ b/android_icu4j/src/main/java/android/icu/impl/breakiter/DictionaryBreakEngine.java
@@ -7,13 +7,17 @@
* others. All Rights Reserved. *
*******************************************************************************
*/
-package android.icu.text;
+package android.icu.impl.breakiter;
import java.text.CharacterIterator;
import android.icu.impl.CharacterIteration;
+import android.icu.text.UnicodeSet;
-abstract class DictionaryBreakEngine implements LanguageBreakEngine {
+/**
+ * @hide Only a subset of ICU is exposed in Android
+ */
+public abstract class DictionaryBreakEngine implements LanguageBreakEngine {
/* Helper class for improving readability of the Thai/Lao/Khmer word break
* algorithm.
@@ -87,9 +91,10 @@
* A deque-like structure holding raw ints.
* Partial, limited implementation, only what is needed by the dictionary implementation.
* For internal use only.
+ * @hide Only a subset of ICU is exposed in Android
* @hide draft / provisional / internal are hidden on Android
*/
- static class DequeI implements Cloneable {
+ public static class DequeI implements Cloneable {
private int[] data = new int[50];
private int lastIdx = 4; // or base of stack. Index of element.
private int firstIdx = 4; // or Top of Stack. Index of element + 1.
@@ -101,11 +106,11 @@
return result;
}
- int size() {
+ public int size() {
return firstIdx - lastIdx;
}
- boolean isEmpty() {
+ public boolean isEmpty() {
return size() == 0;
}
@@ -115,26 +120,26 @@
data = newData;
}
- void offer(int v) {
+ public void offer(int v) {
// Note that the actual use cases of offer() add at most one element.
// We make no attempt to handle more than a few.
assert lastIdx > 0;
data[--lastIdx] = v;
}
- void push(int v) {
+ public void push(int v) {
if (firstIdx >= data.length) {
grow();
}
data[firstIdx++] = v;
}
- int pop() {
+ public int pop() {
assert size() > 0;
return data[--firstIdx];
}
- int peek() {
+ public int peek() {
assert size() > 0;
return data[firstIdx - 1];
}
@@ -158,12 +163,12 @@
return false;
}
- int elementAt(int i) {
+ public int elementAt(int i) {
assert i < size();
return data[lastIdx + i];
}
- void removeAllElements() {
+ public void removeAllElements() {
lastIdx = firstIdx = 4;
}
}
@@ -183,7 +188,7 @@
@Override
public int findBreaks(CharacterIterator text, int startPos, int endPos,
- DequeI foundBreaks) {
+ DequeI foundBreaks, boolean isPhraseBreaking) {
int result = 0;
// Find the span of characters included in the set.
@@ -202,7 +207,7 @@
rangeStart = start;
rangeEnd = current;
- result = divideUpDictionaryRange(text, rangeStart, rangeEnd, foundBreaks);
+ result = divideUpDictionaryRange(text, rangeStart, rangeEnd, foundBreaks, isPhraseBreaking);
text.setIndex(current);
return result;
@@ -226,5 +231,6 @@
abstract int divideUpDictionaryRange(CharacterIterator text,
int rangeStart,
int rangeEnd,
- DequeI foundBreaks );
+ DequeI foundBreaks,
+ boolean isPhraseBreaking);
}
diff --git a/android_icu4j/src/main/java/android/icu/text/DictionaryData.java b/android_icu4j/src/main/java/android/icu/impl/breakiter/DictionaryData.java
similarity index 98%
rename from android_icu4j/src/main/java/android/icu/text/DictionaryData.java
rename to android_icu4j/src/main/java/android/icu/impl/breakiter/DictionaryData.java
index ad2b39b..4208a4f 100644
--- a/android_icu4j/src/main/java/android/icu/text/DictionaryData.java
+++ b/android_icu4j/src/main/java/android/icu/impl/breakiter/DictionaryData.java
@@ -8,7 +8,7 @@
*******************************************************************************
*/
-package android.icu.text;
+package android.icu.impl.breakiter;
import java.io.IOException;
import java.nio.ByteBuffer;
diff --git a/android_icu4j/src/main/java/android/icu/text/DictionaryMatcher.java b/android_icu4j/src/main/java/android/icu/impl/breakiter/DictionaryMatcher.java
similarity index 97%
rename from android_icu4j/src/main/java/android/icu/text/DictionaryMatcher.java
rename to android_icu4j/src/main/java/android/icu/impl/breakiter/DictionaryMatcher.java
index 3b27240..70ac324 100644
--- a/android_icu4j/src/main/java/android/icu/text/DictionaryMatcher.java
+++ b/android_icu4j/src/main/java/android/icu/impl/breakiter/DictionaryMatcher.java
@@ -7,7 +7,7 @@
* others. All Rights Reserved. *
*******************************************************************************
*/
-package android.icu.text;
+package android.icu.impl.breakiter;
import java.text.CharacterIterator;
diff --git a/android_icu4j/src/main/java/android/icu/text/KhmerBreakEngine.java b/android_icu4j/src/main/java/android/icu/impl/breakiter/KhmerBreakEngine.java
similarity index 88%
rename from android_icu4j/src/main/java/android/icu/text/KhmerBreakEngine.java
rename to android_icu4j/src/main/java/android/icu/impl/breakiter/KhmerBreakEngine.java
index d40cb1b..f746b98 100644
--- a/android_icu4j/src/main/java/android/icu/text/KhmerBreakEngine.java
+++ b/android_icu4j/src/main/java/android/icu/impl/breakiter/KhmerBreakEngine.java
@@ -7,7 +7,7 @@
* others. All Rights Reserved. *
*******************************************************************************
*/
-package android.icu.text;
+package android.icu.impl.breakiter;
import java.io.IOException;
import java.text.CharacterIterator;
@@ -15,8 +15,12 @@
import android.icu.lang.UCharacter;
import android.icu.lang.UProperty;
import android.icu.lang.UScript;
+import android.icu.text.UnicodeSet;
-class KhmerBreakEngine extends DictionaryBreakEngine {
+/**
+ * @hide Only a subset of ICU is exposed in Android
+ */
+public class KhmerBreakEngine extends DictionaryBreakEngine {
// Constants for KhmerBreakIterator
// How many words in a row are "good enough"?
@@ -33,24 +37,20 @@
private DictionaryMatcher fDictionary;
- private static UnicodeSet fKhmerWordSet;
- private static UnicodeSet fEndWordSet;
- private static UnicodeSet fBeginWordSet;
- private static UnicodeSet fMarkSet;
+ private UnicodeSet fEndWordSet;
+ private UnicodeSet fBeginWordSet;
+ private UnicodeSet fMarkSet;
- static {
+ public KhmerBreakEngine() throws IOException {
// Initialize UnicodeSets
- fKhmerWordSet = new UnicodeSet();
- fMarkSet = new UnicodeSet();
- fBeginWordSet = new UnicodeSet();
-
- fKhmerWordSet.applyPattern("[[:Khmer:]&[:LineBreak=SA:]]");
- fKhmerWordSet.compact();
-
- fMarkSet.applyPattern("[[:Khmer:]&[:LineBreak=SA:]&[:M:]]");
+ UnicodeSet khmerWordSet = new UnicodeSet("[[:Khmer:]&[:LineBreak=SA:]]");
+ fMarkSet = new UnicodeSet("[[:Khmer:]&[:LineBreak=SA:]&[:M:]]");
fMarkSet.add(0x0020);
- fEndWordSet = new UnicodeSet(fKhmerWordSet);
- fBeginWordSet.add(0x1780, 0x17B3);
+ fBeginWordSet = new UnicodeSet(0x1780, 0x17B3);
+
+ khmerWordSet.compact();
+
+ fEndWordSet = new UnicodeSet(khmerWordSet);
fEndWordSet.remove(0x17D2); // KHMER SIGN COENG that combines some following characters
// Compact for caching
@@ -59,14 +59,12 @@
fBeginWordSet.compact();
// Freeze the static UnicodeSet
- fKhmerWordSet.freeze();
+ khmerWordSet.freeze();
fMarkSet.freeze();
fEndWordSet.freeze();
fBeginWordSet.freeze();
- }
- public KhmerBreakEngine() throws IOException {
- setCharacters(fKhmerWordSet);
+ setCharacters(khmerWordSet);
// Initialize dictionary
fDictionary = DictionaryData.loadDictionaryFor("Khmr");
}
@@ -91,7 +89,7 @@
@Override
public int divideUpDictionaryRange(CharacterIterator fIter, int rangeStart, int rangeEnd,
- DequeI foundBreaks) {
+ DequeI foundBreaks, boolean isPhraseBreaking) {
if ((rangeEnd - rangeStart) < KHMER_MIN_WORD_SPAN) {
return 0; // Not enough characters for word
@@ -125,13 +123,9 @@
// If we're already at the end of the range, we're done
if (fIter.getIndex() < rangeEnd) {
do {
- int wordsMatched = 1;
if (words[(wordsFound+1)%KHMER_LOOKAHEAD].candidates(fIter, fDictionary, rangeEnd) > 0) {
- if (wordsMatched < 2) {
- // Followed by another dictionary word; mark first word as a good candidate
- words[wordsFound%KHMER_LOOKAHEAD].markCurrent();
- wordsMatched = 2;
- }
+ // Followed by another dictionary word; mark first word as a good candidate
+ words[wordsFound%KHMER_LOOKAHEAD].markCurrent();
// If we're already at the end of the range, we're done
if (fIter.getIndex() >= rangeEnd) {
diff --git a/android_icu4j/src/main/java/android/icu/impl/breakiter/LSTMBreakEngine.java b/android_icu4j/src/main/java/android/icu/impl/breakiter/LSTMBreakEngine.java
new file mode 100644
index 0000000..4b49b07
--- /dev/null
+++ b/android_icu4j/src/main/java/android/icu/impl/breakiter/LSTMBreakEngine.java
@@ -0,0 +1,454 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+// © 2021 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html
+//
+/**
+ * A LSTMBreakEngine
+ */
+package android.icu.impl.breakiter;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.text.CharacterIterator;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import android.icu.impl.ICUData;
+import android.icu.impl.ICUResourceBundle;
+import android.icu.lang.UCharacter;
+import android.icu.lang.UProperty;
+import android.icu.lang.UScript;
+import android.icu.text.BreakIterator;
+import android.icu.text.UnicodeSet;
+import android.icu.util.UResourceBundle;
+
+/**
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+public class LSTMBreakEngine extends DictionaryBreakEngine {
+ /**
+ * @hide Only a subset of ICU is exposed in Android
+ */
+ public enum EmbeddingType {
+ UNKNOWN,
+ CODE_POINTS,
+ GRAPHEME_CLUSTER
+ }
+
+ /**
+ * @hide Only a subset of ICU is exposed in Android
+ */
+ public enum LSTMClass {
+ BEGIN,
+ INSIDE,
+ END,
+ SINGLE,
+ }
+
+ private static float[][] make2DArray(int[] data, int start, int d1, int d2) {
+ byte[] bytes = new byte[4];
+ float [][] result = new float[d1][d2];
+ for (int i = 0; i < d1 ; i++) {
+ for (int j = 0; j < d2 ; j++) {
+ int d = data[start++];
+ bytes[0] = (byte) (d >> 24);
+ bytes[1] = (byte) (d >> 16);
+ bytes[2] = (byte) (d >> 8);
+ bytes[3] = (byte) (d /*>> 0*/);
+ result[i][j] = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getFloat();
+ }
+ }
+ return result;
+ }
+
+ private static float[] make1DArray(int[] data, int start, int d1) {
+ byte[] bytes = new byte[4];
+ float [] result = new float[d1];
+ for (int i = 0; i < d1 ; i++) {
+ int d = data[start++];
+ bytes[0] = (byte) (d >> 24);
+ bytes[1] = (byte) (d >> 16);
+ bytes[2] = (byte) (d >> 8);
+ bytes[3] = (byte) (d /*>> 0*/);
+ result[i] = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getFloat();
+ }
+ return result;
+ }
+
+ /** @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android*/
+ public static class LSTMData {
+ private LSTMData() {
+ }
+
+ public LSTMData(UResourceBundle rb) {
+ int embeddings = rb.get("embeddings").getInt();
+ int hunits = rb.get("hunits").getInt();
+ this.fType = EmbeddingType.UNKNOWN;
+ this.fName = rb.get("model").getString();
+ String typeString = rb.get("type").getString();
+ if (typeString.equals("codepoints")) {
+ this.fType = EmbeddingType.CODE_POINTS;
+ } else if (typeString.equals("graphclust")) {
+ this.fType = EmbeddingType.GRAPHEME_CLUSTER;
+ }
+ String[] dict = rb.get("dict").getStringArray();
+ int[] data = rb.get("data").getIntVector();
+ int dataLen = data.length;
+ int numIndex = dict.length;
+ fDict = new HashMap<String, Integer>(numIndex + 1);
+ int idx = 0;
+ for (String embedding : dict){
+ fDict.put(embedding, idx++);
+ }
+ int mat1Size = (numIndex + 1) * embeddings;
+ int mat2Size = embeddings * 4 * hunits;
+ int mat3Size = hunits * 4 * hunits;
+ int mat4Size = 4 * hunits;
+ int mat5Size = mat2Size;
+ int mat6Size = mat3Size;
+ int mat7Size = mat4Size;
+ int mat8Size = 2 * hunits * 4;
+ int mat9Size = 4;
+ assert dataLen == mat1Size + mat2Size + mat3Size + mat4Size + mat5Size + mat6Size + mat7Size + mat8Size + mat9Size;
+ int start = 0;
+ this.fEmbedding = make2DArray(data, start, (numIndex+1), embeddings);
+ start += mat1Size;
+ this.fForwardW = make2DArray(data, start, embeddings, 4 * hunits);
+ start += mat2Size;
+ this.fForwardU = make2DArray(data, start, hunits, 4 * hunits);
+ start += mat3Size;
+ this.fForwardB = make1DArray(data, start, 4 * hunits);
+ start += mat4Size;
+ this.fBackwardW = make2DArray(data, start, embeddings, 4 * hunits);
+ start += mat5Size;
+ this.fBackwardU = make2DArray(data, start, hunits, 4 * hunits);
+ start += mat6Size;
+ this.fBackwardB = make1DArray(data, start, 4 * hunits);
+ start += mat7Size;
+ this.fOutputW = make2DArray(data, start, 2 * hunits, 4);
+ start += mat8Size;
+ this.fOutputB = make1DArray(data, start, 4);
+ }
+
+ public EmbeddingType fType;
+ public String fName;
+ public Map<String, Integer> fDict;
+ public float fEmbedding[][];
+ public float fForwardW[][];
+ public float fForwardU[][];
+ public float fForwardB[];
+ public float fBackwardW[][];
+ public float fBackwardU[][];
+ public float fBackwardB[];
+ public float fOutputW[][];
+ public float fOutputB[];
+ }
+
+ // Minimum word size
+ private static final byte MIN_WORD = 2;
+
+ // Minimum number of characters for two words
+ private static final byte MIN_WORD_SPAN = MIN_WORD * 2;
+
+ abstract class Vectorizer {
+ public Vectorizer(Map<String, Integer> dict) {
+ this.fDict = dict;
+ }
+ abstract public void vectorize(CharacterIterator fIter, int rangeStart, int rangeEnd,
+ List<Integer> offsets, List<Integer> indicies);
+ protected int getIndex(String token) {
+ Integer res = fDict.get(token);
+ return (res == null) ? fDict.size() : res;
+ }
+ private Map<String, Integer> fDict;
+ }
+
+ class CodePointsVectorizer extends Vectorizer {
+ public CodePointsVectorizer(Map<String, Integer> dict) {
+ super(dict);
+ }
+
+ public void vectorize(CharacterIterator fIter, int rangeStart, int rangeEnd,
+ List<Integer> offsets, List<Integer> indicies) {
+ fIter.setIndex(rangeStart);
+ for (char c = fIter.current();
+ c != CharacterIterator.DONE && fIter.getIndex() < rangeEnd;
+ c = fIter.next()) {
+ offsets.add(fIter.getIndex());
+ indicies.add(getIndex(String.valueOf(c)));
+ }
+ }
+ }
+
+ class GraphemeClusterVectorizer extends Vectorizer {
+ public GraphemeClusterVectorizer(Map<String, Integer> dict) {
+ super(dict);
+ }
+
+ private String substring(CharacterIterator text, int startPos, int endPos) {
+ int saved = text.getIndex();
+ text.setIndex(startPos);
+ StringBuilder sb = new StringBuilder();
+ for (char c = text.current();
+ c != CharacterIterator.DONE && text.getIndex() < endPos;
+ c = text.next()) {
+ sb.append(c);
+ }
+ text.setIndex(saved);
+ return sb.toString();
+ }
+
+ public void vectorize(CharacterIterator text, int startPos, int endPos,
+ List<Integer> offsets, List<Integer> indicies) {
+ BreakIterator iter = BreakIterator.getCharacterInstance();
+ iter.setText(text);
+ int last = iter.next(startPos);
+ for (int curr = iter.next(); curr != BreakIterator.DONE && curr <= endPos; curr = iter.next()) {
+ offsets.add(last);
+ String segment = substring(text, last, curr);
+ int index = getIndex(segment);
+ indicies.add(index);
+ last = curr;
+ }
+ }
+ }
+
+ private final LSTMData fData;
+ private int fScript;
+ private final Vectorizer fVectorizer;
+
+ private Vectorizer makeVectorizer(LSTMData data) {
+ switch(data.fType) {
+ case CODE_POINTS:
+ return new CodePointsVectorizer(data.fDict);
+ case GRAPHEME_CLUSTER:
+ return new GraphemeClusterVectorizer(data.fDict);
+ default:
+ return null;
+ }
+ }
+
+ public LSTMBreakEngine(int script, UnicodeSet set, LSTMData data) {
+ setCharacters(set);
+ this.fScript = script;
+ this.fData = data;
+ this.fVectorizer = makeVectorizer(this.fData);
+ }
+
+ @Override
+ public int hashCode() {
+ return getClass().hashCode();
+ }
+
+ @Override
+ public boolean handles(int c) {
+ return fScript == UCharacter.getIntPropertyValue(c, UProperty.SCRIPT);
+ }
+
+ static private void addDotProductTo(final float [] a, final float[][] b, float[] result) {
+ assert a.length == b.length;
+ assert b[0].length == result.length;
+ for (int i = 0; i < result.length; i++) {
+ for (int j = 0; j < a.length; j++) {
+ result[i] += a[j] * b[j][i];
+ }
+ }
+ }
+
+ static private void addTo(final float [] a, float[] result) {
+ assert a.length == result.length;
+ for (int i = 0; i < result.length; i++) {
+ result[i] += a[i];
+ }
+ }
+
+ static private void hadamardProductTo(final float [] a, float[] result) {
+ assert a.length == result.length;
+ for (int i = 0; i < result.length; i++) {
+ result[i] *= a[i];
+ }
+ }
+
+ static private void addHadamardProductTo(final float [] a, final float [] b, float[] result) {
+ assert a.length == result.length;
+ assert b.length == result.length;
+ for (int i = 0; i < result.length; i++) {
+ result[i] += a[i] * b[i];
+ }
+ }
+
+ static private void sigmoid(float [] result, int start, int length) {
+ assert start < result.length;
+ assert start + length <= result.length;
+ for (int i = start; i < start + length; i++) {
+ result[i] = (float)(1.0/(1.0 + Math.exp(-result[i])));
+ }
+ }
+
+ static private void tanh(float [] result, int start, int length) {
+ assert start < result.length;
+ assert start + length <= result.length;
+ for (int i = start; i < start + length; i++) {
+ result[i] = (float)Math.tanh(result[i]);
+ }
+ }
+
+ static private int maxIndex(float [] data) {
+ int index = 0;
+ float max = data[0];
+ for (int i = 1; i < data.length; i++) {
+ if (data[i] > max) {
+ max = data[i];
+ index = i;
+ }
+ }
+ return index;
+ }
+
+ /*
+ static private void print(float [] data) {
+ for (int i=0; i < data.length; i++) {
+ System.out.format(" %e", data[i]);
+ if (i % 4 == 3) {
+ System.out.println();
+ }
+ }
+ System.out.println();
+ }
+ */
+
+ private float[] compute(final float[][] W, final float[][] U, final float[] B,
+ final float[] x, float[] h, float[] c) {
+ // ifco = x * W + h * U + b
+ float[] ifco = Arrays.copyOf(B, B.length);
+ addDotProductTo(x, W, ifco);
+ float[] hU = new float[B.length];
+ addDotProductTo(h, U, ifco);
+
+ int hunits = B.length / 4;
+ sigmoid(ifco, 0*hunits, hunits); // i
+ sigmoid(ifco, 1*hunits, hunits); // f
+ tanh(ifco, 2*hunits, hunits); // c_
+ sigmoid(ifco, 3*hunits, hunits); // o
+
+ hadamardProductTo(Arrays.copyOfRange(ifco, hunits, 2*hunits), c);
+ addHadamardProductTo(Arrays.copyOf(ifco, hunits),
+ Arrays.copyOfRange(ifco, 2*hunits, 3*hunits), c);
+
+ h = Arrays.copyOf(c, c.length);
+ tanh(h, 0, h.length);
+ hadamardProductTo(Arrays.copyOfRange(ifco, 3*hunits, 4*hunits), h);
+ // System.out.println("c");
+ // print(c);
+ // System.out.println("h");
+ // print(h);
+ return h;
+ }
+
+ @Override
+ public int divideUpDictionaryRange(CharacterIterator fIter, int rangeStart, int rangeEnd,
+ DequeI foundBreaks, boolean isPhraseBreaking) {
+ int beginSize = foundBreaks.size();
+
+ if ((rangeEnd - rangeStart) < MIN_WORD_SPAN) {
+ return 0; // Not enough characters for word
+ }
+ List<Integer> offsets = new ArrayList<Integer>(rangeEnd - rangeStart);
+ List<Integer> indicies = new ArrayList<Integer>(rangeEnd - rangeStart);
+
+ fVectorizer.vectorize(fIter, rangeStart, rangeEnd, offsets, indicies);
+
+ // To save the needed memory usage, the following is different from the
+ // Python or ICU4X implementation. We first perform the Backward LSTM
+ // and then merge the iteration of the forward LSTM and the output layer
+ // together because we only need to remember the h[t-1] for Forward LSTM.
+ int inputSeqLength = indicies.size();
+ int hunits = this.fData.fForwardU.length;
+ float c[] = new float[hunits];
+
+ // TODO: limit size of hBackward. If input_seq_len is too big, we could
+ // run out of memory.
+ // Backward LSTM
+ float hBackward[][] = new float[inputSeqLength][hunits];
+ for (int i = inputSeqLength - 1; i >= 0; i--) {
+ if (i != inputSeqLength - 1) {
+ hBackward[i] = Arrays.copyOf(hBackward[i+1], hunits);
+ }
+ // System.out.println("Backward LSTM " + i);
+ hBackward[i] = compute(this.fData.fBackwardW, this.fData.fBackwardU, this.fData.fBackwardB,
+ this.fData.fEmbedding[indicies.get(i)],
+ hBackward[i], c);
+ }
+
+ c = new float[hunits];
+ float forwardH[] = new float[hunits];
+ float both[] = new float[2*hunits];
+
+ // The following iteration merge the forward LSTM and the output layer
+ // together.
+ for (int i = 0 ; i < inputSeqLength; i++) {
+ // Forward LSTM
+ forwardH = compute(this.fData.fForwardW, this.fData.fForwardU, this.fData.fForwardB,
+ this.fData.fEmbedding[indicies.get(i)],
+ forwardH, c);
+
+ System.arraycopy(forwardH, 0, both, 0, hunits);
+ System.arraycopy(hBackward[i], 0, both, hunits, hunits);
+
+ //System.out.println("Merged " + i);
+ //print(both);
+
+ // Output layer
+ // logp = fbRow * fOutputW + fOutputB
+ float logp[] = Arrays.copyOf(this.fData.fOutputB, this.fData.fOutputB.length);
+ addDotProductTo(both, this.fData.fOutputW, logp);
+
+ int current = maxIndex(logp);
+
+ // BIES logic.
+ if (current == LSTMClass.BEGIN.ordinal() ||
+ current == LSTMClass.SINGLE.ordinal()) {
+ if (i != 0) {
+ foundBreaks.push(offsets.get(i));
+ }
+ }
+ }
+
+ return foundBreaks.size() - beginSize;
+ }
+
+ public static LSTMData createData(UResourceBundle bundle) {
+ return new LSTMData(bundle);
+ }
+
+ private static String defaultLSTM(int script) {
+ ICUResourceBundle rb = (ICUResourceBundle)UResourceBundle.getBundleInstance(ICUData.ICU_BRKITR_BASE_NAME);
+ return rb.getStringWithFallback("lstm/" + UScript.getShortName(script));
+ }
+
+ public static LSTMData createData(int script) {
+ if (script != UScript.KHMER && script != UScript.LAO && script != UScript.MYANMAR && script != UScript.THAI) {
+ return null;
+ }
+ String name = defaultLSTM(script);
+ name = name.substring(0, name.indexOf("."));
+
+ UResourceBundle rb = UResourceBundle.getBundleInstance(
+ ICUData.ICU_BRKITR_BASE_NAME, name,
+ ICUResourceBundle.ICU_DATA_CLASS_LOADER);
+ return createData(rb);
+ }
+
+ public static LSTMBreakEngine create(int script, LSTMData data) {
+ String setExpr = "[[:" + UScript.getShortName(script) + ":]&[:LineBreak=SA:]]";
+ UnicodeSet set = new UnicodeSet();
+ set.applyPattern(setExpr);
+ set.compact();
+ return new LSTMBreakEngine(script, set, data);
+ }
+}
diff --git a/android_icu4j/src/main/java/android/icu/text/LanguageBreakEngine.java b/android_icu4j/src/main/java/android/icu/impl/breakiter/LanguageBreakEngine.java
similarity index 87%
copy from android_icu4j/src/main/java/android/icu/text/LanguageBreakEngine.java
copy to android_icu4j/src/main/java/android/icu/impl/breakiter/LanguageBreakEngine.java
index 5e1f38a..1b11cc3 100644
--- a/android_icu4j/src/main/java/android/icu/text/LanguageBreakEngine.java
+++ b/android_icu4j/src/main/java/android/icu/impl/breakiter/LanguageBreakEngine.java
@@ -7,15 +7,16 @@
* others. All Rights Reserved. *
*******************************************************************************
*/
-package android.icu.text;
+package android.icu.impl.breakiter;
import java.text.CharacterIterator;
/**
* The LanguageBreakEngine interface is to be used to implement any
* language-specific logic for break iteration.
+ * @hide Only a subset of ICU is exposed in Android
*/
-interface LanguageBreakEngine {
+public interface LanguageBreakEngine {
/**
* @param c A Unicode codepoint value
* @return true if the engine can handle this character, false otherwise
@@ -33,7 +34,7 @@
* @return the number of breaks found
*/
int findBreaks(CharacterIterator text, int startPos, int endPos,
- DictionaryBreakEngine.DequeI foundBreaks);
+ DictionaryBreakEngine.DequeI foundBreaks, boolean isPhraseBreaking);
}
diff --git a/android_icu4j/src/main/java/android/icu/text/LaoBreakEngine.java b/android_icu4j/src/main/java/android/icu/impl/breakiter/LaoBreakEngine.java
similarity index 86%
rename from android_icu4j/src/main/java/android/icu/text/LaoBreakEngine.java
rename to android_icu4j/src/main/java/android/icu/impl/breakiter/LaoBreakEngine.java
index 687b593..9db413d 100644
--- a/android_icu4j/src/main/java/android/icu/text/LaoBreakEngine.java
+++ b/android_icu4j/src/main/java/android/icu/impl/breakiter/LaoBreakEngine.java
@@ -7,7 +7,7 @@
* others. All Rights Reserved. *
*******************************************************************************
*/
-package android.icu.text;
+package android.icu.impl.breakiter;
import java.io.IOException;
import java.text.CharacterIterator;
@@ -15,8 +15,12 @@
import android.icu.lang.UCharacter;
import android.icu.lang.UProperty;
import android.icu.lang.UScript;
+import android.icu.text.UnicodeSet;
-class LaoBreakEngine extends DictionaryBreakEngine {
+/**
+ * @hide Only a subset of ICU is exposed in Android
+ */
+public class LaoBreakEngine extends DictionaryBreakEngine {
// Constants for LaoBreakIterator
// How many words in a row are "good enough"?
@@ -30,27 +34,24 @@
private static final byte LAO_MIN_WORD = 2;
private DictionaryMatcher fDictionary;
- private static UnicodeSet fLaoWordSet;
- private static UnicodeSet fEndWordSet;
- private static UnicodeSet fBeginWordSet;
- private static UnicodeSet fMarkSet;
+ private UnicodeSet fEndWordSet;
+ private UnicodeSet fBeginWordSet;
+ private UnicodeSet fMarkSet;
- static {
+ public LaoBreakEngine() throws IOException {
// Initialize UnicodeSets
- fLaoWordSet = new UnicodeSet();
- fMarkSet = new UnicodeSet();
- fBeginWordSet = new UnicodeSet();
-
- fLaoWordSet.applyPattern("[[:Laoo:]&[:LineBreak=SA:]]");
- fLaoWordSet.compact();
-
- fMarkSet.applyPattern("[[:Laoo:]&[:LineBreak=SA:]&[:M:]]");
+ UnicodeSet laoWordSet = new UnicodeSet("[[:Laoo:]&[:LineBreak=SA:]]");
+ fMarkSet = new UnicodeSet("[[:Laoo:]&[:LineBreak=SA:]&[:M:]]");
fMarkSet.add(0x0020);
- fEndWordSet = new UnicodeSet(fLaoWordSet);
+ fBeginWordSet = new UnicodeSet(
+ 0x0E81, 0x0EAE, // basic consonants (including holes for corresponding Thai characters)
+ 0x0EC0, 0x0EC4, // prefix vowels
+ 0x0EDC, 0x0EDD); // digraph consonants (no Thai equivalent)
+
+ laoWordSet.compact();
+
+ fEndWordSet = new UnicodeSet(laoWordSet);
fEndWordSet.remove(0x0EC0, 0x0EC4); // prefix vowels
- fBeginWordSet.add(0x0E81, 0x0EAE); // basic consonants (including holes for corresponding Thai characters)
- fBeginWordSet.add(0x0EDC, 0x0EDD); // digraph consonants (no Thai equivalent)
- fBeginWordSet.add(0x0EC0, 0x0EC4); // prefix vowels
// Compact for caching
fMarkSet.compact();
@@ -58,14 +59,12 @@
fBeginWordSet.compact();
// Freeze the static UnicodeSet
- fLaoWordSet.freeze();
+ laoWordSet.freeze();
fMarkSet.freeze();
fEndWordSet.freeze();
fBeginWordSet.freeze();
- }
- public LaoBreakEngine() throws IOException {
- setCharacters(fLaoWordSet);
+ setCharacters(laoWordSet);
// Initialize dictionary
fDictionary = DictionaryData.loadDictionaryFor("Laoo");
}
@@ -90,7 +89,7 @@
@Override
public int divideUpDictionaryRange(CharacterIterator fIter, int rangeStart, int rangeEnd,
- DequeI foundBreaks) {
+ DequeI foundBreaks, boolean isPhraseBreaking) {
if ((rangeEnd - rangeStart) < LAO_MIN_WORD) {
@@ -124,13 +123,9 @@
// If we're already at the end of the range, we're done
if (fIter.getIndex() < rangeEnd) {
do {
- int wordsMatched = 1;
if (words[(wordsFound+1)%LAO_LOOKAHEAD].candidates(fIter, fDictionary, rangeEnd) > 0) {
- if (wordsMatched < 2) {
- // Followed by another dictionary word; mark first word as a good candidate
- words[wordsFound%LAO_LOOKAHEAD].markCurrent();
- wordsMatched = 2;
- }
+ // Followed by another dictionary word; mark first word as a good candidate
+ words[wordsFound%LAO_LOOKAHEAD].markCurrent();
// If we're already at the end of the range, we're done
if (fIter.getIndex() >= rangeEnd) {
diff --git a/android_icu4j/src/main/java/android/icu/text/ThaiBreakEngine.java b/android_icu4j/src/main/java/android/icu/impl/breakiter/ThaiBreakEngine.java
similarity index 89%
rename from android_icu4j/src/main/java/android/icu/text/ThaiBreakEngine.java
rename to android_icu4j/src/main/java/android/icu/impl/breakiter/ThaiBreakEngine.java
index 776046c..9d663bd 100644
--- a/android_icu4j/src/main/java/android/icu/text/ThaiBreakEngine.java
+++ b/android_icu4j/src/main/java/android/icu/impl/breakiter/ThaiBreakEngine.java
@@ -7,7 +7,7 @@
* others. All Rights Reserved. *
*******************************************************************************
*/
-package android.icu.text;
+package android.icu.impl.breakiter;
import java.io.IOException;
import java.text.CharacterIterator;
@@ -15,8 +15,12 @@
import android.icu.lang.UCharacter;
import android.icu.lang.UProperty;
import android.icu.lang.UScript;
+import android.icu.text.UnicodeSet;
-class ThaiBreakEngine extends DictionaryBreakEngine {
+/**
+ * @hide Only a subset of ICU is exposed in Android
+ */
+public class ThaiBreakEngine extends DictionaryBreakEngine {
// Constants for ThaiBreakIterator
// How many words in a row are "good enough"?
@@ -36,32 +40,28 @@
private static final byte THAI_MIN_WORD_SPAN = THAI_MIN_WORD * 2;
private DictionaryMatcher fDictionary;
- private static UnicodeSet fThaiWordSet;
- private static UnicodeSet fEndWordSet;
- private static UnicodeSet fBeginWordSet;
- private static UnicodeSet fSuffixSet;
- private static UnicodeSet fMarkSet;
+ private UnicodeSet fEndWordSet;
+ private UnicodeSet fBeginWordSet;
+ private UnicodeSet fSuffixSet;
+ private UnicodeSet fMarkSet;
- static {
+ public ThaiBreakEngine() throws IOException {
// Initialize UnicodeSets
- fThaiWordSet = new UnicodeSet();
- fMarkSet = new UnicodeSet();
- fBeginWordSet = new UnicodeSet();
- fSuffixSet = new UnicodeSet();
-
- fThaiWordSet.applyPattern("[[:Thai:]&[:LineBreak=SA:]]");
- fThaiWordSet.compact();
-
- fMarkSet.applyPattern("[[:Thai:]&[:LineBreak=SA:]&[:M:]]");
+ UnicodeSet thaiWordSet = new UnicodeSet("[[:Thai:]&[:LineBreak=SA:]]");
+ fMarkSet = new UnicodeSet("[[:Thai:]&[:LineBreak=SA:]&[:M:]]");
fMarkSet.add(0x0020);
- fEndWordSet = new UnicodeSet(fThaiWordSet);
- fEndWordSet.remove(0x0E31); // MAI HAN-AKAT
- fEndWordSet.remove(0x0E40, 0x0E44); // SARA E through SARA AI MAIMALAI
- fBeginWordSet.add(0x0E01, 0x0E2E); //KO KAI through HO NOKHUK
- fBeginWordSet.add(0x0E40, 0x0E44); // SARA E through SARA AI MAIMALAI
+ fBeginWordSet = new UnicodeSet(0x0E01, 0x0E2E, //KO KAI through HO NOKHUK
+ 0x0E40, 0x0E44); // SARA E through SARA AI MAIMALAI
+ fSuffixSet = new UnicodeSet();
fSuffixSet.add(THAI_PAIYANNOI);
fSuffixSet.add(THAI_MAIYAMOK);
+ thaiWordSet.compact();
+
+ fEndWordSet = new UnicodeSet(thaiWordSet);
+ fEndWordSet.remove(0x0E31); // MAI HAN-AKAT
+ fEndWordSet.remove(0x0E40, 0x0E44); // SARA E through SARA AI MAIMALAI
+
// Compact for caching
fMarkSet.compact();
fEndWordSet.compact();
@@ -69,15 +69,13 @@
fSuffixSet.compact();
// Freeze the static UnicodeSet
- fThaiWordSet.freeze();
+ thaiWordSet.freeze();
fMarkSet.freeze();
fEndWordSet.freeze();
fBeginWordSet.freeze();
fSuffixSet.freeze();
- }
- public ThaiBreakEngine() throws IOException {
- setCharacters(fThaiWordSet);
+ setCharacters(thaiWordSet);
// Initialize dictionary
fDictionary = DictionaryData.loadDictionaryFor("Thai");
}
@@ -102,7 +100,7 @@
@Override
public int divideUpDictionaryRange(CharacterIterator fIter, int rangeStart, int rangeEnd,
- DequeI foundBreaks) {
+ DequeI foundBreaks, boolean isPhraseBreaking) {
if ((rangeEnd - rangeStart) < THAI_MIN_WORD_SPAN) {
return 0; // Not enough characters for word
@@ -135,13 +133,9 @@
if (fIter.getIndex() < rangeEnd) {
foundBest:
do {
- int wordsMatched = 1;
if (words[(wordsFound+1)%THAI_LOOKAHEAD].candidates(fIter, fDictionary, rangeEnd) > 0) {
- if (wordsMatched < 2) {
- // Followed by another dictionary word; mark first word as a good candidate
- words[wordsFound%THAI_LOOKAHEAD].markCurrent();
- wordsMatched = 2;
- }
+ // Followed by another dictionary word; mark first word as a good candidate
+ words[wordsFound%THAI_LOOKAHEAD].markCurrent();
// If we're already at the end of the range, we're done
if (fIter.getIndex() >= rangeEnd) {
diff --git a/android_icu4j/src/main/java/android/icu/text/UnhandledBreakEngine.java b/android_icu4j/src/main/java/android/icu/impl/breakiter/UnhandledBreakEngine.java
similarity index 90%
rename from android_icu4j/src/main/java/android/icu/text/UnhandledBreakEngine.java
rename to android_icu4j/src/main/java/android/icu/impl/breakiter/UnhandledBreakEngine.java
index b035185..b5c0bed 100644
--- a/android_icu4j/src/main/java/android/icu/text/UnhandledBreakEngine.java
+++ b/android_icu4j/src/main/java/android/icu/impl/breakiter/UnhandledBreakEngine.java
@@ -7,15 +7,19 @@
* others. All Rights Reserved. *
*******************************************************************************
*/
-package android.icu.text;
+package android.icu.impl.breakiter;
import java.text.CharacterIterator;
import android.icu.impl.CharacterIteration;
import android.icu.lang.UCharacter;
import android.icu.lang.UProperty;
+import android.icu.text.UnicodeSet;
-final class UnhandledBreakEngine implements LanguageBreakEngine {
+/**
+ * @hide Only a subset of ICU is exposed in Android
+ */
+public final class UnhandledBreakEngine implements LanguageBreakEngine {
// TODO: Use two UnicodeSets, one with all frozen sets, one with unfrozen.
// in handleChar(), update the unfrozen version, clone, freeze, replace the frozen one.
@@ -44,7 +48,7 @@
@Override
public int findBreaks(CharacterIterator text, int startPos, int endPos,
- DictionaryBreakEngine.DequeI foundBreaks) {
+ DictionaryBreakEngine.DequeI foundBreaks, boolean isPhraseBreaking) {
UnicodeSet uniset = fHandled;
int c = CharacterIteration.current32(text);
diff --git a/android_icu4j/src/main/java/android/icu/impl/coll/Collation.java b/android_icu4j/src/main/java/android/icu/impl/coll/Collation.java
index abb465f..03a4f69 100644
--- a/android_icu4j/src/main/java/android/icu/impl/coll/Collation.java
+++ b/android_icu4j/src/main/java/android/icu/impl/coll/Collation.java
@@ -254,7 +254,7 @@
* Tag for a lead surrogate code unit.
* Optional optimization for UTF-16 string processing.
* Bits 31..10: Unused, 0.
- * 9.. 8: =0: All associated supplementary code points are unassigned-implict.
+ * 9.. 8: =0: All associated supplementary code points are unassigned-implicit.
* =1: All associated supplementary code points fall back to the base data.
* else: (Normally 2) Look up the data for the supplementary code point.
*/
diff --git a/android_icu4j/src/main/java/android/icu/impl/coll/CollationBuilder.java b/android_icu4j/src/main/java/android/icu/impl/coll/CollationBuilder.java
index 0b77abe..219d37e 100644
--- a/android_icu4j/src/main/java/android/icu/impl/coll/CollationBuilder.java
+++ b/android_icu4j/src/main/java/android/icu/impl/coll/CollationBuilder.java
@@ -472,7 +472,7 @@
// A Hangul syllable completely inside a contraction is ok.
}
// Note: If there is a prefix, then the parser checked that
- // both the prefix and the string beging with NFC boundaries (not Jamo V or T).
+ // both the prefix and the string begin with NFC boundaries (not Jamo V or T).
// Therefore: prefix.isEmpty() || !isJamoVOrT(nfdString.charAt(0))
// (While handling a Hangul syllable, prefixes on Jamo V or T
// would not see the previous Jamo of that syllable.)
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 9bac086..533faee 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
@@ -125,27 +125,27 @@
0,0,0,0,0,0,0,0,1,1,2,3,0,0,0,0,
0,0,0,0,4,0,0,0,0,0,0,0,5,6,7,0,
8,0,9,0xa,0,0,0xb,0xc,0xd,0xe,0xf,0,0,0,0,0x10,
-0x11,0x12,0x13,0,0,0,0x14,0x15,0,0x16,0x17,0,0,0x16,0x18,0x19,
-0,0x16,0x18,0,0,0x16,0x18,0,0,0x16,0x18,0,0,0,0x18,0,
-0,0,0x1a,0,0,0x16,0x18,0,0,0x1b,0x18,0,0,0,0x1c,0,
-0,0x1d,0x1e,0,0,0x1d,0x1e,0,0x1f,0x20,0,0x21,0x22,0,0x23,0,
-0,0x24,0,0,0x18,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0,0,0,0x25,0,0,0,0,0,
+0x11,0x12,0x13,0,0x14,0,0x15,0x16,0,0x17,0x18,0,0,0x17,0x19,0x1a,
+0,0x17,0x19,0,0,0x17,0x19,0,0,0x17,0x19,0,0,0,0x19,0,
+0,0x17,0x1b,0,0,0x17,0x19,0,0,0x1c,0x19,0,0,0,0x1d,0,
+0,0x1e,0x1f,0,0,0x1e,0x1f,0,0x20,0x21,0,0x22,0x23,0,0x24,0,
+0,0x25,0,0,0x19,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0x26,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0,0x26,0x26,0,0,0,0,0x27,0,
-0,0,0,0,0,0x28,0,0,0,0x13,0,0,0,0,0,0,
-0x29,0,0,0x2a,0,0x2b,0x2c,0,0,0x26,0x2d,0x2e,0,0x2f,0,0x30,
-0,0x31,0,0,0,0,0x32,0x33,0,0,0,0,0,0,1,0x34,
+0,0,0,0,0,0,0,0,0x27,0x28,0,0,0,0,0x29,0,
+0,0,0,0,0,0x2a,0,0,0,0x13,0,0,0,0,0,0,
+0x2b,0,0,0x2c,0,0x2d,0x2e,0,0,0x28,0x2f,0x30,0,0x31,0,0x32,
+0,0x33,0,0,0,0,0x34,0x35,0,0,0,0,0,0,1,1,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0x35,0x36,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0x36,0x37,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0x37,0,0,0,0x38,0,0,0,1,
+0,0,0,0,0,0,0,0x38,0,0,0,0x39,0,0,0,1,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0x39,0,0,0x3a,0,0,0,0,0,0,0,0,0,0,0,
+0,0x3a,0,0,0x3b,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
@@ -204,9 +204,9 @@
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0x3b,0x3c,0,0,0x3d,0,0,0,0,0,0,0,0,
-0x23,0x3e,0,0,0,0,0x2d,0x3f,0,0x40,0x41,0,0,0x41,0x2c,0,
-0,0,0,0,0,0x42,0x43,0x44,0,0,0,0,0,0,0,0x18,
+0,0,0,0x3c,0x3d,0,0,0x3e,0,0,0,0,0,0,0,0,
+0x24,0x3f,0,0,0,0,0x2f,0x40,0,0x41,0x42,0,0,0x42,0x43,0,
+0,0,0,0,0,0x44,0x45,0x46,0,0,0,0,0,0,0,0x19,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
@@ -229,7 +229,7 @@
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0x45,0x46,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0x47,0x48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
@@ -246,37 +246,45 @@
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0,0x19,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0x1a,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0
};
+ private static final int[] lcccBits={
+0,0xffffffff,0xffff7fff,0xffff,0xf8,0xfffe0000,0xbfffffff,0xb6,0x7ff0000,0xfffff800,0x10000,0x9fc00000,0x3d9f,0x20000,0xffff0000,0x7ff,
+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
+};
+
private static final byte[] tcccIndex={
0,0,0,0,0,0,2,3,4,5,6,7,0,8,9,0xa,
0xb,0xc,0,0,0,0,0,0,1,1,0xd,0xe,0xf,0x10,0x11,0,
0x12,0x13,0x14,0x15,0x16,0,0x17,0x18,0,0,0,0,0x19,0x1a,0x1b,0,
0x1c,0x1d,0x1e,0x1f,0,0,0x20,0x21,0x22,0x23,0x24,0,0,0,0,0x25,
-0x26,0x27,0x28,0,0,0,0x29,0x2a,0,0x2b,0x2c,0,0,0x2d,0x2e,0x2f,
-0,0x30,0x31,0,0,0x2d,0x32,0,0,0x2d,0x33,0,0,0,0x32,0,
-0,0,0x34,0,0,0x2d,0x32,0,0,0x35,0x32,0,0,0,0x36,0,
-0,0x37,0x38,0,0,0x37,0x38,0,0x39,0x3a,0,0x3b,0x3c,0,0x3d,0,
-0,0x3e,0,0,0x32,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0,0,0,0x3f,0,0,0,0,0,
+0x26,0x27,0x28,0,0x29,0,0x2a,0x2b,0,0x2c,0x2d,0,0,0x2e,0x2f,0x30,
+0,0x31,0x32,0,0,0x2e,0x33,0,0,0x2e,0x34,0,0,0,0x33,0,
+0,0x2e,0x35,0,0,0x2e,0x33,0,0,0x36,0x33,0,0,0,0x37,0,
+0,0x38,0x39,0,0,0x38,0x39,0,0x3a,0x3b,0,0x3c,0x3d,0,0x3e,0,
+0,0x3f,0,0,0x33,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0x40,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0,0x40,0x40,0,0,0,0,0x41,0,
-0,0,0,0,0,0x42,0,0,0,0x28,0,0,0,0,0,0,
-0x43,0,0,0x44,0,0x45,0x46,0,0,0x40,0x47,0x48,0,0x49,0,0x4a,
-0,0x4b,0,0,0,0,0x4c,0x4d,0,0,0,0,0,0,1,0x4e,
-1,1,1,1,0x4f,1,1,0x50,0x51,1,0x52,0x53,1,0x54,0x55,0x56,
-0,0,0,0,0,0,0x57,0x58,0,0x59,0,0,0x5a,0x5b,0x5c,0,
-0x5d,0x5e,0x5f,0x60,0x61,0x62,0,0x63,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0x41,0x42,0,0,0,0,0x43,0,
+0,0,0,0,0,0x44,0,0,0,0x28,0,0,0,0,0,0,
+0x45,0,0,0x46,0,0x47,0x48,0,0,0x42,0x49,0x4a,0,0x4b,0,0x4c,
+0,0x4d,0,0,0,0,0x4e,0x4f,0,0,0,0,0,0,1,1,
+1,1,1,1,0x50,1,1,0x51,0x52,1,0x53,0x54,1,0x55,0x56,0x57,
+0,0,0,0,0,0,0x58,0x59,0,0x5a,0,0,0x5b,0x5c,0x5d,0,
+0x5e,0x5f,0x60,0x61,0x62,0x63,0,0x64,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0x2d,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0x64,0,0,0,0x65,0,0,0,1,
+0,0,0,0,0,0,0x2e,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0x65,0,0,0,0x66,0,0,0,1,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0x66,0x67,0x68,0x69,0x67,0x68,0x6a,0,0,0,0,0,0,0,0,
+0,0x67,0x68,0x69,0x6a,0x68,0x69,0x6b,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
@@ -335,9 +343,9 @@
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0x6b,0x6c,0,0,0x6d,0,0,0,0,0,0,0,0,
-0x3d,0x6e,0,0,0,0,0x47,0x6f,0,0x70,0x71,0,0,0x71,0x46,0,
-0,0,0,0,0,0x72,0x73,0x74,0,0,0,0,0,0,0,0x32,
+0,0,0,0x6c,0x6d,0,0,0x6e,0,0,0,0,0,0,0,0,
+0x3e,0x6f,0,0,0,0,0x49,0x70,0,0x71,0x72,0,0,0x72,0x73,0,
+0,0,0,0,0,0x74,0x75,0x76,0,0,0,0,0,0,0,0x33,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
@@ -360,7 +368,7 @@
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0x75,0x76,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0x77,0x78,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
@@ -377,27 +385,20 @@
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0,0x3f,0x77,0x78,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0x40,0x79,0x7a,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0xe,0,0,0,0,0,0,0,0,0,0,0,0,0,0
};
- private static final int[] lcccBits={
-0,0xffffffff,0xffff7fff,0xffff,0xf8,0xfffe0000,0xbfffffff,0xb6,0x7ff0000,0xfffff800,0x10000,0x9fc00000,0x3d9f,0x20000,0xffff0000,0x7ff,
-0x200ff800,0xfbc00000,0x3eef,0xe000000,0xfff80000,0xfffffffb,0x10000000,0x1e2000,0x2000,0x40000000,0x602000,0x18000000,0x400,0x7000000,0xf00,0x3000000,
-0x2a00000,0x3c3e0000,0xdf,0x40,0x6800000,0xe0000000,0x100000,0x20040000,0x200,0x1800000,0x9fe00001,0xbfff0000,1,0x10,0xff800,0xc00,
-0xc0040,0x800000,0xfff70000,0x31021fd,0xfbffffff,0x1fff0000,0x1ffe2,0x38000,0x80000000,0xfc00,0x6000000,0x3ff08000,0xc0000000,0x30000,0x1000,0x3ffff,
-0x3800,0x80000,0xc19d0000,2,0x400000,0xc0000fd,0x5108000
-};
private static final int[] tcccBits={
0,0xffffffff,0x3e7effbf,0xbe7effbf,0xfffcffff,0x7ef1ff3f,0xfff3f1f8,0x7fffff3f,0x18003,0xdfffe000,0xff31ffcf,0xcfffffff,0xfffc0,0xffff7fff,0xffff,0x1d760,
0x1fc00,0x187c00,0x200708b,0x2000000,0x708b0000,0xc00000,0xf8,0xfccf0006,0x33ffcfc,0xfffe0000,0xbfffffff,0xb6,0x7ff0000,0x7c,0xfffff800,0x10000,
-0x9fc80005,0x3d9f,0x20000,0xffff0000,0x7ff,0x200ff800,0xfbc00000,0x3eef,0xe000000,0xfff80000,0xfffffffb,0x10120200,0xff1e2000,0x10000000,0xb0002000,0x40000000,
-0x10480000,0x4e002000,0x2000,0x30002000,0x602100,0x18000000,0x24000400,0x7000000,0xf00,0x3000000,0x2a00000,0x3d7e0000,0xdf,0x40,0x6800000,0xe0000000,
-0x100000,0x20040000,0x200,0x1800000,0x9fe00001,0xbfff0000,1,0x10,0xff800,0xc00,0xc0040,0x800000,0xfff70000,0x31021fd,0xfbffffff,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,0xc19d0000,2,0x400000,0xc0000fd,0x5108000,0x5f7ffc00,0x7fdb
+0x9fc80005,0x3d9f,0x20000,0xffff0000,0x7ff,0x200ff800,0xfbc00000,0x3eef,0xe000000,0xff000000,0xfffffc00,0xfffffffb,0x10120200,0xff1e2000,0x10000000,0xb0002000,
+0x40000000,0x10480000,0x4e002000,0x2000,0x30002000,0x602100,0x18000000,0x24000400,0x7000000,0xf00,0x3000000,0x2a00000,0x3d7e0000,0xdf,0x40,0x6800000,
+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
};
}
diff --git a/android_icu4j/src/main/java/android/icu/impl/coll/CollationLoader.java b/android_icu4j/src/main/java/android/icu/impl/coll/CollationLoader.java
index d61e666..257365f 100644
--- a/android_icu4j/src/main/java/android/icu/impl/coll/CollationLoader.java
+++ b/android_icu4j/src/main/java/android/icu/impl/coll/CollationLoader.java
@@ -119,7 +119,7 @@
ULocale validLocale = bundle.getULocale();
// Normalize the root locale. See
- // http://bugs.icu-project.org/trac/ticket/10715
+ // https://unicode-org.atlassian.net/browse/ICU-10715
String validLocaleName = validLocale.getName();
if (validLocaleName.length() == 0 || validLocaleName.equals("root")) {
validLocale = ULocale.ROOT;
@@ -187,7 +187,7 @@
// Is this the same as the root collator? If so, then use that instead.
ULocale actualLocale = data.getULocale();
- // http://bugs.icu-project.org/trac/ticket/10715 ICUResourceBundle(root).getULocale() != ULocale.ROOT
+ // https://unicode-org.atlassian.net/browse/ICU-10715 ICUResourceBundle(root).getULocale() != ULocale.ROOT
// Therefore not just if (actualLocale.equals(ULocale.ROOT) && type.equals("standard")) {
String actualLocaleName = actualLocale.getName();
if (actualLocaleName.length() == 0 || actualLocaleName.equals("root")) {
diff --git a/android_icu4j/src/main/java/android/icu/impl/data/TokenIterator.java b/android_icu4j/src/main/java/android/icu/impl/data/TokenIterator.java
index 87a3be4..6610a18 100644
--- a/android_icu4j/src/main/java/android/icu/impl/data/TokenIterator.java
+++ b/android_icu4j/src/main/java/android/icu/impl/data/TokenIterator.java
@@ -89,7 +89,7 @@
public int getLineNumber() {
return reader.getLineNumber();
}
-
+
/**
* Return a string description of the position of the last line
* returned by readLine() or readLineSkippingComments().
@@ -97,7 +97,7 @@
public String describePosition() {
return reader.describePosition() + ':' + (lastpos+1);
}
-
+
/**
* Read the next token from 'this.line' and append it to
* 'this.buf'. Tokens are separated by Pattern_White_Space. Tokens
@@ -129,22 +129,17 @@
buf.append(c);
break;
}
- int[] posref = null;
while (position < line.length()) {
c = line.charAt(position); // 16-bit ok
if (c == '\\') {
- if (posref == null) {
- posref = new int[1];
- }
- posref[0] = position+1;
- int c32 = Utility.unescapeAt(line, posref);
- if (c32 < 0) {
+ int cpAndLength = Utility.unescapeAndLengthAt(line, position + 1);
+ if (cpAndLength < 0) {
throw new RuntimeException("Invalid escape at " +
reader.describePosition() + ':' +
position);
}
- UTF16.append(buf, c32);
- position = posref[0];
+ UTF16.append(buf, Utility.cpFromCodePointAndLength(cpAndLength));
+ position += 1 + Utility.lengthFromCodePointAndLength(cpAndLength);
} else if ((quote != 0 && c == quote) ||
(quote == 0 && PatternProps.isWhiteSpace(c))) {
return ++position;
diff --git a/android_icu4j/src/main/java/android/icu/impl/duration/PeriodBuilder.java b/android_icu4j/src/main/java/android/icu/impl/duration/PeriodBuilder.java
index b809425..2331312 100644
--- a/android_icu4j/src/main/java/android/icu/impl/duration/PeriodBuilder.java
+++ b/android_icu4j/src/main/java/android/icu/impl/duration/PeriodBuilder.java
@@ -35,7 +35,7 @@
/**
* Create a period of the given duration using the provided reference date.
*
- * @param duration the duration in milliseconds from the referenct time
+ * @param duration the duration in milliseconds from the referenced time
* to the target time. A negative duration indicates a time before the
* reference time
* @param referenceDate the reference date from which to compute the period
diff --git a/android_icu4j/src/main/java/android/icu/impl/duration/TimeUnit.java b/android_icu4j/src/main/java/android/icu/impl/duration/TimeUnit.java
index 316feb9..f663e56 100644
--- a/android_icu4j/src/main/java/android/icu/impl/duration/TimeUnit.java
+++ b/android_icu4j/src/main/java/android/icu/impl/duration/TimeUnit.java
@@ -81,7 +81,7 @@
they are measured */
// hack, initialization long array using expressions with 'L' at end doesn't
- // compute entire expression using 'long'. differs from initializtion of
+ // compute entire expression using 'long'. differs from initialization of
// a single constant
static final long[] approxDurations = {
36525L*24*60*60*10, 3045*24*60*60*10L, 7*24*60*60*1000L, 24*60*60*1000L,
diff --git a/android_icu4j/src/main/java/android/icu/impl/locale/LanguageTag.java b/android_icu4j/src/main/java/android/icu/impl/locale/LanguageTag.java
index 00d2ee6..41c3292 100644
--- a/android_icu4j/src/main/java/android/icu/impl/locale/LanguageTag.java
+++ b/android_icu4j/src/main/java/android/icu/impl/locale/LanguageTag.java
@@ -599,6 +599,12 @@
return false;
}
+ public static boolean isTKey(String s) {
+ // tkey = = alpha digit ;
+ return (s.length() == 2) && AsciiUtil.isAlpha(s.charAt(0))
+ && AsciiUtil.isNumeric(s.charAt(1));
+ }
+
public static boolean isExtensionSingleton(String s) {
// singleton = DIGIT ; 0 - 9
// / %x41-57 ; A - W
@@ -661,18 +667,20 @@
public static String canonicalizeExtension(String s) {
s = AsciiUtil.toLowerString(s);
- int found;
- while (s.endsWith("-true")) {
- s = s.substring(0, s.length() - 5); // length of "-true" is 5
- }
- while ((found = s.indexOf("-true-")) > 0) {
- s = s.substring(0, found) + s.substring(found + 5); // length of "-true" is 5
- }
- while (s.endsWith("-yes")) {
- s = s.substring(0, s.length() - 4); // length of "-yes" is 4
- }
- while ((found = s.indexOf("-yes-")) > 0) {
- s = s.substring(0, found) + s.substring(found + 4); // length of "-yes" is 5
+ if (s.startsWith("u-")) {
+ int found;
+ while (s.endsWith("-true")) {
+ s = s.substring(0, s.length() - 5); // length of "-true" is 5
+ }
+ while ((found = s.indexOf("-true-")) > 0) {
+ s = s.substring(0, found) + s.substring(found + 5); // length of "-true" is 5
+ }
+ while (s.endsWith("-yes")) {
+ s = s.substring(0, s.length() - 4); // length of "-yes" is 4
+ }
+ while ((found = s.indexOf("-yes-")) > 0) {
+ s = s.substring(0, found) + s.substring(found + 4); // length of "-yes" is 5
+ }
}
return s;
}
diff --git a/android_icu4j/src/main/java/android/icu/impl/locale/XLikelySubtags.java b/android_icu4j/src/main/java/android/icu/impl/locale/XLikelySubtags.java
index 4e290e0..f8aea60 100644
--- a/android_icu4j/src/main/java/android/icu/impl/locale/XLikelySubtags.java
+++ b/android_icu4j/src/main/java/android/icu/impl/locale/XLikelySubtags.java
@@ -196,8 +196,9 @@
String name = locale.getName(); // Faster than .toLanguageTag().
if (name.startsWith("@x=")) {
String tag = locale.toLanguageTag();
- assert tag.startsWith("x-");
- // Private use language tag x-subtag-subtag...
+ assert tag.startsWith("und-x-");
+ // Private use language tag x-subtag-subtag... which CLDR changes to
+ // und-x-subtag-subtag...
return new LSR(tag, "", "", LSR.EXPLICIT_LSR);
}
return makeMaximizedLsr(locale.getLanguage(), locale.getScript(), locale.getCountry(),
@@ -206,8 +207,9 @@
public LSR makeMaximizedLsrFrom(Locale locale) {
String tag = locale.toLanguageTag();
- if (tag.startsWith("x-")) {
- // Private use language tag x-subtag-subtag...
+ if (tag.startsWith("x-") || tag.startsWith("und-x-")) {
+ // Private use language tag x-subtag-subtag... which CLDR changes to
+ // und-x-subtag-subtag...
return new LSR(tag, "", "", LSR.EXPLICIT_LSR);
}
return makeMaximizedLsr(locale.getLanguage(), locale.getScript(), locale.getCountry(),
diff --git a/android_icu4j/src/main/java/android/icu/impl/number/AffixPatternProvider.java b/android_icu4j/src/main/java/android/icu/impl/number/AffixPatternProvider.java
index 251656a..c7f8820 100644
--- a/android_icu4j/src/main/java/android/icu/impl/number/AffixPatternProvider.java
+++ b/android_icu4j/src/main/java/android/icu/impl/number/AffixPatternProvider.java
@@ -45,4 +45,9 @@
* number instead of rendering the number.
*/
public boolean hasBody();
+
+ /**
+ * True if the currency symbol should replace the decimal separator.
+ */
+ public boolean currencyAsDecimal();
}
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 44b847e..4bacf6d 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
@@ -84,26 +84,29 @@
/** Represents a plus sign symbol '+'. */
public static final int TYPE_PLUS_SIGN = -2;
+ // Represents an approximately sign symbol '~'.
+ public static final int TYPE_APPROXIMATELY_SIGN = -3;
+
/** Represents a percent sign symbol '%'. */
- public static final int TYPE_PERCENT = -3;
+ public static final int TYPE_PERCENT = -4;
/** Represents a permille sign symbol '‰'. */
- public static final int TYPE_PERMILLE = -4;
+ public static final int TYPE_PERMILLE = -5;
/** Represents a single currency symbol '¤'. */
- public static final int TYPE_CURRENCY_SINGLE = -5;
+ public static final int TYPE_CURRENCY_SINGLE = -6;
/** Represents a double currency symbol '¤¤'. */
- public static final int TYPE_CURRENCY_DOUBLE = -6;
+ public static final int TYPE_CURRENCY_DOUBLE = -7;
/** Represents a triple currency symbol '¤¤¤'. */
- public static final int TYPE_CURRENCY_TRIPLE = -7;
+ public static final int TYPE_CURRENCY_TRIPLE = -8;
/** Represents a quadruple currency symbol '¤¤¤¤'. */
- public static final int TYPE_CURRENCY_QUAD = -8;
+ public static final int TYPE_CURRENCY_QUAD = -9;
/** Represents a quintuple currency symbol '¤¤¤¤¤'. */
- public static final int TYPE_CURRENCY_QUINT = -9;
+ public static final int TYPE_CURRENCY_QUINT = -10;
/** Represents a sequence of six or more currency symbols. */
public static final int TYPE_CURRENCY_OVERFLOW = -15;
@@ -275,6 +278,9 @@
return NumberFormat.Field.SIGN;
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;
case TYPE_PERCENT:
return NumberFormat.Field.PERCENT;
case TYPE_PERMILLE:
@@ -511,6 +517,8 @@
return makeTag(offset + count, TYPE_MINUS_SIGN, STATE_BASE, 0);
case '+':
return makeTag(offset + count, TYPE_PLUS_SIGN, STATE_BASE, 0);
+ case '~':
+ return makeTag(offset + count, TYPE_APPROXIMATELY_SIGN, STATE_BASE, 0);
case '%':
return makeTag(offset + count, TYPE_PERCENT, STATE_BASE, 0);
case '‰':
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 c65f2d0..4b33b63 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
@@ -12,6 +12,7 @@
import android.icu.impl.StandardPlural;
import android.icu.impl.UResource;
import android.icu.text.CompactDecimalFormat.CompactStyle;
+import android.icu.text.PluralRules;
import android.icu.util.ICUException;
import android.icu.util.ULocale;
import android.icu.util.UResourceBundle;
@@ -104,10 +105,6 @@
byte magnitude = (byte) (magnitudeEntry.getKey().length() - 1);
for (Map.Entry<String, String> pluralEntry : magnitudeEntry.getValue().entrySet()) {
String pluralString = pluralEntry.getKey().toString();
- if ("0".equals(pluralString) || "1".equals(pluralString)) {
- // TODO(ICU-21258): Handle this case. For now, skip.
- continue;
- }
StandardPlural plural = StandardPlural.fromString(pluralString);
String patternString = pluralEntry.getValue().toString();
patterns[getIndex(magnitude, plural)] = patternString;
@@ -135,14 +132,27 @@
return multipliers[magnitude];
}
- public String getPattern(int magnitude, StandardPlural plural) {
+ public String getPattern(int magnitude, PluralRules rules, DecimalQuantity dq) {
if (magnitude < 0) {
return null;
}
if (magnitude > largestMagnitude) {
magnitude = largestMagnitude;
}
- String patternString = patterns[getIndex(magnitude, plural)];
+ String patternString = null;
+ if (dq.isHasIntegerValue()) {
+ long i = dq.toLong(true);
+ if (i == 0) {
+ patternString = patterns[getIndex(magnitude, StandardPlural.EQ_0)];
+ } else if (i == 1) {
+ patternString = patterns[getIndex(magnitude, StandardPlural.EQ_1)];
+ }
+ if (patternString != null) {
+ return patternString;
+ }
+ }
+ StandardPlural plural = dq.getStandardPlural(rules);
+ patternString = patterns[getIndex(magnitude, plural)];
if (patternString == null && plural != StandardPlural.OTHER) {
// Fall back to "other" plural variant
patternString = patterns[getIndex(magnitude, StandardPlural.OTHER)];
@@ -186,12 +196,6 @@
// Iterate over the plural variants ("one", "other", etc)
UResource.Table pluralVariantsTable = value.getTable();
for (int i4 = 0; pluralVariantsTable.getKeyAndValue(i4, key, value); ++i4) {
-
- if ("0".equals(key.toString()) || "1".equals(key.toString())) {
- // TODO(ICU-21258): Handle this case. For now, skip.
- continue;
- }
-
// Skip this magnitude/plural if we already have it from a child locale.
// Note: This also skips USE_FALLBACK entries.
StandardPlural plural = StandardPlural.fromString(key.toString());
diff --git a/android_icu4j/src/main/java/android/icu/impl/number/CurrencyPluralInfoAffixProvider.java b/android_icu4j/src/main/java/android/icu/impl/number/CurrencyPluralInfoAffixProvider.java
index 213ffac..8edcedf 100644
--- a/android_icu4j/src/main/java/android/icu/impl/number/CurrencyPluralInfoAffixProvider.java
+++ b/android_icu4j/src/main/java/android/icu/impl/number/CurrencyPluralInfoAffixProvider.java
@@ -72,4 +72,9 @@
public boolean hasBody() {
return affixesByPlural[StandardPlural.OTHER.ordinal()].hasBody();
}
+
+ @Override
+ public boolean currencyAsDecimal() {
+ return affixesByPlural[StandardPlural.OTHER.ordinal()].currencyAsDecimal();
+ }
}
diff --git a/android_icu4j/src/main/java/android/icu/impl/number/DecimalFormatProperties.java b/android_icu4j/src/main/java/android/icu/impl/number/DecimalFormatProperties.java
index d701344..001390f 100644
--- a/android_icu4j/src/main/java/android/icu/impl/number/DecimalFormatProperties.java
+++ b/android_icu4j/src/main/java/android/icu/impl/number/DecimalFormatProperties.java
@@ -97,6 +97,7 @@
private transient boolean decimalPatternMatchRequired;
private transient boolean decimalSeparatorAlwaysShown;
private transient boolean exponentSignAlwaysShown;
+ private transient boolean currencyAsDecimal;
private transient int formatWidth;
private transient int groupingSize;
private transient boolean groupingUsed;
@@ -169,6 +170,7 @@
decimalPatternMatchRequired = false;
decimalSeparatorAlwaysShown = false;
exponentSignAlwaysShown = false;
+ currencyAsDecimal = false;
formatWidth = -1;
groupingSize = -1;
groupingUsed = true;
@@ -215,6 +217,7 @@
decimalPatternMatchRequired = other.decimalPatternMatchRequired;
decimalSeparatorAlwaysShown = other.decimalSeparatorAlwaysShown;
exponentSignAlwaysShown = other.exponentSignAlwaysShown;
+ currencyAsDecimal = other.currencyAsDecimal;
formatWidth = other.formatWidth;
groupingSize = other.groupingSize;
groupingUsed = other.groupingUsed;
@@ -262,6 +265,7 @@
eq = eq && _equalsHelper(decimalPatternMatchRequired, other.decimalPatternMatchRequired);
eq = eq && _equalsHelper(decimalSeparatorAlwaysShown, other.decimalSeparatorAlwaysShown);
eq = eq && _equalsHelper(exponentSignAlwaysShown, other.exponentSignAlwaysShown);
+ eq = eq && _equalsHelper(currencyAsDecimal, other.currencyAsDecimal);
eq = eq && _equalsHelper(formatWidth, other.formatWidth);
eq = eq && _equalsHelper(groupingSize, other.groupingSize);
eq = eq && _equalsHelper(groupingUsed, other.groupingUsed);
@@ -325,6 +329,7 @@
hashCode ^= _hashCodeHelper(decimalPatternMatchRequired);
hashCode ^= _hashCodeHelper(decimalSeparatorAlwaysShown);
hashCode ^= _hashCodeHelper(exponentSignAlwaysShown);
+ hashCode ^= _hashCodeHelper(currencyAsDecimal);
hashCode ^= _hashCodeHelper(formatWidth);
hashCode ^= _hashCodeHelper(groupingSize);
hashCode ^= _hashCodeHelper(groupingUsed);
@@ -448,6 +453,10 @@
return exponentSignAlwaysShown;
}
+ public boolean getCurrencyAsDecimal() {
+ return currencyAsDecimal;
+ }
+
public int getFormatWidth() {
return formatWidth;
}
@@ -775,6 +784,18 @@
}
/**
+ * Sets whether the currency symbol should replace the decimal separator.
+ *
+ * @param currencyAsDecimal
+ * Whether the currency symbol should replace the decimal separator.
+ * @return The property bag, for chaining.
+ */
+ public DecimalFormatProperties setCurrencyAsDecimal(boolean currencyAsDecimal) {
+ this.currencyAsDecimal = currencyAsDecimal;
+ return this;
+ }
+
+ /**
* Sets the minimum width of the string output by the formatting pipeline. For example, if padding is
* enabled and paddingWidth is set to 6, formatting the number "3.14159" with the pattern "0.00" will
* result in "··3.14" if '·' is your padding string.
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 ddf546b..b73fc87 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
@@ -145,6 +145,11 @@
public void adjustExponent(int delta);
/**
+ * Resets the DecimalQuantity to the value before adjustMagnitude and adjustExponent.
+ */
+ public void resetExponent();
+
+ /**
* @return Whether the value represented by this {@link DecimalQuantity} is
* zero, infinity, or NaN.
*/
@@ -169,6 +174,18 @@
public BigDecimal toBigDecimal();
+ /**
+ * Returns a long approximating the decimal quantity. A long can only represent the
+ * integral part of the number. Note: this method incorporates the value of
+ * {@code getExponent} (for cases such as compact notation) to return the proper long
+ * value represented by the result.
+ *
+ * @param truncateIfOverflow if false and the number does NOT fit, fails with an error.
+ * See comment about call site guards in DecimalQuantity_AbstractBCD.java
+ * @return A 64-bit integer representation of the internal number.
+ */
+ public long toLong(boolean truncateIfOverflow);
+
public void setToBigDecimal(BigDecimal input);
public int maxRepresentableDigits();
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 0bb8254..046024e 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
@@ -236,6 +236,17 @@
}
@Override
+ public void resetExponent() {
+ adjustMagnitude(exponent);
+ exponent = 0;
+ }
+
+ @Override
+ public boolean isHasIntegerValue() {
+ return scale >= 0;
+ }
+
+ @Override
public StandardPlural getStandardPlural(PluralRules rules) {
if (rules == null) {
// Fail gracefully if the user didn't provide a PluralRules
@@ -267,6 +278,9 @@
return fractionCountWithoutTrailingZeros();
case e:
return getExponent();
+ case c:
+ // Plural operand `c` is currently an alias for `e`.
+ return getExponent();
default:
return Math.abs(toDouble());
}
@@ -602,15 +616,7 @@
scale -= fracLength;
}
- /**
- * Returns a long approximating the internal BCD. A long can only represent the integral part of the
- * number. Note: this method incorporates the value of {@code exponent}
- * (for cases such as compact notation) to return the proper long value
- * represented by the result.
- *
- * @param truncateIfOverflow if false and the number does NOT fit, fails with an assertion error.
- * @return A 64-bit integer representation of the internal BCD.
- */
+ @Override
public long toLong(boolean truncateIfOverflow) {
// NOTE: Call sites should be guarded by fitsInLong(), like this:
// if (dq.fitsInLong()) { /* use dq.toLong() */ } else { /* use some fallback */ }
@@ -920,6 +926,7 @@
// Perform truncation
if (position >= precision) {
+ assert trailingDigit == 0;
setBcdToZero();
scale = magnitude;
} else {
@@ -937,6 +944,10 @@
// do not return: use the bubbling logic below
} else {
setDigitPos(0, (byte) 5);
+ // If the quantity was set to 0, we may need to restore a digit.
+ if (precision == 0) {
+ precision = 1;
+ }
// compact not necessary: digit at position 0 is nonzero
return;
}
@@ -1161,8 +1172,10 @@
protected abstract byte getDigitPos(int position);
/**
- * Sets the digit in the BCD list. This method only sets the digit; it is the caller's responsibility
- * to call {@link #compact} after setting the digit.
+ * Sets the digit in the BCD list. This method only sets the digit; it is the caller's
+ * responsibility to call {@link #compact} after setting the digit, and to ensure
+ * that the precision field is updated to reflect the correct number of digits if a
+ * nonzero digit is added to the decimal.
*
* @param position
* The position of the digit to pop, counted in BCD units from the least significant
diff --git a/android_icu4j/src/main/java/android/icu/impl/number/Grouper.java b/android_icu4j/src/main/java/android/icu/impl/number/Grouper.java
index c5db57b..932a814 100644
--- a/android_icu4j/src/main/java/android/icu/impl/number/Grouper.java
+++ b/android_icu4j/src/main/java/android/icu/impl/number/Grouper.java
@@ -106,7 +106,7 @@
private final short grouping2;
/**
- * The minimum gropuing size, with the following special values:
+ * The minimum grouping size, with the following special values:
* <ul>
* <li>-2 = needs locale data
* <li>-3 = no less than 2
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 33dd783..34f5595 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
@@ -3,6 +3,7 @@
// License & terms of use: http://www.unicode.org/copyright.html
package android.icu.impl.number;
+import java.util.ArrayList;
import java.util.EnumMap;
import java.util.Map;
import java.util.MissingResourceException;
@@ -10,35 +11,47 @@
import android.icu.impl.CurrencyData;
import android.icu.impl.ICUData;
import android.icu.impl.ICUResourceBundle;
+import android.icu.impl.PatternProps;
import android.icu.impl.SimpleFormatterImpl;
import android.icu.impl.StandardPlural;
import android.icu.impl.UResource;
import android.icu.impl.number.Modifier.Signum;
+import android.icu.impl.units.MeasureUnitImpl;
+import android.icu.impl.units.SingleUnitImpl;
+import android.icu.lang.UCharacter;
import android.icu.number.NumberFormatter.UnitWidth;
import android.icu.text.NumberFormat;
import android.icu.text.PluralRules;
import android.icu.util.Currency;
import android.icu.util.ICUException;
import android.icu.util.MeasureUnit;
+import android.icu.util.MeasureUnit.Complexity;
+import android.icu.util.MeasureUnit.MeasurePrefix;
import android.icu.util.ULocale;
import android.icu.util.UResourceBundle;
/**
+ * Takes care of formatting currency and measurement unit names, as well as populating the gender of measure units.
* @hide Only a subset of ICU is exposed in Android
*/
public class LongNameHandler
implements MicroPropsGenerator, ModifierStore, LongNameMultiplexer.ParentlessMicroPropsGenerator {
- private static final int DNAM_INDEX = StandardPlural.COUNT;
- private static final int PER_INDEX = StandardPlural.COUNT + 1;
- static final int ARRAY_LENGTH = StandardPlural.COUNT + 2;
+ private static int i = 0;
+ private static final int DNAM_INDEX = StandardPlural.COUNT + i++;
+ private static final int PER_INDEX = StandardPlural.COUNT + i++;
+ private static final int GENDER_INDEX = StandardPlural.COUNT + i++;
+ static final int ARRAY_LENGTH = StandardPlural.COUNT + i++;
+ // Returns the array index that corresponds to the given pluralKeyword.
private static int getIndex(String pluralKeyword) {
- // pluralKeyword can also be "dnam" or "per"
+ // pluralKeyword can also be "dnam", "per" or "gender"
if (pluralKeyword.equals("dnam")) {
return DNAM_INDEX;
} else if (pluralKeyword.equals("per")) {
return PER_INDEX;
+ } else if (pluralKeyword.equals("gender")) {
+ return GENDER_INDEX;
} else {
return StandardPlural.fromString(pluralKeyword).ordinal();
}
@@ -56,43 +69,215 @@
return result;
}
+ private enum PlaceholderPosition { NONE, BEGINNING, MIDDLE, END }
+
+ private static class ExtractCorePatternResult {
+ String coreUnit;
+ PlaceholderPosition placeholderPosition;
+ char joinerChar;
+ }
+
+ /**
+ * Returns three outputs extracted from pattern.
+ *
+ * @param coreUnit is extracted as per Extract(...) in the spec:
+ * https://unicode.org/reports/tr35/tr35-general.html#compound-units
+ * @param PlaceholderPosition indicates where in the string the placeholder
+ * was found.
+ * @param joinerChar Iff the placeholder was at the beginning or end,
+ * joinerChar contains the space character (if any) that separated the
+ * placeholder from the rest of the pattern. Otherwise, joinerChar is set
+ * to NUL. Only one space character is considered.
+ */
+ private static ExtractCorePatternResult extractCorePattern(String pattern) {
+ ExtractCorePatternResult result = new ExtractCorePatternResult();
+ result.joinerChar = 0;
+ int len = pattern.length();
+ if (pattern.startsWith("{0}")) {
+ result.placeholderPosition = PlaceholderPosition.BEGINNING;
+ if (len > 3 && Character.isSpaceChar(pattern.charAt(3))) {
+ result.joinerChar = pattern.charAt(3);
+ result.coreUnit = pattern.substring(4);
+ } else {
+ result.coreUnit = pattern.substring(3);
+ }
+ } else if (pattern.endsWith("{0}")) {
+ result.placeholderPosition = PlaceholderPosition.END;
+ if (Character.isSpaceChar(pattern.charAt(len - 4))) {
+ result.coreUnit = pattern.substring(0, len - 4);
+ result.joinerChar = pattern.charAt(len - 4);
+ } else {
+ result.coreUnit = pattern.substring(0, len - 3);
+ }
+ } else if (pattern.indexOf("{0}", 1) == -1) {
+ result.placeholderPosition = PlaceholderPosition.NONE;
+ result.coreUnit = pattern;
+ } else {
+ result.placeholderPosition = PlaceholderPosition.MIDDLE;
+ result.coreUnit = pattern;
+ }
+ return result;
+ }
+
//////////////////////////
/// BEGIN DATA LOADING ///
//////////////////////////
- private static final class PluralTableSink extends UResource.Sink {
+ // Gets the gender of a built-in unit: unit must be a built-in. Returns an empty
+ // string both in case of unknown gender and in case of unknown unit.
+ private static String getGenderForBuiltin(ULocale locale, MeasureUnit builtinUnit) {
+ ICUResourceBundle unitsBundle = (ICUResourceBundle) UResourceBundle.getBundleInstance(ICUData.ICU_UNIT_BASE_NAME, locale);
- String[] outArray;
+ StringBuilder key = new StringBuilder();
+ key.append("units/");
+ key.append(builtinUnit.getType());
+ key.append("/");
- public PluralTableSink(String[] outArray) {
+ // Map duration-year-person, duration-week-person, etc. to duration-year, duration-week, ...
+ // TODO(ICU-20400): Get duration-*-person data properly with aliases.
+ if (builtinUnit.getSubtype() != null && builtinUnit.getSubtype().endsWith("-person")) {
+ key.append(builtinUnit.getSubtype(), 0, builtinUnit.getSubtype().length() - 7);
+ } else {
+ key.append(builtinUnit.getSubtype());
+ }
+ key.append("/gender");
+
+ try {
+ return unitsBundle.getWithFallback(key.toString()).getString();
+ } catch (MissingResourceException e) {
+ // TODO(icu-units#28): "$unitRes/gender" does not exist. Do we want to
+ // check whether the parent "$unitRes" exists? Then we could return
+ // U_MISSING_RESOURCE_ERROR for incorrect usage (e.g. builtinUnit not
+ // being a builtin).
+ return "";
+ }
+ }
+
+ // Loads data from a resource tree with paths matching
+ // $key/$pluralForm/$gender/$case, with lateral inheritance for missing cases
+ // and genders.
+ //
+ // An InflectedPluralSink is configured to load data for a specific gender and
+ // case. It loads all plural forms, because selection between plural forms is
+ // dependent upon the value being formatted.
+ //
+ // See data/unit/de.txt and data/unit/fr.txt for examples - take a look at
+ // units/compound/power2: German has case, French has differences for
+ // gender, but no case.
+ //
+ // TODO(icu-units#138): Conceptually similar to PluralTableSink, however the
+ // tree structures are different. After homogenizing the structures, we may be
+ // able to unify the two classes.
+ //
+ // TODO: Spec violation: expects presence of "count" - does not fallback to an
+ // absent "count"! If this fallback were added, getCompoundValue could be
+ // superseded?
+ private static final class InflectedPluralSink extends UResource.Sink {
+ // NOTE: outArray MUST have a length of at least ARRAY_LENGTH. No bounds
+ // checking is performed.
+ public InflectedPluralSink(String gender, String caseVariant, String[] outArray) {
+ this.gender = gender;
+ this.caseVariant = caseVariant;
this.outArray = outArray;
+ for (int i = 0; i < ARRAY_LENGTH; i++) {
+ outArray[i] = null;
+ }
}
+ // See ResourceSink::put().
@Override
public void put(UResource.Key key, UResource.Value value, boolean noFallback) {
UResource.Table pluralsTable = value.getTable();
for (int i = 0; pluralsTable.getKeyAndValue(i, key, value); ++i) {
- int index = getIndex(key.toString());
- if (outArray[index] != null) {
+ String keyString = key.toString();
+ int pluralIndex = getIndex(keyString);
+ if (outArray[pluralIndex] != null) {
+ // We already have a pattern
continue;
}
- String formatString = value.getString();
- outArray[index] = formatString;
+ UResource.Table genderTable = value.getTable();
+ if (loadForPluralForm(genderTable, value)) {
+ outArray[pluralIndex] = value.getString();
+ }
}
}
+
+ // Tries to load data for the configured gender from `genderTable`. The
+ // returned data will be for the configured gender if found, falling
+ // back to "neuter" and no-gender. If none of those are found, null is
+ // returned.
+ private boolean loadForPluralForm(UResource.Table genderTable, UResource.Value value) {
+ if (gender != null && !gender.isEmpty()) {
+ if (loadForGender(genderTable, gender, value)) {
+ return true;
+ }
+ if (gender != "neuter") {
+ if (loadForGender(genderTable, "neuter", value)) {
+ return true;
+ }
+ }
+ }
+ if (loadForGender(genderTable, "_", value)) {
+ return true;
+ }
+ return false;
+ }
+
+ // Tries to load data for the given gender from `genderTable`. Returns true
+ // if found, returning the data in `value`. The returned data will be for
+ // the configured case if found, falling back to "nominative" and no-case if
+ // not.
+ private boolean
+ loadForGender(UResource.Table genderTable, String genderVal, UResource.Value value) {
+ if (!genderTable.findValue(genderVal, value)) {
+ return false;
+ }
+ UResource.Table caseTable = value.getTable();
+ if (caseVariant != null && !caseVariant.isEmpty()) {
+ if (loadForCase(caseTable, caseVariant, value)) {
+ return true;
+ }
+ if (caseVariant != "nominative") {
+ if (loadForCase(caseTable, "nominative", value)) {
+ return true;
+ }
+ }
+ }
+ if (loadForCase(caseTable, "_", value)) {
+ return true;
+ }
+ return false;
+ }
+
+ // Tries to load data for the given case from `caseTable`. Returns null
+ // if not found.
+ private boolean loadForCase(UResource.Table caseTable, String caseValue, UResource.Value value) {
+ if (!caseTable.findValue(caseValue, value)) {
+ return false;
+ }
+ return true;
+ }
+
+ String gender;
+ String caseVariant;
+ String[] outArray;
}
- // NOTE: outArray MUST have at least ARRAY_LENGTH entries. No bounds checking is performed.
+ // Fetches localised formatting patterns for the given subKey. See
+ // documentation for InflectedPluralSink for details.
+ //
+ // Data is loaded for the appropriate unit width, with missing data filled
+ // in from unitsShort.
+ static void getInflectedMeasureData(String subKey,
+ ULocale locale,
+ UnitWidth width,
+ String gender,
+ String caseVariant,
+ String[] outArray) {
+ InflectedPluralSink sink = new InflectedPluralSink(gender, caseVariant, outArray);
+ ICUResourceBundle unitsBundle =
+ (ICUResourceBundle)UResourceBundle.getBundleInstance(ICUData.ICU_UNIT_BASE_NAME, locale);
- static void getMeasureData(
- ULocale locale,
- MeasureUnit unit,
- UnitWidth width,
- String[] outArray) {
- PluralTableSink sink = new PluralTableSink(outArray);
- ICUResourceBundle resource;
- resource = (ICUResourceBundle) UResourceBundle.getBundleInstance(ICUData.ICU_UNIT_BASE_NAME,
- locale);
StringBuilder key = new StringBuilder();
key.append("units");
if (width == UnitWidth.NARROW) {
@@ -101,17 +286,130 @@
key.append("Short");
}
key.append("/");
- key.append(unit.getType());
- key.append("/");
+ key.append(subKey);
+
+ try {
+ unitsBundle.getAllItemsWithFallback(key.toString(), sink);
+ if (width == UnitWidth.SHORT) {
+ return;
+ }
+ } catch (MissingResourceException e) {
+ // 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);
+ }
+
+ private static final class PluralTableSink extends UResource.Sink {
+
+ String[] outArray;
+
+ // NOTE: outArray MUST have at least ARRAY_LENGTH entries. No bounds
+ // checking is performed.
+ public PluralTableSink(String[] outArray) {
+ this.outArray = outArray;
+ }
+
+ @Override
+ public void put(UResource.Key key, UResource.Value value, boolean noFallback) {
+ UResource.Table pluralsTable = value.getTable();
+ for (int i = 0; pluralsTable.getKeyAndValue(i, key, value); ++i) {
+ String keyString = key.toString();
+
+ if (keyString.equals("case")) {
+ continue;
+ }
+
+ int index = getIndex(keyString);
+ if (outArray[index] != null) {
+ continue;
+ }
+
+ String formatString = value.getString();
+ outArray[index] = formatString;
+ }
+ }
+ }
+
+ // NOTE: outArray MUST have at least ARRAY_LENGTH entries. No bounds checking is performed.
+ static void getMeasureData(
+ ULocale locale,
+ MeasureUnit unit,
+ UnitWidth width,
+ String unitDisplayCase,
+ String[] outArray) {
+ PluralTableSink sink = new PluralTableSink(outArray);
+ ICUResourceBundle resource;
+ resource = (ICUResourceBundle) UResourceBundle.getBundleInstance(ICUData.ICU_UNIT_BASE_NAME,
+ locale);
+
+ StringBuilder subKey = new StringBuilder();
+ subKey.append("/");
+ subKey.append(unit.getType());
+ subKey.append("/");
// 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")) {
- key.append(unit.getSubtype(), 0, unit.getSubtype().length() - 7);
+ subKey.append(unit.getSubtype(), 0, unit.getSubtype().length() - 7);
} else {
- key.append(unit.getSubtype());
+ subKey.append(unit.getSubtype());
}
+ if (width != UnitWidth.FULL_NAME) {
+ StringBuilder genderKey = new StringBuilder();
+ genderKey.append("units");
+ genderKey.append(subKey);
+ genderKey.append("/gender");
+ try {
+ outArray[GENDER_INDEX] = resource.getWithFallback(genderKey.toString()).getString();
+ } catch (MissingResourceException e) {
+ // continue
+ }
+ }
+
+ StringBuilder key = new StringBuilder();
+ key.append("units");
+ if (width == UnitWidth.NARROW) {
+ key.append("Narrow");
+ } else if (width == UnitWidth.SHORT) {
+ key.append("Short");
+ }
+ key.append(subKey);
+
+ // Grab desired case first, if available. Then grab nominative case to fill
+ // in the gaps.
+ //
+ // TODO(icu-units#138): check that fallback is spec-compliant
+ if (width == UnitWidth.FULL_NAME
+ && unitDisplayCase != null
+ && !unitDisplayCase.isEmpty()) {
+ StringBuilder caseKey = new StringBuilder();
+ caseKey.append(key);
+ caseKey.append("/case/");
+ caseKey.append(unitDisplayCase);
+
+ try {
+ // TODO(icu-units#138): our fallback logic is not spec-compliant:
+ // lateral fallback should happen before locale fallback. Switch to
+ // getInflectedMeasureData after homogenizing data format? Find a unit
+ // test case that demonstrates the incorrect fallback logic (via
+ // regional variant of an inflected language?)
+ resource.getAllItemsWithFallback(caseKey.toString(), sink);
+ // TODO(icu-units#138): our fallback logic is not spec-compliant: we
+ // check the given case, then go straight to the no-case data. The spec
+ // states we should first look for case="nominative". As part of #138,
+ // either get the spec changed, or add unit tests that warn us if
+ // case="nominative" data differs from no-case data?
+ } catch (MissingResourceException e) {
+ // continue.
+ }
+ }
try {
resource.getAllItemsWithFallback(key.toString(), sink);
} catch (MissingResourceException e) {
@@ -138,7 +436,7 @@
}
}
- private static String getPerUnitFormat(ULocale locale, UnitWidth width) {
+ private static String getCompoundValue(String compoundKey, ULocale locale, UnitWidth width) {
ICUResourceBundle resource;
resource = (ICUResourceBundle) UResourceBundle.getBundleInstance(ICUData.ICU_UNIT_BASE_NAME,
locale);
@@ -149,22 +447,298 @@
} else if (width == UnitWidth.SHORT) {
key.append("Short");
}
- key.append("/compound/per");
+ key.append("/compound/");
+ key.append(compoundKey);
try {
return resource.getStringWithFallback(key.toString());
} catch (MissingResourceException e) {
- throw new IllegalArgumentException(
- "Could not find x-per-y format for " + locale + ", width " + width);
+ if (width == UnitWidth.SHORT) {
+ return "";
+ }
}
+
+ // 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) {
+ return "";
+ }
+ }
+
+ /**
+ * Loads and applies deriveComponent rules from CLDR's
+ * grammaticalFeatures.xml.
+ * <p>
+ * Consider a deriveComponent rule that looks like this:
+ * <pre>
+ * <deriveComponent feature="case" structure="per" value0="compound" value1="nominative"/>
+ * </pre>
+ * Instantiating an instance as follows:
+ * <pre>
+ * DerivedComponents d(loc, "case", "per");
+ * </pre>
+ * <p>
+ * Applying the rule in the XML element above, <code>d.value0("foo")</code>
+ * will be "foo", and <code>d.value1("foo")</code> will be "nominative".
+ * <p>
+ * In case of any kind of failure, value0() and value1() will simply return
+ * "".
+ */
+ private static class DerivedComponents {
+ /**
+ * Constructor.
+ */
+ DerivedComponents(ULocale locale, String feature, String structure) {
+ try {
+ ICUResourceBundle derivationsBundle =
+ (ICUResourceBundle)UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME,
+ "grammaticalFeatures");
+ derivationsBundle = (ICUResourceBundle)derivationsBundle.get("grammaticalData");
+ derivationsBundle = (ICUResourceBundle)derivationsBundle.get("derivations");
+
+ ICUResourceBundle stackBundle;
+ try {
+ // TODO: use standard normal locale resolution algorithms rather than just grabbing
+ // language:
+ stackBundle = (ICUResourceBundle)derivationsBundle.get(locale.getLanguage());
+ } catch (MissingResourceException e) {
+ stackBundle = (ICUResourceBundle)derivationsBundle.get("root");
+ }
+
+ stackBundle = (ICUResourceBundle)stackBundle.get("component");
+ stackBundle = (ICUResourceBundle)stackBundle.get(feature);
+ stackBundle = (ICUResourceBundle)stackBundle.get(structure);
+
+ String value = stackBundle.getString(0);
+ if (value.compareTo("compound") == 0) {
+ this.value0 = null;
+ } else {
+ this.value0 = value;
+ }
+
+ value = stackBundle.getString(1);
+ if (value.compareTo("compound") == 0) {
+ this.value1 = null;
+ } else {
+ this.value1 = value;
+ }
+ } catch (MissingResourceException e) {
+ // Fall back to uninflected.
+ }
+ }
+
+ String value0(String compoundValue) {
+ return (this.value0 != null) ? this.value0 : compoundValue;
+ }
+
+ String value1(String compoundValue) {
+ return (this.value1 != null) ? this.value1 : compoundValue;
+ }
+
+ private String value0 = "", value1 = "";
+ }
+
+ // TODO(icu-units#28): test somehow? Associate with an ICU ticket for adding
+ // testsuite support for testing with synthetic data?
+ /**
+ * Loads and returns the value in rules that look like these:
+ *
+ * <deriveCompound feature="gender" structure="per" value="0"/>
+ * <deriveCompound feature="gender" structure="times" value="1"/>
+ *
+ * Currently a fake example, but spec compliant:
+ * <deriveCompound feature="gender" structure="power" value="feminine"/>
+ *
+ * NOTE: If U_FAILURE(status), returns an empty string.
+ */
+ private static String getDeriveCompoundRule(ULocale locale, String feature, String structure) {
+ ICUResourceBundle derivationsBundle =
+ (ICUResourceBundle) UResourceBundle
+ .getBundleInstance(ICUData.ICU_BASE_NAME, "grammaticalFeatures");
+
+ derivationsBundle = (ICUResourceBundle) derivationsBundle.get("grammaticalData");
+ derivationsBundle = (ICUResourceBundle) derivationsBundle.get("derivations");
+
+ ICUResourceBundle stackBundle;
+ try {
+ // TODO: use standard normal locale resolution algorithms rather than just grabbing language:
+ stackBundle = (ICUResourceBundle) derivationsBundle.get(locale.getLanguage());
+ } catch (MissingResourceException e) {
+ stackBundle = (ICUResourceBundle) derivationsBundle.get("root");
+ }
+
+ stackBundle = (ICUResourceBundle) stackBundle.get("compound");
+ stackBundle = (ICUResourceBundle) stackBundle.get(feature);
+
+ return stackBundle.getString(structure);
+ }
+
+ // Returns the gender string for structures following these rules:
+ //
+ // <deriveCompound feature="gender" structure="per" value="0"/>
+ // <deriveCompound feature="gender" structure="times" value="1"/>
+ //
+ // Fake example:
+ // <deriveCompound feature="gender" structure="power" value="feminine"/>
+ //
+ // data0 and data1 should be pattern arrays (UnicodeString[ARRAY_SIZE]) that
+ // correspond to value="0" and value="1".
+ //
+ // Pass a null to data1 if the structure has no concept of value="1" (e.g.
+ // "prefix" doesn't).
+ private static String
+ getDerivedGender(ULocale locale, String structure, String[] data0, String[] data1) {
+ String val = getDeriveCompoundRule(locale, "gender", structure);
+ if (val.length() == 1) {
+ switch (val.charAt(0)) {
+ case '0':
+ return data0[GENDER_INDEX];
+ case '1':
+ if (data1 == null) {
+ return null;
+ }
+ return data1[GENDER_INDEX];
+ }
+ }
+ return val;
}
////////////////////////
/// END DATA LOADING ///
////////////////////////
+ /**
+ * Calculates the gender of an arbitrary unit: this is the *second*
+ * implementation of an algorithm to do this:
+ *
+ * Gender is also calculated in "processPatternTimes": that code path is
+ * "bottom up", loading the gender for every component of a compound unit
+ * (at the same time as loading the Long Names formatting patterns), even if
+ * the gender is unneeded, then combining the single units' genders into the
+ * compound unit's gender, according to the rules. This algorithm does a
+ * lazier "top-down" evaluation, starting with the compound unit,
+ * calculating which single unit's gender is needed by breaking it down
+ * according to the rules, and then loading only the gender of the one
+ * single unit who's gender is needed.
+ *
+ * For future refactorings:
+ * 1. we could drop processPatternTimes' gender calculation and just call
+ * this function: for UNUM_UNIT_WIDTH_FULL_NAME, the unit gender is in
+ * the very same table as the formatting patterns, so loading it then may
+ * be efficient. For other unit widths however, it needs to be explicitly
+ * looked up anyway.
+ * 2. alternatively, if CLDR is providing all the genders we need such that
+ * we don't need to calculate them in ICU anymore, we could drop this
+ * function and keep only processPatternTimes' calculation. (And optimise
+ * it a bit?)
+ *
+ * @param locale The desired locale.
+ * @param unit The measure unit to calculate the gender for.
+ * @return The gender string for the unit, or an empty string if unknown or
+ * ungendered.
+ */
+ private static String calculateGenderForUnit(ULocale locale, MeasureUnit unit) {
+ MeasureUnitImpl mui = unit.getCopyOfMeasureUnitImpl();
+ ArrayList<SingleUnitImpl> singleUnits = mui.getSingleUnits();
+ int singleUnitIndex = 0;
+ if (mui.getComplexity() == MeasureUnit.Complexity.COMPOUND) {
+ int startSlice = 0;
+ // inclusive
+ int endSlice = singleUnits.size() - 1;
+ assert endSlice > 0 : "COMPOUND units have more than one single unit";
+ if (singleUnits.get(endSlice).getDimensionality() < 0) {
+ // We have a -per- construct
+ String perRule = getDeriveCompoundRule(locale, "gender", "per");
+ if (perRule.length() != 1) {
+ // Fixed gender for -per- units
+ return perRule;
+ }
+ if (perRule.charAt(0) == '1') {
+ // Find the start of the denominator. We already know there is one.
+ while (singleUnits.get(startSlice).getDimensionality() >= 0) {
+ startSlice++;
+ }
+ } else {
+ // Find the end of the numerator
+ while (endSlice >= 0 && singleUnits.get(endSlice).getDimensionality() < 0) {
+ endSlice--;
+ }
+ if (endSlice < 0) {
+ // We have only a denominator, e.g. "per-second".
+ // TODO(icu-units#28): find out what gender to use in the
+ // absence of a first value - mentioned in CLDR-14253.
+ return "";
+ }
+ }
+ }
+ if (endSlice > startSlice) {
+ // We have a -times- construct
+ String timesRule = getDeriveCompoundRule(locale, "gender", "times");
+ if (timesRule.length() != 1) {
+ // Fixed gender for -times- units
+ return timesRule;
+ }
+ if (timesRule.charAt(0) == '0') {
+ endSlice = startSlice;
+ } else {
+ // We assume timesRule[0] == u'1'
+ startSlice = endSlice;
+ }
+ }
+ assert startSlice == endSlice;
+ singleUnitIndex = startSlice;
+ } else if (mui.getComplexity() == MeasureUnit.Complexity.MIXED) {
+ throw new ICUException("calculateGenderForUnit does not support MIXED units");
+ } else {
+ assert mui.getComplexity() == MeasureUnit.Complexity.SINGLE;
+ assert singleUnits.size() == 1;
+ }
+
+ // Now we know which singleUnit's gender we want
+ SingleUnitImpl singleUnit = singleUnits.get(singleUnitIndex);
+ // Check for any power-prefix gender override:
+ if (Math.abs(singleUnit.getDimensionality()) != 1) {
+ String powerRule = getDeriveCompoundRule(locale, "gender", "power");
+ if (powerRule.length() != 1) {
+ // Fixed gender for -powN- units
+ return powerRule;
+ }
+ // powerRule[0] == u'0'; u'1' not currently in spec.
+ }
+ // Check for any SI and binary prefix gender override:
+ if (Math.abs(singleUnit.getDimensionality()) != 1) {
+ String prefixRule = getDeriveCompoundRule(locale, "gender", "prefix");
+ if (prefixRule.length() != 1) {
+ // Fixed gender for -powN- units
+ return prefixRule;
+ }
+ // prefixRule[0] == u'0'; u'1' not currently in spec.
+ }
+ // Now we've boiled it down to the gender of one simple unit identifier:
+ return getGenderForBuiltin(locale, MeasureUnit.forIdentifier(singleUnit.getSimpleUnitID()));
+ }
+
+ private static void maybeCalculateGender(ULocale locale, MeasureUnit unit, String[] outArray) {
+ if (outArray[GENDER_INDEX] == null) {
+ String meterGender = getGenderForBuiltin(locale, MeasureUnit.METER);
+ if (meterGender.isEmpty()) {
+ // No gender for meter: assume ungendered language
+ return;
+ }
+ // We have a gendered language, but are lacking gender for unitRef.
+ outArray[GENDER_INDEX] = calculateGenderForUnit(locale, unit);
+ }
+ }
+
private final Map<StandardPlural, SimpleModifier> modifiers;
private final PluralRules rules;
private final MicroPropsGenerator parent;
+ // Grammatical gender of the formatted result.
+ private String gender = "";
private LongNameHandler(
Map<StandardPlural, SimpleModifier> modifiers,
@@ -177,7 +751,7 @@
public static String getUnitDisplayName(ULocale locale, MeasureUnit unit, UnitWidth width) {
String[] measureData = new String[ARRAY_LENGTH];
- getMeasureData(locale, unit, width, measureData);
+ getMeasureData(locale, unit, width, "", measureData);
return measureData[DNAM_INDEX];
}
@@ -193,103 +767,508 @@
StandardPlural.class);
LongNameHandler result = new LongNameHandler(modifiers, rules, parent);
result.simpleFormatsToModifiers(simpleFormats, NumberFormat.Field.CURRENCY);
+ // TODO(icu-units#28): currency gender?
return result;
}
/**
* Construct a localized LongNameHandler for the specified MeasureUnit.
* <p>
- * Compound units can be constructed via `unit` and `perUnit`. Both of these
- * must then be built-in units.
- * <p>
* Mixed units are not supported, use MixedUnitLongNameHandler.forMeasureUnit.
*
* @param locale The desired locale.
- * @param unit The measure unit to construct a LongNameHandler for. If
- * `perUnit` is also defined, `unit` must not be a mixed unit.
- * @param perUnit If `unit` is a mixed unit, `perUnit` must be null.
+ * @param unit The measure unit to construct a LongNameHandler for.
* @param width Specifies the desired unit rendering.
+ * @param unitDisplayCase Specifies the desired grammatical case. If the
+ * specified case is not found, we fall back to nominative or no-case.
* @param rules Plural rules.
* @param parent Plural rules.
*/
public static LongNameHandler forMeasureUnit(
ULocale locale,
MeasureUnit unit,
- MeasureUnit perUnit,
UnitWidth width,
+ String unitDisplayCase,
PluralRules rules,
MicroPropsGenerator parent) {
- if (perUnit != null) {
- // Compound unit: first try to simplify (e.g., meters per second is its own unit).
- MeasureUnit simplified = unit.product(perUnit.reciprocal());
- if (simplified.getType() != null) {
- unit = simplified;
+ // From https://unicode.org/reports/tr35/tr35-general.html#compound-units -
+ // Points 1 and 2 are mostly handled by MeasureUnit:
+ //
+ // 1. If the unitId is empty or invalid, fail
+ // 2. Put the unitId into normalized order
+ if (unit.getType() != null) {
+ String[] simpleFormats = new String[ARRAY_LENGTH];
+ getMeasureData(locale, unit, width, unitDisplayCase, simpleFormats);
+ maybeCalculateGender(locale, unit, simpleFormats);
+ // TODO(ICU4J): Reduce the number of object creations here?
+ Map<StandardPlural, SimpleModifier> modifiers = new EnumMap<>(StandardPlural.class);
+ LongNameHandler result = new LongNameHandler(modifiers, rules, parent);
+ result.simpleFormatsToModifiers(simpleFormats, NumberFormat.Field.MEASURE_UNIT);
+ if (simpleFormats[GENDER_INDEX] != null) {
+ result.gender = simpleFormats[GENDER_INDEX];
+ }
+ return result;
+ } else {
+ assert unit.getComplexity() != Complexity.MIXED
+ : "Mixed units not supported by LongNameHandler: use MixedUnitLongNameHandler";
+ return forArbitraryUnit(locale, unit, width, unitDisplayCase, rules, parent);
+ }
+ }
+
+ private static LongNameHandler forArbitraryUnit(ULocale loc,
+ MeasureUnit unit,
+ UnitWidth width,
+ String unitDisplayCase,
+ PluralRules rules,
+ MicroPropsGenerator parent) {
+ // Numbered list items are from the algorithms at
+ // https://unicode.org/reports/tr35/tr35-general.html#compound-units:
+ //
+ // 4. Divide the unitId into numerator (the part before the "-per-") and
+ // denominator (the part after the "-per-). If both are empty, fail
+ MeasureUnitImpl fullUnit = unit.getCopyOfMeasureUnitImpl();
+ unit = null;
+ MeasureUnit perUnit = null;
+ // TODO(icu-units#28): lots of inefficiency in the handling of
+ // MeasureUnit/MeasureUnitImpl:
+ for (SingleUnitImpl subUnit : fullUnit.getSingleUnits()) {
+ if (subUnit.getDimensionality() > 0) {
+ if (unit == null) {
+ unit = subUnit.build();
+ } else {
+ unit = unit.product(subUnit.build());
+ }
} else {
- // No simplified form is available.
- return forCompoundUnit(locale, unit, perUnit, width, rules, parent);
+ // It's okay to mutate fullUnit, we made a temporary copy:
+ subUnit.setDimensionality(subUnit.getDimensionality() * -1);
+ if (perUnit == null) {
+ perUnit = subUnit.build();
+ } else {
+ perUnit = perUnit.product(subUnit.build());
+ }
}
}
+ MeasureUnitImpl unitImpl = unit == null ? null : unit.getCopyOfMeasureUnitImpl();
+ MeasureUnitImpl perUnitImpl = perUnit == null ? null : perUnit.getCopyOfMeasureUnitImpl();
- if (unit.getType() == null) {
- // TODO(ICU-20941): Unsanctioned unit. Not yet fully supported.
- throw new UnsupportedOperationException("Unsanctioned unit, not yet supported: " +
- unit.getIdentifier());
- }
+ // TODO(icu-units#28): check placeholder logic, see if it needs to be
+ // present here instead of only in processPatternTimes:
+ //
+ // 5. Set both globalPlaceholder and globalPlaceholderPosition to be empty
- String[] simpleFormats = new String[ARRAY_LENGTH];
- getMeasureData(locale, unit, width, simpleFormats);
- // TODO(ICU4J): Reduce the number of object creations here?
- Map<StandardPlural, SimpleModifier> modifiers = new EnumMap<>(
- StandardPlural.class);
- LongNameHandler result = new LongNameHandler(modifiers, rules, parent);
- result.simpleFormatsToModifiers(simpleFormats, NumberFormat.Field.MEASURE_UNIT);
- return result;
- }
+ DerivedComponents derivedPerCases = new DerivedComponents(loc, "case", "per");
- private static LongNameHandler forCompoundUnit(
- ULocale locale,
- MeasureUnit unit,
- MeasureUnit perUnit,
- UnitWidth width,
- PluralRules rules,
- MicroPropsGenerator parent) {
- if (unit.getType() == null || perUnit.getType() == null) {
- // TODO(ICU-20941): Unsanctioned unit. Not yet fully supported. Set an
- // error code.
- throw new UnsupportedOperationException(
- "Unsanctioned units, not yet supported: " + unit.getIdentifier() + "/" +
- perUnit.getIdentifier());
- }
- String[] primaryData = new String[ARRAY_LENGTH];
- getMeasureData(locale, unit, width, primaryData);
- String[] secondaryData = new String[ARRAY_LENGTH];
- getMeasureData(locale, perUnit, width, secondaryData);
- String perUnitFormat;
- if (secondaryData[PER_INDEX] != null) {
- perUnitFormat = secondaryData[PER_INDEX];
+ // 6. numeratorUnitString
+ String[] numeratorUnitData = new String[ARRAY_LENGTH];
+ processPatternTimes(unitImpl, loc, width, derivedPerCases.value0(unitDisplayCase),
+ numeratorUnitData);
+
+ // 7. denominatorUnitString
+ String[] denominatorUnitData = new String[ARRAY_LENGTH];
+ processPatternTimes(perUnitImpl, loc, width, derivedPerCases.value1(unitDisplayCase),
+ denominatorUnitData);
+
+ // TODO(icu-units#139):
+ // - implement DerivedComponents for "plural/times" and "plural/power":
+ // French has different rules, we'll be producing the wrong results
+ // currently. (Prove via tests!)
+ // - implement DerivedComponents for "plural/per", "plural/prefix",
+ // "case/times", "case/power", and "case/prefix" - although they're
+ // currently hardcoded. Languages with different rules are surely on the
+ // way.
+ //
+ // Currently we only use "case/per", "plural/times", "case/times", and
+ // "case/power".
+ //
+ // This may have impact on multiSimpleFormatsToModifiers(...) below too?
+ // These rules are currently (ICU 69) all the same and hard-coded below.
+ String perUnitPattern = null;
+ if (denominatorUnitData[PER_INDEX] != null) {
+ // If we have no denominator, we obtain the empty string:
+ perUnitPattern = denominatorUnitData[PER_INDEX];
} else {
- String rawPerUnitFormat = getPerUnitFormat(locale, width);
- // rawPerUnitFormat is something like "{0}/{1}"; we need to substitute in the secondary unit.
- // TODO: Lots of thrashing. Improve?
StringBuilder sb = new StringBuilder();
- String compiled = SimpleFormatterImpl
- .compileToStringMinMaxArguments(rawPerUnitFormat, sb, 2, 2);
- String secondaryFormat = getWithPlural(secondaryData, StandardPlural.ONE);
+ // 8. Set perPattern to be getValue([per], locale, length)
+ String rawPerUnitFormat = getCompoundValue("per", loc, width);
+ // rawPerUnitFormat is something like "{0} per {1}"; we need to substitute in the secondary
+ // unit.
+ String perPatternFormatter =
+ SimpleFormatterImpl.compileToStringMinMaxArguments(rawPerUnitFormat, sb, 2, 2);
+ // Plural and placeholder handling for 7. denominatorUnitString:
+ // TODO(icu-units#139): hardcoded:
+ // <deriveComponent feature="plural" structure="per" value0="compound" value1="one"/>
+ String rawDenominatorFormat = getWithPlural(denominatorUnitData, StandardPlural.ONE);
// Some "one" pattern may not contain "{0}". For example in "ar" or "ne" locale.
- String secondaryCompiled = SimpleFormatterImpl
- .compileToStringMinMaxArguments(secondaryFormat, sb, 0, 1);
- String secondaryString = SimpleFormatterImpl.getTextWithNoArguments(secondaryCompiled)
- .trim();
- perUnitFormat = SimpleFormatterImpl.formatCompiledPattern(compiled, "{0}", secondaryString);
+ String denominatorFormatter =
+ SimpleFormatterImpl.compileToStringMinMaxArguments(rawDenominatorFormat, sb, 0, 1);
+ String denominatorString = PatternProps.trimSpaceChar(
+ SimpleFormatterImpl.getTextWithNoArguments(denominatorFormatter));
+
+ // 9. If the denominatorString is empty, set result to
+ // [numeratorString], otherwise set result to format(perPattern,
+ // numeratorString, denominatorString)
+ //
+ // TODO(icu-units#28): Why does UnicodeString need to be explicit in the
+ // following line?
+ perUnitPattern =
+ SimpleFormatterImpl.formatCompiledPattern(perPatternFormatter, "{0}", denominatorString);
}
Map<StandardPlural, SimpleModifier> modifiers = new EnumMap<>(
StandardPlural.class);
LongNameHandler result = new LongNameHandler(modifiers, rules, parent);
- result.multiSimpleFormatsToModifiers(primaryData, perUnitFormat, NumberFormat.Field.MEASURE_UNIT);
+ if (perUnitPattern.length() == 0) {
+ result.simpleFormatsToModifiers(numeratorUnitData, NumberFormat.Field.MEASURE_UNIT);
+ } else {
+ result.multiSimpleFormatsToModifiers(numeratorUnitData, perUnitPattern,
+ NumberFormat.Field.MEASURE_UNIT);
+ }
+
+ // Gender
+ //
+ // TODO(icu-units#28): find out what gender to use in the absence of a first
+ // value - e.g. what's the gender of "per-second"? Mentioned in CLDR-14253.
+ //
+ // gender/per deriveCompound rules don't say:
+ // <deriveCompound feature="gender" structure="per" value="0"/> <!-- gender(gram-per-meter) ←
+ // gender(gram) -->
+ result.gender = getDerivedGender(loc, "per", numeratorUnitData, denominatorUnitData);
return result;
}
+ /**
+ * Roughly corresponds to patternTimes(...) in the spec:
+ * https://unicode.org/reports/tr35/tr35-general.html#compound-units
+ */
+ private static void processPatternTimes(MeasureUnitImpl productUnit,
+ ULocale loc,
+ UnitWidth width,
+ String caseVariant,
+ String[] outArray) {
+ assert outArray[StandardPlural.OTHER.ordinal()] == null : "outArray must have only null values!";
+ assert outArray[PER_INDEX] == null : "outArray must have only null values!";
+
+ if (productUnit == null) {
+ outArray[StandardPlural.OTHER.ordinal()] = "";
+ outArray[PER_INDEX] = "";
+ return;
+ }
+ if (productUnit.getComplexity() == Complexity.MIXED) {
+ // These are handled by MixedUnitLongNameHandler
+ throw new UnsupportedOperationException("Mixed units not supported by LongNameHandler");
+ }
+
+ if (productUnit.getIdentifier() == null) {
+ // TODO(icu-units#28): consider when serialize should be called.
+ // identifier might also be empty for MeasureUnit().
+ productUnit.serialize();
+ }
+ if (productUnit.getIdentifier().length() == 0) {
+ // MeasureUnit(): no units: return empty strings.
+ return;
+ }
+
+ MeasureUnit simpleUnit = MeasureUnit.findBySubType(productUnit.getIdentifier());
+ if (simpleUnit != null) {
+ // TODO(icu-units#145): spec doesn't cover builtin-per-builtin, it
+ // breaks them all down. Do we want to drop this?
+ // - findBySubType isn't super efficient, if we skip it and go to basic
+ // singles, we don't have to construct MeasureUnit's anymore.
+ // - Check all the existing unit tests that fail without this: is it due
+ // to incorrect fallback via getMeasureData?
+ // - Do those unit tests cover this code path representatively?
+ getMeasureData(loc, simpleUnit, width, caseVariant, outArray);
+ maybeCalculateGender(loc, simpleUnit, outArray);
+ return;
+ }
+
+ // 2. Set timesPattern to be getValue(times, locale, length)
+ String timesPattern = getCompoundValue("times", loc, width);
+ StringBuilder sb = new StringBuilder();
+ String timesPatternFormatter = SimpleFormatterImpl.compileToStringMinMaxArguments(timesPattern, sb, 2, 2);
+
+ PlaceholderPosition[] globalPlaceholder = new PlaceholderPosition[ARRAY_LENGTH];
+ char globalJoinerChar = 0;
+ // Numbered list items are from the algorithms at
+ // https://unicode.org/reports/tr35/tr35-general.html#compound-units:
+ //
+ // pattern(...) point 5:
+ // - Set both globalPlaceholder and globalPlaceholderPosition to be empty
+ //
+ // 3. Set result to be empty
+ for (StandardPlural plural : StandardPlural.values()) {
+ int pluralIndex = plural.ordinal();
+ // Initial state: empty string pattern, via all falling back to OTHER:
+ if (plural == StandardPlural.OTHER) {
+ outArray[pluralIndex] = "";
+ } else {
+ outArray[pluralIndex] = null;
+ }
+ globalPlaceholder[pluralIndex] = null;
+ }
+
+ // null represents "compound" (propagate the plural form).
+ String pluralCategory = null;
+ DerivedComponents derivedTimesPlurals = new DerivedComponents(loc, "plural", "times");
+ DerivedComponents derivedTimesCases = new DerivedComponents(loc, "case", "times");
+ DerivedComponents derivedPowerCases = new DerivedComponents(loc, "case", "power");
+
+ // 4. For each single_unit in product_unit
+ ArrayList<SingleUnitImpl> singleUnits = productUnit.getSingleUnits();
+ for (int singleUnitIndex = 0; singleUnitIndex < singleUnits.size(); singleUnitIndex++) {
+ SingleUnitImpl singleUnit = singleUnits.get(singleUnitIndex);
+ String singlePluralCategory;
+ String singleCaseVariant;
+ // TODO(icu-units#28): ensure we have unit tests that change/fail if we
+ // assign incorrect case variants here:
+ if (singleUnitIndex < singleUnits.size() - 1) {
+ // 4.1. If hasMultiple
+ singlePluralCategory = derivedTimesPlurals.value0(pluralCategory);
+ singleCaseVariant = derivedTimesCases.value0(caseVariant);
+ pluralCategory = derivedTimesPlurals.value1(pluralCategory);
+ caseVariant = derivedTimesCases.value1(caseVariant);
+ } else {
+ singlePluralCategory = derivedTimesPlurals.value1(pluralCategory);
+ singleCaseVariant = derivedTimesCases.value1(caseVariant);
+ }
+
+ // 4.2. Get the gender of that single_unit
+ simpleUnit = MeasureUnit.findBySubType(singleUnit.getSimpleUnitID());
+ if (simpleUnit == null) {
+ // Ideally all simple units should be known, but they're not:
+ // 100-kilometer is internally treated as a simple unit, but it is
+ // not a built-in unit and does not have formatting data in CLDR 39.
+ //
+ // TODO(icu-units#28): test (desirable) invariants in unit tests.
+ throw new UnsupportedOperationException("Unsupported sinlgeUnit: " +
+ singleUnit.getSimpleUnitID());
+ }
+ String gender = getGenderForBuiltin(loc, simpleUnit);
+
+ // 4.3. If singleUnit starts with a dimensionality_prefix, such as 'square-'
+ assert singleUnit.getDimensionality() > 0;
+ int dimensionality = singleUnit.getDimensionality();
+ String[] dimensionalityPrefixPatterns = new String[ARRAY_LENGTH];
+ if (dimensionality != 1) {
+ // 4.3.1. set dimensionalityPrefixPattern to be
+ // getValue(that dimensionality_prefix, locale, length, singlePluralCategory,
+ // singleCaseVariant, gender), such as "{0} kwadratowym"
+ StringBuilder dimensionalityKey = new StringBuilder("compound/power");
+ dimensionalityKey.append(dimensionality);
+ try {
+ getInflectedMeasureData(dimensionalityKey.toString(), loc, width, gender,
+ singleCaseVariant, dimensionalityPrefixPatterns);
+ } catch (MissingResourceException e) {
+ // At the time of writing, only pow2 and pow3 are supported.
+ // Attempting to format other powers results in a
+ // U_RESOURCE_TYPE_MISMATCH. We convert the error if we
+ // understand it:
+ if (dimensionality > 3) {
+ throw new UnsupportedOperationException("powerN not supported for N > 3: " +
+ productUnit.getIdentifier());
+ } else {
+ throw e;
+ }
+ }
+
+ // TODO(icu-units#139):
+ // 4.3.2. set singlePluralCategory to be power0(singlePluralCategory)
+
+ // 4.3.3. set singleCaseVariant to be power0(singleCaseVariant)
+ singleCaseVariant = derivedPowerCases.value0(singleCaseVariant);
+ // 4.3.4. remove the dimensionality_prefix from singleUnit
+ singleUnit.setDimensionality(1);
+ }
+
+ // 4.4. if singleUnit starts with an si_prefix, such as 'centi'
+ MeasurePrefix prefix = singleUnit.getPrefix();
+ String prefixPattern = "";
+ if (prefix != MeasurePrefix.ONE) {
+ // 4.4.1. set siPrefixPattern to be getValue(that si_prefix, locale,
+ // length), such as "centy{0}"
+ StringBuilder prefixKey = new StringBuilder();
+ // prefixKey looks like "1024p3" or "10p-2":
+ prefixKey.append(prefix.getBase());
+ prefixKey.append('p');
+ prefixKey.append(prefix.getPower());
+ // Contains a pattern like "centy{0}".
+ prefixPattern = getCompoundValue(prefixKey.toString(), loc, width);
+
+ // 4.4.2. set singlePluralCategory to be prefix0(singlePluralCategory)
+ //
+ // TODO(icu-units#139): that refers to these rules:
+ // <deriveComponent feature="plural" structure="prefix" value0="one" value1="compound"/>
+ // though I'm not sure what other value they might end up having.
+ //
+ // 4.4.3. set singleCaseVariant to be prefix0(singleCaseVariant)
+ //
+ // TODO(icu-units#139): that refers to:
+ // <deriveComponent feature="case" structure="prefix" value0="nominative"
+ // value1="compound"/> but the prefix (value0) doesn't have case, the rest simply
+ // propagates.
+
+ // 4.4.4. remove the si_prefix from singleUnit
+ singleUnit.setPrefix(MeasurePrefix.ONE);
+ }
+
+ // 4.5. Set corePattern to be the getValue(singleUnit, locale, length,
+ // singlePluralCategory, singleCaseVariant), such as "{0} metrem"
+ String[] singleUnitArray = new String[ARRAY_LENGTH];
+ // At this point we are left with a Simple Unit:
+ assert singleUnit.build().getIdentifier().equals(singleUnit.getSimpleUnitID())
+ : "Should be equal: singleUnit.build().getIdentifier() produced " +
+ singleUnit.build().getIdentifier() + ", singleUnit.getSimpleUnitID() produced " +
+ singleUnit.getSimpleUnitID();
+ getMeasureData(loc, singleUnit.build(), width, singleCaseVariant, singleUnitArray);
+
+ // Calculate output gender
+ if (singleUnitArray[GENDER_INDEX] != null) {
+ assert !singleUnitArray[GENDER_INDEX].isEmpty();
+
+ if (prefix != MeasurePrefix.ONE) {
+ singleUnitArray[GENDER_INDEX] =
+ getDerivedGender(loc, "prefix", singleUnitArray, null);
+ }
+
+ if (dimensionality != 1) {
+ singleUnitArray[GENDER_INDEX] =
+ getDerivedGender(loc, "power", singleUnitArray, null);
+ }
+
+ String timesGenderRule = getDeriveCompoundRule(loc, "gender", "times");
+ if (timesGenderRule.length() == 1) {
+ switch (timesGenderRule.charAt(0)) {
+ case '0':
+ if (singleUnitIndex == 0) {
+ assert outArray[GENDER_INDEX] == null;
+ outArray[GENDER_INDEX] = singleUnitArray[GENDER_INDEX];
+ }
+ break;
+ case '1':
+ if (singleUnitIndex == singleUnits.size() - 1) {
+ assert outArray[GENDER_INDEX] == null;
+ outArray[GENDER_INDEX] = singleUnitArray[GENDER_INDEX];
+ }
+ }
+ } else {
+ if (outArray[GENDER_INDEX] == null) {
+ outArray[GENDER_INDEX] = timesGenderRule;
+ }
+ }
+ }
+
+ // Calculate resulting patterns for each plural form
+ for (StandardPlural plural_ : StandardPlural.values()) {
+ StandardPlural plural = plural_;
+ int pluralIndex = plural.ordinal();
+
+ // singleUnitArray[pluralIndex] looks something like "{0} Meter"
+ if (outArray[pluralIndex] == null) {
+ if (singleUnitArray[pluralIndex] == null) {
+ // Let the usual plural fallback mechanism take care of this
+ // plural form
+ continue;
+ } else {
+ // Since our singleUnit can have a plural form that outArray
+ // doesn't yet have (relying on fallback to OTHER), we start
+ // by grabbing it with the normal plural fallback mechanism
+ outArray[pluralIndex] = getWithPlural(outArray, plural);
+ }
+ }
+
+ if (singlePluralCategory != null) {
+ plural = StandardPlural.fromString(singlePluralCategory);
+ }
+
+ // 4.6. Extract(corePattern, coreUnit, placeholder, placeholderPosition) from that
+ // pattern.
+ ExtractCorePatternResult r = extractCorePattern(getWithPlural(singleUnitArray, plural));
+
+ // 4.7 If the position is middle, then fail
+ if (r.placeholderPosition == PlaceholderPosition.MIDDLE) {
+ throw new UnsupportedOperationException();
+ }
+
+ // 4.8. If globalPlaceholder is empty
+ if (globalPlaceholder[pluralIndex] == null) {
+ globalPlaceholder[pluralIndex] = r.placeholderPosition;
+ globalJoinerChar = r.joinerChar;
+ } else {
+ // Expect all units involved to have the same placeholder position
+ assert globalPlaceholder[pluralIndex] == r.placeholderPosition;
+ // TODO(icu-units#28): Do we want to add a unit test that checks
+ // for consistent joiner chars? Probably not, given how
+ // inconsistent they are. File a CLDR ticket with examples?
+ }
+ // Now coreUnit would be just "Meter"
+
+ // 4.9. If siPrefixPattern is not empty
+ if (prefix != MeasurePrefix.ONE) {
+ String prefixCompiled =
+ SimpleFormatterImpl.compileToStringMinMaxArguments(prefixPattern, sb, 1, 1);
+
+ // 4.9.1. Set coreUnit to be the combineLowercasing(locale, length, siPrefixPattern,
+ // coreUnit)
+ // combineLowercasing(locale, length, prefixPattern, coreUnit)
+ //
+ // TODO(icu-units#28): run this only if prefixPattern does not
+ // contain space characters - do languages "as", "bn", "hi",
+ // "kk", etc have concepts of upper and lower case?:
+ if (width == UnitWidth.FULL_NAME) {
+ r.coreUnit = UCharacter.toLowerCase(loc, r.coreUnit);
+ }
+ r.coreUnit = SimpleFormatterImpl.formatCompiledPattern(prefixCompiled, r.coreUnit);
+ }
+
+ // 4.10. If dimensionalityPrefixPattern is not empty
+ if (dimensionality != 1) {
+ String dimensionalityCompiled = SimpleFormatterImpl.compileToStringMinMaxArguments(
+ getWithPlural(dimensionalityPrefixPatterns, plural), sb, 1, 1);
+
+ // 4.10.1. Set coreUnit to be the combineLowercasing(locale, length,
+ // dimensionalityPrefixPattern, coreUnit)
+ // combineLowercasing(locale, length, prefixPattern, coreUnit)
+ //
+ // TODO(icu-units#28): run this only if prefixPattern does not
+ // contain space characters - do languages "as", "bn", "hi",
+ // "kk", etc have concepts of upper and lower case?:
+ if (width == UnitWidth.FULL_NAME) {
+ r.coreUnit = UCharacter.toLowerCase(loc, r.coreUnit);
+ }
+ r.coreUnit =
+ SimpleFormatterImpl.formatCompiledPattern(dimensionalityCompiled, r.coreUnit);
+ }
+
+ if (outArray[pluralIndex].length() == 0) {
+ // 4.11. If the result is empty, set result to be coreUnit
+ outArray[pluralIndex] = r.coreUnit;
+ } else {
+ // 4.12. Otherwise set result to be format(timesPattern, result, coreUnit)
+ outArray[pluralIndex] = SimpleFormatterImpl.formatCompiledPattern(
+ timesPatternFormatter, outArray[pluralIndex], r.coreUnit);
+ }
+ }
+ }
+ for (StandardPlural plural : StandardPlural.values()) {
+ int pluralIndex = plural.ordinal();
+ if (globalPlaceholder[pluralIndex] == PlaceholderPosition.BEGINNING) {
+ StringBuilder tmp = new StringBuilder();
+ tmp.append("{0}");
+ if (globalJoinerChar != 0) {
+ tmp.append(globalJoinerChar);
+ }
+ tmp.append(outArray[pluralIndex]);
+ outArray[pluralIndex] = tmp.toString();
+ } else if (globalPlaceholder[pluralIndex] == PlaceholderPosition.END) {
+ if (globalJoinerChar != 0) {
+ outArray[pluralIndex] = outArray[pluralIndex] + globalJoinerChar;
+ }
+ outArray[pluralIndex] = outArray[pluralIndex] + "{0}";
+ }
+ }
+ }
+
+ /** Sets modifiers to use the patterns from simpleFormats. */
private void simpleFormatsToModifiers(
String[] simpleFormats,
NumberFormat.Field field) {
@@ -305,6 +1284,14 @@
}
}
+ /**
+ * Sets modifiers to a combination of `leadFormats` (one per plural form)
+ * and `trailFormat` appended to each.
+ *
+ * With a leadFormat of "{0}m" and a trailFormat of "{0}/s", it produces a
+ * pattern of "{0}m/s" by inserting each leadFormat pattern into
+ * trailFormat.
+ */
private void multiSimpleFormatsToModifiers(
String[] leadFormats,
String trailFormat,
@@ -313,9 +1300,14 @@
String trailCompiled = SimpleFormatterImpl.compileToStringMinMaxArguments(trailFormat, sb, 1, 1);
for (StandardPlural plural : StandardPlural.VALUES) {
String leadFormat = getWithPlural(leadFormats, plural);
- String compoundFormat = SimpleFormatterImpl.formatCompiledPattern(trailCompiled, leadFormat);
- String compoundCompiled = SimpleFormatterImpl
- .compileToStringMinMaxArguments(compoundFormat, sb, 0, 1);
+ String compoundFormat;
+ if (leadFormat.length() == 0) {
+ compoundFormat = trailFormat;
+ } else {
+ compoundFormat = SimpleFormatterImpl.formatCompiledPattern(trailCompiled, leadFormat);
+ }
+ String compoundCompiled =
+ SimpleFormatterImpl.compileToStringMinMaxArguments(compoundFormat, sb, 0, 1);
Modifier.Parameters parameters = new Modifier.Parameters();
parameters.obj = this;
parameters.signum = null; // Signum ignored
@@ -329,6 +1321,7 @@
MicroProps micros = parent.processQuantity(quantity);
StandardPlural pluralForm = RoundingUtils.getPluralSafe(micros.rounder, rules, quantity);
micros.modOuter = modifiers.get(pluralForm);
+ micros.gender = this.gender;
return micros;
}
@@ -340,6 +1333,7 @@
* Does not call parent.processQuantity, so cannot get a MicroProps instance
* that way. Instead, the instance is passed in as a parameter.
*/
+ @Override
public MicroProps processQuantityWithMicros(DecimalQuantity quantity, MicroProps micros) {
StandardPlural pluralForm = RoundingUtils.getPluralSafe(micros.rounder, rules, quantity);
micros.modOuter = modifiers.get(pluralForm);
diff --git a/android_icu4j/src/main/java/android/icu/impl/number/LongNameMultiplexer.java b/android_icu4j/src/main/java/android/icu/impl/number/LongNameMultiplexer.java
index 024cc12..6a5bc6d 100644
--- a/android_icu4j/src/main/java/android/icu/impl/number/LongNameMultiplexer.java
+++ b/android_icu4j/src/main/java/android/icu/impl/number/LongNameMultiplexer.java
@@ -9,7 +9,6 @@
import android.icu.number.NumberFormatter;
import android.icu.text.PluralRules;
import android.icu.util.MeasureUnit;
-import android.icu.util.NoUnit;
import android.icu.util.ULocale;
/**
@@ -49,6 +48,7 @@
public static LongNameMultiplexer forMeasureUnits(ULocale locale,
List<MeasureUnit> units,
NumberFormatter.UnitWidth width,
+ String unitDisplayCase,
PluralRules rules,
MicroPropsGenerator parent) {
LongNameMultiplexer result = new LongNameMultiplexer(parent);
@@ -64,11 +64,10 @@
result.fMeasureUnits.add(unit);
if (unit.getComplexity() == MeasureUnit.Complexity.MIXED) {
MixedUnitLongNameHandler mlnh = MixedUnitLongNameHandler
- .forMeasureUnit(locale, unit, width, rules, null);
+ .forMeasureUnit(locale, unit, width, unitDisplayCase, rules, null);
result.fHandlers.add(mlnh);
} else {
- LongNameHandler lnh = LongNameHandler
- .forMeasureUnit(locale, unit, NoUnit.BASE, width, rules, null);
+ LongNameHandler lnh = LongNameHandler.forMeasureUnit(locale, unit, width, unitDisplayCase, rules, null);
result.fHandlers.add(lnh);
}
}
@@ -80,7 +79,7 @@
// one of the units provided to the factory function.
@Override
public MicroProps processQuantity(DecimalQuantity quantity) {
- // We call parent->processQuantity() from the Multiplexer, instead of
+ // We call parent.processQuantity() from the Multiplexer, instead of
// letting LongNameHandler handle it: we don't know which LongNameHandler to
// call until we've called the parent!
MicroProps micros = this.fParent.processQuantity(quantity);
diff --git a/android_icu4j/src/main/java/android/icu/impl/number/MacroProps.java b/android_icu4j/src/main/java/android/icu/impl/number/MacroProps.java
index 1a97450..9bf64cc 100644
--- a/android_icu4j/src/main/java/android/icu/impl/number/MacroProps.java
+++ b/android_icu4j/src/main/java/android/icu/impl/number/MacroProps.java
@@ -31,7 +31,9 @@
public IntegerWidth integerWidth;
public Object symbols;
public UnitWidth unitWidth;
+ public String unitDisplayCase;
public SignDisplay sign;
+ public Boolean approximately;
public DecimalSeparatorDisplay decimal;
public Scale scale;
public String usage;
@@ -67,8 +69,12 @@
symbols = fallback.symbols;
if (unitWidth == null)
unitWidth = fallback.unitWidth;
+ if (unitDisplayCase == null)
+ unitDisplayCase = fallback.unitDisplayCase;
if (sign == null)
sign = fallback.sign;
+ if (approximately == null)
+ approximately = fallback.approximately;
if (decimal == null)
decimal = fallback.decimal;
if (affixProvider == null)
@@ -95,7 +101,9 @@
integerWidth,
symbols,
unitWidth,
+ unitDisplayCase,
sign,
+ approximately,
decimal,
affixProvider,
scale,
@@ -123,7 +131,9 @@
&& Objects.equals(integerWidth, other.integerWidth)
&& Objects.equals(symbols, other.symbols)
&& Objects.equals(unitWidth, other.unitWidth)
+ && Objects.equals(unitDisplayCase, other.unitDisplayCase)
&& Objects.equals(sign, other.sign)
+ && Objects.equals(approximately, other.approximately)
&& Objects.equals(decimal, other.decimal)
&& Objects.equals(affixProvider, other.affixProvider)
&& Objects.equals(scale, other.scale)
diff --git a/android_icu4j/src/main/java/android/icu/impl/number/MicroProps.java b/android_icu4j/src/main/java/android/icu/impl/number/MicroProps.java
index 606b0af..be75b29 100644
--- a/android_icu4j/src/main/java/android/icu/impl/number/MicroProps.java
+++ b/android_icu4j/src/main/java/android/icu/impl/number/MicroProps.java
@@ -46,6 +46,10 @@
public Precision rounder;
public Grouper grouping;
public boolean useCurrency;
+ public String gender;
+
+ // Currency symbol to be used as the decimal separator
+ public String currencyAsDecimal;
// Internal fields:
private final boolean immutable;
@@ -55,10 +59,16 @@
// play.
public MeasureUnit outputUnit;
- // In the case of mixed units, this is the set of integer-only units
- // *preceding* the final unit.
+ /**
+ * Contains all the measures.
+ */
public List<Measure> mixedMeasures;
+ /**
+ * Points to quantity position, -1 if the position is not set yet.
+ */
+ public int indexOfQuantity = -1;
+
private volatile boolean exhausted;
/**
diff --git a/android_icu4j/src/main/java/android/icu/impl/number/MixedUnitLongNameHandler.java b/android_icu4j/src/main/java/android/icu/impl/number/MixedUnitLongNameHandler.java
index 9d1e0e5..1d21c87 100644
--- a/android_icu4j/src/main/java/android/icu/impl/number/MixedUnitLongNameHandler.java
+++ b/android_icu4j/src/main/java/android/icu/impl/number/MixedUnitLongNameHandler.java
@@ -53,13 +53,24 @@
* @param mixedUnit The mixed measure unit to construct a
* MixedUnitLongNameHandler for.
* @param width Specifies the desired unit rendering.
+ * @param unitDisplayCase Specifies the desired grammatical case. If the
+ * specified case is not found, we fall back to nominative or no-case.
* @param rules PluralRules instance.
* @param parent MicroPropsGenerator instance.
*/
- public static MixedUnitLongNameHandler forMeasureUnit(ULocale locale, MeasureUnit mixedUnit,
- NumberFormatter.UnitWidth width, PluralRules rules,
+ public static MixedUnitLongNameHandler forMeasureUnit(ULocale locale,
+ MeasureUnit mixedUnit,
+ NumberFormatter.UnitWidth width,
+ String unitDisplayCase,
+ PluralRules rules,
MicroPropsGenerator parent) {
- assert (mixedUnit.getComplexity() == MeasureUnit.Complexity.MIXED);
+ assert mixedUnit.getComplexity() == MeasureUnit.Complexity.MIXED
+ : "MixedUnitLongNameHandler only supports MIXED units";
+ // In ICU4C, in addition to an assert, we return a failure status if the
+ // unit is not mixed (commented by: "Defensive, for production code").
+ // In Java, we don't have efficient access to MeasureUnitImpl, so we
+ // skip this check - relying on unit tests and the assert above to help
+ // enforce the invariant.
MixedUnitLongNameHandler result = new MixedUnitLongNameHandler(rules, parent);
List<MeasureUnit> individualUnits = mixedUnit.splitToSingleUnits();
@@ -68,7 +79,10 @@
for (int i = 0; i < individualUnits.size(); i++) {
// Grab data for each of the components.
String[] unitData = new String[LongNameHandler.ARRAY_LENGTH];
- LongNameHandler.getMeasureData(locale, individualUnits.get(i), width, unitData);
+ LongNameHandler.getMeasureData(locale, individualUnits.get(i), width, unitDisplayCase,
+ unitData);
+ // TODO(ICU-21494): if we add support for gender for mixed units, we may
+ // need LongNameHandler.maybeCalculateGender() here.
result.fMixedUnitData.add(unitData);
}
@@ -129,7 +143,7 @@
*/
@Override
public Modifier getModifier(Modifier.Signum signum, StandardPlural plural) {
- // TODO(units): investigate this method while investigating where
+ // TODO(icu-units#28): investigate this method while investigating where
// LongNameHandler.getModifier() gets used. To be sure it remains
// unreachable:
assert false : "should be unreachable";
@@ -170,14 +184,33 @@
List<String> outputMeasuresList = new ArrayList<>();
+ StandardPlural quantityPlural = StandardPlural.OTHER;
for (int i = 0; i < micros.mixedMeasures.size(); i++) {
+
+ if ( i == micros.indexOfQuantity) {
+ if (i > 0 && quantity.isNegative()) {
+ // If numbers are negative, only the first number needs to have its
+ // negative sign formatted.
+ quantity.negate();
+ }
+
+ quantityPlural = RoundingUtils.getPluralSafe(micros.rounder, rules, quantity);
+ String quantitySimpleFormat = LongNameHandler.getWithPlural(this.fMixedUnitData.get(i), quantityPlural);
+ SimpleFormatter finalFormatter = SimpleFormatter.compileMinMaxArguments(quantitySimpleFormat, 0, 1);
+ outputMeasuresList.add(finalFormatter.format("{0}"));
+
+ continue;
+ }
+
+
DecimalQuantity fdec = new DecimalQuantity_DualStorageBCD(micros.mixedMeasures.get(i).getNumber());
if (i > 0 && fdec.isNegative()) {
// If numbers are negative, only the first number needs to have its
// negative sign formatted.
fdec.negate();
}
- StandardPlural pluralForm = fdec.getStandardPlural(rules);
+
+ StandardPlural pluralForm = RoundingUtils.getPluralSafe(micros.rounder, rules, fdec);
String simpleFormat = LongNameHandler.getWithPlural(this.fMixedUnitData.get(i), pluralForm);
SimpleFormatter compiledFormatter = SimpleFormatter.compileMinMaxArguments(simpleFormat, 0, 1);
@@ -188,18 +221,6 @@
// TODO(icu-units#67): fix field positions
}
- // Reiterated: we have at least one mixedMeasure:
- assert micros.mixedMeasures.size() > 0;
- // Thus if negative, a negative has already been formatted:
- if (quantity.isNegative()) {
- quantity.negate();
- }
-
- String[] finalSimpleFormats = this.fMixedUnitData.get(this.fMixedUnitData.size() - 1);
- StandardPlural finalPlural = RoundingUtils.getPluralSafe(micros.rounder, rules, quantity);
- String finalSimpleFormat = LongNameHandler.getWithPlural(finalSimpleFormats, finalPlural);
- SimpleFormatter finalFormatter = SimpleFormatter.compileMinMaxArguments(finalSimpleFormat, 0, 1);
- outputMeasuresList.add(finalFormatter.format("{0}"));
// Combine list into a "premixed" pattern
String premixedFormatPattern = this.fListFormatter.format(outputMeasuresList);
@@ -211,7 +232,7 @@
Modifier.Parameters params = new Modifier.Parameters();
params.obj = this;
params.signum = Modifier.Signum.POS_ZERO;
- params.plural = finalPlural;
+ params.plural = quantityPlural;
// Return a SimpleModifier for the "premixed" pattern
return new SimpleModifier(premixedCompiled, null, false, params);
}
diff --git a/android_icu4j/src/main/java/android/icu/impl/number/MutablePatternModifier.java b/android_icu4j/src/main/java/android/icu/impl/number/MutablePatternModifier.java
index 3d7baa5..e84af1e 100644
--- a/android_icu4j/src/main/java/android/icu/impl/number/MutablePatternModifier.java
+++ b/android_icu4j/src/main/java/android/icu/impl/number/MutablePatternModifier.java
@@ -44,6 +44,7 @@
Field field;
SignDisplay signDisplay;
boolean perMilleReplacesPercent;
+ boolean approximately;
// Symbol details
DecimalFormatSymbols symbols;
@@ -91,10 +92,13 @@
* Whether to force a plus sign on positive numbers.
* @param perMille
* Whether to substitute the percent sign in the pattern with a permille sign.
+ * @param approximately
+ * Whether to prepend approximately to the sign
*/
- public void setPatternAttributes(SignDisplay signDisplay, boolean perMille) {
+ public void setPatternAttributes(SignDisplay signDisplay, boolean perMille, boolean approximately) {
this.signDisplay = signDisplay;
this.perMilleReplacesPercent = perMille;
+ this.approximately = approximately;
}
/**
@@ -380,6 +384,7 @@
PatternStringUtils.patternInfoToStringBuilder(patternInfo,
isPrefix,
PatternStringUtils.resolveSignDisplay(signDisplay, signum),
+ approximately,
plural,
perMilleReplacesPercent,
currentAffix);
@@ -395,36 +400,14 @@
return symbols.getMinusSignString();
case AffixUtils.TYPE_PLUS_SIGN:
return symbols.getPlusSignString();
+ case AffixUtils.TYPE_APPROXIMATELY_SIGN:
+ return symbols.getApproximatelySignString();
case AffixUtils.TYPE_PERCENT:
return symbols.getPercentString();
case AffixUtils.TYPE_PERMILLE:
return symbols.getPerMillString();
case AffixUtils.TYPE_CURRENCY_SINGLE:
- // UnitWidth ISO, HIDDEN, or NARROW overrides the singular currency symbol.
- if (unitWidth == UnitWidth.ISO_CODE) {
- return currency.getCurrencyCode();
- } else if (unitWidth == UnitWidth.HIDDEN) {
- return "";
- } else {
- int selector;
- switch (unitWidth) {
- case SHORT:
- selector = Currency.SYMBOL_NAME;
- break;
- case NARROW:
- selector = Currency.NARROW_SYMBOL_NAME;
- break;
- case FORMAL:
- selector = Currency.FORMAL_SYMBOL_NAME;
- break;
- case VARIANT:
- selector = Currency.VARIANT_SYMBOL_NAME;
- break;
- default:
- throw new AssertionError();
- }
- return currency.getName(symbols.getULocale(), selector, null);
- }
+ return getCurrencySymbolForUnitWidth();
case AffixUtils.TYPE_CURRENCY_DOUBLE:
return currency.getCurrencyCode();
case AffixUtils.TYPE_CURRENCY_TRIPLE:
@@ -442,4 +425,35 @@
throw new AssertionError();
}
}
+
+ /**
+ * Returns the currency symbol for the unit width specified in setSymbols()
+ */
+ public String getCurrencySymbolForUnitWidth() {
+ // UnitWidth ISO, HIDDEN, or NARROW overrides the singular currency symbol.
+ if (unitWidth == UnitWidth.ISO_CODE) {
+ return currency.getCurrencyCode();
+ } else if (unitWidth == UnitWidth.HIDDEN) {
+ return "";
+ } else {
+ int selector;
+ switch (unitWidth) {
+ case SHORT:
+ selector = Currency.SYMBOL_NAME;
+ break;
+ case NARROW:
+ selector = Currency.NARROW_SYMBOL_NAME;
+ break;
+ case FORMAL:
+ selector = Currency.FORMAL_SYMBOL_NAME;
+ break;
+ case VARIANT:
+ selector = Currency.VARIANT_SYMBOL_NAME;
+ break;
+ default:
+ throw new AssertionError();
+ }
+ return currency.getName(symbols.getULocale(), selector, null);
+ }
+ }
}
diff --git a/android_icu4j/src/main/java/android/icu/impl/number/PatternStringParser.java b/android_icu4j/src/main/java/android/icu/impl/number/PatternStringParser.java
index 1c83604..52be19c 100644
--- a/android_icu4j/src/main/java/android/icu/impl/number/PatternStringParser.java
+++ b/android_icu4j/src/main/java/android/icu/impl/number/PatternStringParser.java
@@ -177,6 +177,11 @@
public boolean hasBody() {
return positive.integerTotal > 0;
}
+
+ @Override
+ public boolean currencyAsDecimal() {
+ return positive.hasCurrencyDecimal;
+ }
}
/**
@@ -201,6 +206,7 @@
public boolean hasPercentSign = false;
public boolean hasPerMilleSign = false;
public boolean hasCurrencySign = false;
+ public boolean hasCurrencyDecimal = false;
public boolean hasMinusSign = false;
public boolean hasPlusSign = false;
@@ -223,6 +229,7 @@
this.offset = 0;
}
+ /** Returns the next code point, or -1 if string is too short. */
int peek() {
if (offset == pattern.length()) {
return -1;
@@ -231,6 +238,20 @@
}
}
+ /** Returns the code point after the next code point, or -1 if string is too short. */
+ int peek2() {
+ if (offset == pattern.length()) {
+ return -1;
+ }
+ int cp1 = pattern.codePointAt(offset);
+ int offset2 = offset + Character.charCount(cp1);
+ if (offset2 == pattern.length()) {
+ return -1;
+ }
+ return pattern.codePointAt(offset2);
+ }
+
+ /** Returns the next code point and then steps forward. */
int next() {
int codePoint = peek();
offset += Character.charCount(codePoint);
@@ -372,6 +393,34 @@
result.hasDecimal = true;
result.widthExceptAffixes += 1;
consumeFractionFormat(state, result);
+ } else if (state.peek() == '¤') {
+ // Check if currency is a decimal separator
+ switch (state.peek2()) {
+ case '#':
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ break;
+ default:
+ // Currency symbol followed by a non-numeric character;
+ // treat as a normal affix.
+ return;
+ }
+ // Currency symbol is followed by a numeric character;
+ // treat as a decimal separator.
+ result.hasCurrencySign = true;
+ result.hasCurrencyDecimal = true;
+ result.hasDecimal = true;
+ result.widthExceptAffixes += 1;
+ state.next(); // consume the symbol
+ consumeFractionFormat(state, result);
}
}
@@ -634,6 +683,9 @@
properties.setDecimalSeparatorAlwaysShown(false);
}
+ // Persist the currency as decimal separator
+ properties.setCurrencyAsDecimal(positive.hasCurrencyDecimal);
+
// Scientific notation settings
if (positive.exponentZeros > 0) {
properties.setExponentSignAlwaysShown(positive.exponentHasPlusSign);
diff --git a/android_icu4j/src/main/java/android/icu/impl/number/PatternStringUtils.java b/android_icu4j/src/main/java/android/icu/impl/number/PatternStringUtils.java
index b36bc13..d1c7215 100644
--- a/android_icu4j/src/main/java/android/icu/impl/number/PatternStringUtils.java
+++ b/android_icu4j/src/main/java/android/icu/impl/number/PatternStringUtils.java
@@ -96,6 +96,7 @@
int minSig = Math.min(properties.getMinimumSignificantDigits(), dosMax);
int maxSig = Math.min(properties.getMaximumSignificantDigits(), dosMax);
boolean alwaysShowDecimal = properties.getDecimalSeparatorAlwaysShown();
+ boolean currencyAsDecimal = properties.getCurrencyAsDecimal();
int exponentDigits = Math.min(properties.getMinimumExponentDigits(), dosMax);
boolean exponentShowPlusSign = properties.getExponentSignAlwaysShown();
AffixPatternProvider affixes = PropertiesAffixPatternProvider.forProperties(properties);
@@ -158,7 +159,11 @@
}
// Decimal separator
if (magnitude == 0 && (alwaysShowDecimal || mN < 0)) {
- sb.append('.');
+ if (currencyAsDecimal) {
+ sb.append('¤');
+ } else {
+ sb.append('.');
+ }
}
if (!useGrouping) {
continue;
@@ -300,6 +305,7 @@
String[][] table = new String[21][2];
int standIdx = toLocalized ? 0 : 1;
int localIdx = toLocalized ? 1 : 0;
+ // TODO: Add approximately sign here?
table[0][standIdx] = "%";
table[0][localIdx] = symbols.getPercentString();
table[1][standIdx] = "‰";
@@ -435,6 +441,7 @@
AffixPatternProvider patternInfo,
boolean isPrefix,
PatternSignType patternSignType,
+ boolean approximately,
StandardPlural plural,
boolean perMilleReplacesPercent,
StringBuilder output) {
@@ -446,7 +453,7 @@
// (If not, we will use the positive subpattern.)
boolean useNegativeAffixPattern = patternInfo.hasNegativeSubpattern()
&& (patternSignType == PatternSignType.NEG
- || (patternInfo.negativeHasMinusSign() && plusReplacesMinusSign));
+ || (patternInfo.negativeHasMinusSign() && (plusReplacesMinusSign || approximately)));
// Resolve the flags for the affix pattern.
int flags = 0;
@@ -468,10 +475,25 @@
} else if (patternSignType == PatternSignType.NEG) {
prependSign = true;
} else {
- prependSign = plusReplacesMinusSign;
+ prependSign = plusReplacesMinusSign || approximately;
}
// Compute the length of the affix pattern.
+ // What symbols should take the place of the sign placeholder?
+ String signSymbols = "-";
+ if (approximately) {
+ if (plusReplacesMinusSign) {
+ signSymbols = "~+";
+ } else if (patternSignType == PatternSignType.NEG) {
+ signSymbols = "~-";
+ } else {
+ signSymbols = "~";
+ }
+ } else if (plusReplacesMinusSign) {
+ signSymbols = "+";
+ }
+
+ // Compute the number of tokens in the affix pattern (signSymbols is considered one token).
int length = patternInfo.length(flags) + (prependSign ? 1 : 0);
// Finally, set the result into the StringBuilder.
@@ -485,8 +507,13 @@
} else {
candidate = patternInfo.charAt(flags, index);
}
- if (plusReplacesMinusSign && candidate == '-') {
- candidate = '+';
+ if (candidate == '-') {
+ if (signSymbols.length() == 1) {
+ candidate = signSymbols.charAt(0);
+ } else {
+ output.append(signSymbols.charAt(0));
+ candidate = signSymbols.charAt(1);
+ }
}
if (perMilleReplacesPercent && candidate == '%') {
candidate = '‰';
@@ -534,6 +561,18 @@
}
break;
+ case NEGATIVE:
+ case ACCOUNTING_NEGATIVE:
+ switch (signum) {
+ case NEG:
+ return PatternSignType.NEG;
+ case NEG_ZERO:
+ case POS_ZERO:
+ case POS:
+ return PatternSignType.POS;
+ }
+ break;
+
case NEVER:
return PatternSignType.POS;
diff --git a/android_icu4j/src/main/java/android/icu/impl/number/PropertiesAffixPatternProvider.java b/android_icu4j/src/main/java/android/icu/impl/number/PropertiesAffixPatternProvider.java
index 749babf..e77707b 100644
--- a/android_icu4j/src/main/java/android/icu/impl/number/PropertiesAffixPatternProvider.java
+++ b/android_icu4j/src/main/java/android/icu/impl/number/PropertiesAffixPatternProvider.java
@@ -12,6 +12,7 @@
private final String negPrefix;
private final String negSuffix;
private final boolean isCurrencyPattern;
+ private final boolean currencyAsDecimal;
public static AffixPatternProvider forProperties(DecimalFormatProperties properties) {
if (properties.getCurrencyPluralInfo() == null) {
@@ -88,7 +89,10 @@
AffixUtils.hasCurrencySymbols(ppp) ||
AffixUtils.hasCurrencySymbols(psp) ||
AffixUtils.hasCurrencySymbols(npp) ||
- AffixUtils.hasCurrencySymbols(nsp));
+ AffixUtils.hasCurrencySymbols(nsp) ||
+ properties.getCurrencyAsDecimal());
+
+ currencyAsDecimal = properties.getCurrencyAsDecimal();
}
@Override
@@ -155,6 +159,11 @@
}
@Override
+ public boolean currencyAsDecimal() {
+ return currencyAsDecimal;
+ }
+
+ @Override
public String toString() {
return super.toString()
+ " {"
diff --git a/android_icu4j/src/main/java/android/icu/impl/number/UnitConversionHandler.java b/android_icu4j/src/main/java/android/icu/impl/number/UnitConversionHandler.java
index b120ae3..407c637 100644
--- a/android_icu4j/src/main/java/android/icu/impl/number/UnitConversionHandler.java
+++ b/android_icu4j/src/main/java/android/icu/impl/number/UnitConversionHandler.java
@@ -3,12 +3,9 @@
// License & terms of use: http://www.unicode.org/copyright.html
package android.icu.impl.number;
-import java.util.List;
-
import android.icu.impl.units.ComplexUnitsConverter;
+import android.icu.impl.units.ConversionRates;
import android.icu.impl.units.MeasureUnitImpl;
-import android.icu.impl.units.UnitsData;
-import android.icu.util.Measure;
import android.icu.util.MeasureUnit;
/**
@@ -24,22 +21,17 @@
private ComplexUnitsConverter fComplexUnitConverter;
/**
- * Constructor.
- *
- * @param inputUnit Specifies the input MeasureUnit. Mixed units are not
- * supported as input (because input is just a single decimal quantity).
- * @param outputUnit Specifies the output MeasureUnit.
- * @param parent The parent MicroPropsGenerator.
+ * @param targetUnit Specifies the output MeasureUnit. The input MeasureUnit
+ * is derived from it: in case of a mixed unit, the biggest unit is
+ * taken as the input unit. If not a mixed unit, the input unit will be
+ * the same as the output unit and no unit conversion takes place.
+ * @param parent The parent MicroPropsGenerator.
*/
- public UnitConversionHandler(MeasureUnit inputUnit,
- MeasureUnit outputUnit,
- MicroPropsGenerator parent) {
- this.fOutputUnit = outputUnit;
+ public UnitConversionHandler(MeasureUnit targetUnit, MicroPropsGenerator parent) {
+ this.fOutputUnit = targetUnit;
this.fParent = parent;
- MeasureUnitImpl inputUnitImpl = MeasureUnitImpl.forIdentifier(inputUnit.getIdentifier());
- MeasureUnitImpl outputUnitImpl = MeasureUnitImpl.forIdentifier(outputUnit.getIdentifier());
- this.fComplexUnitConverter = new ComplexUnitsConverter(inputUnitImpl, outputUnitImpl,
- new UnitsData().getConversionRates());
+ MeasureUnitImpl targetUnitImpl = MeasureUnitImpl.forIdentifier(targetUnit.getIdentifier());
+ this.fComplexUnitConverter = new ComplexUnitsConverter(targetUnitImpl, new ConversionRates());
}
/**
@@ -50,10 +42,11 @@
MicroProps result = this.fParent.processQuantity(quantity);
quantity.roundToInfinity(); // Enables toDouble
- List<Measure> measures = this.fComplexUnitConverter.convert(quantity.toBigDecimal(), result.rounder);
+ ComplexUnitsConverter.ComplexConverterResult complexConverterResult
+ = this.fComplexUnitConverter.convert(quantity.toBigDecimal(), result.rounder);
result.outputUnit = this.fOutputUnit;
- UsagePrefsHandler.mixedMeasuresToMicros(measures, quantity, result);
+ UsagePrefsHandler.mixedMeasuresToMicros(complexConverterResult, quantity, result);
return result;
}
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 d2e887a..a9a299a 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
@@ -4,12 +4,11 @@
package android.icu.impl.number;
import java.math.BigDecimal;
-import java.util.ArrayList;
import java.util.List;
+import android.icu.impl.units.ComplexUnitsConverter;
import android.icu.impl.units.MeasureUnitImpl;
import android.icu.impl.units.UnitsRouter;
-import android.icu.util.Measure;
import android.icu.util.MeasureUnit;
import android.icu.util.ULocale;
@@ -34,24 +33,10 @@
* in measures.
*/
protected static void
- mixedMeasuresToMicros(List<Measure> measures, DecimalQuantity outQuantity, MicroProps outMicros) {
- outMicros.mixedMeasures = new ArrayList<>();
- if (measures.size() > 1) {
- // For debugging
- assert (outMicros.outputUnit.getComplexity() == MeasureUnit.Complexity.MIXED);
-
- // Check that we received the expected number of measurements:
- assert measures.size() == outMicros.outputUnit.splitToSingleUnits().size();
-
- // Mixed units: except for the last value, we pass all values to the
- // LongNameHandler via micros->mixedMeasures.
- for (int i = 0, n = measures.size() - 1; i < n; i++) {
- outMicros.mixedMeasures.add(measures.get(i));
- }
- }
-
- // The last value (potentially the only value) gets passed on via quantity.
- outQuantity.setToBigDecimal((BigDecimal) measures.get(measures.size()- 1).getNumber());
+ mixedMeasuresToMicros(ComplexUnitsConverter.ComplexConverterResult complexConverterResult, DecimalQuantity quantity, MicroProps outMicros) {
+ outMicros.mixedMeasures = complexConverterResult.measures;
+ outMicros.indexOfQuantity = complexConverterResult.indexOfQuantity;
+ quantity.setToBigDecimal((BigDecimal) outMicros.mixedMeasures.get(outMicros.indexOfQuantity).getNumber());
}
/**
@@ -78,11 +63,8 @@
quantity.roundToInfinity(); // Enables toDouble
final UnitsRouter.RouteResult routed = fUnitsRouter.route(quantity.toBigDecimal(), micros);
-
- final List<Measure> routedMeasures = routed.measures;
micros.outputUnit = routed.outputUnit.build();
-
- UsagePrefsHandler.mixedMeasuresToMicros(routedMeasures, quantity, micros);
+ UsagePrefsHandler.mixedMeasuresToMicros(routed.complexConverterResult, quantity, micros);
return micros;
}
}
diff --git a/android_icu4j/src/main/java/android/icu/impl/number/parse/AffixMatcher.java b/android_icu4j/src/main/java/android/icu/impl/number/parse/AffixMatcher.java
index 7651470..7cd4ffb 100644
--- a/android_icu4j/src/main/java/android/icu/impl/number/parse/AffixMatcher.java
+++ b/android_icu4j/src/main/java/android/icu/impl/number/parse/AffixMatcher.java
@@ -110,9 +110,11 @@
}
// Generate Prefix
+ // TODO: Handle approximately sign?
PatternStringUtils.patternInfoToStringBuilder(patternInfo,
true,
type,
+ false,
StandardPlural.OTHER,
false,
sb);
@@ -120,9 +122,11 @@
.fromAffixPattern(sb.toString(), factory, parseFlags);
// Generate Suffix
+ // TODO: Handle approximately sign?
PatternStringUtils.patternInfoToStringBuilder(patternInfo,
false,
type,
+ false,
StandardPlural.OTHER,
false,
sb);
diff --git a/android_icu4j/src/main/java/android/icu/impl/number/parse/CombinedCurrencyMatcher.java b/android_icu4j/src/main/java/android/icu/impl/number/parse/CombinedCurrencyMatcher.java
index dba1592..d9aa18d 100644
--- a/android_icu4j/src/main/java/android/icu/impl/number/parse/CombinedCurrencyMatcher.java
+++ b/android_icu4j/src/main/java/android/icu/impl/number/parse/CombinedCurrencyMatcher.java
@@ -76,7 +76,7 @@
// TODO: Figure out how to make this faster and re-enable.
// Computing the "lead code points" set for fastpathing is too slow to use in production.
- // See http://bugs.icu-project.org/trac/ticket/13584
+ // See https://unicode-org.atlassian.net/browse/ICU-13584
// // Compute the full set of characters that could be the first in a currency to allow for
// // efficient smoke test.
// leadCodePoints = new UnicodeSet();
diff --git a/android_icu4j/src/main/java/android/icu/impl/number/parse/DecimalMatcher.java b/android_icu4j/src/main/java/android/icu/impl/number/parse/DecimalMatcher.java
index 8b74020..d638e75 100644
--- a/android_icu4j/src/main/java/android/icu/impl/number/parse/DecimalMatcher.java
+++ b/android_icu4j/src/main/java/android/icu/impl/number/parse/DecimalMatcher.java
@@ -26,7 +26,7 @@
private final boolean groupingDisabled;
// Fraction grouping parsing is disabled for now but could be enabled later.
- // See http://bugs.icu-project.org/trac/ticket/10794
+ // See https://unicode-org.atlassian.net/browse/ICU-10794
// private final boolean fractionGrouping;
/** If true, do not accept numbers in the fraction */
@@ -103,7 +103,7 @@
grouping2 = grouper.getSecondary();
// Fraction grouping parsing is disabled for now but could be enabled later.
- // See http://bugs.icu-project.org/trac/ticket/10794
+ // See https://unicode-org.atlassian.net/browse/ICU-10794
// fractionGrouping = 0 != (parseFlags & ParsingUtils.PARSE_FLAG_FRACTION_GROUPING_ENABLED);
}
diff --git a/android_icu4j/src/main/java/android/icu/impl/number/parse/InfinityMatcher.java b/android_icu4j/src/main/java/android/icu/impl/number/parse/InfinityMatcher.java
index 505a3e6..71f86b3 100644
--- a/android_icu4j/src/main/java/android/icu/impl/number/parse/InfinityMatcher.java
+++ b/android_icu4j/src/main/java/android/icu/impl/number/parse/InfinityMatcher.java
@@ -3,8 +3,6 @@
// License & terms of use: http://www.unicode.org/copyright.html
package android.icu.impl.number.parse;
-import static android.icu.impl.number.parse.ParsingUtils.safeContains;
-
import android.icu.impl.StaticUnicodeSets;
import android.icu.impl.StringSegment;
import android.icu.text.DecimalFormatSymbols;
@@ -20,7 +18,7 @@
public static InfinityMatcher getInstance(DecimalFormatSymbols symbols) {
String symbolString = symbols.getInfinity();
- if (safeContains(DEFAULT.uniSet, symbolString)) {
+ if (DEFAULT.uniSet.contains(symbolString)) {
return DEFAULT;
} else {
return new InfinityMatcher(symbolString);
diff --git a/android_icu4j/src/main/java/android/icu/impl/number/parse/MinusSignMatcher.java b/android_icu4j/src/main/java/android/icu/impl/number/parse/MinusSignMatcher.java
index bd67d6c..150a167 100644
--- a/android_icu4j/src/main/java/android/icu/impl/number/parse/MinusSignMatcher.java
+++ b/android_icu4j/src/main/java/android/icu/impl/number/parse/MinusSignMatcher.java
@@ -3,8 +3,6 @@
// License & terms of use: http://www.unicode.org/copyright.html
package android.icu.impl.number.parse;
-import static android.icu.impl.number.parse.ParsingUtils.safeContains;
-
import android.icu.impl.StaticUnicodeSets;
import android.icu.impl.StringSegment;
import android.icu.text.DecimalFormatSymbols;
@@ -21,7 +19,7 @@
public static MinusSignMatcher getInstance(DecimalFormatSymbols symbols, boolean allowTrailing) {
String symbolString = symbols.getMinusSignString();
- if (safeContains(DEFAULT.uniSet, symbolString)) {
+ if (DEFAULT.uniSet.contains(symbolString)) {
return allowTrailing ? DEFAULT_ALLOW_TRAILING : DEFAULT;
} else {
return new MinusSignMatcher(symbolString, allowTrailing);
diff --git a/android_icu4j/src/main/java/android/icu/impl/number/parse/ParsingUtils.java b/android_icu4j/src/main/java/android/icu/impl/number/parse/ParsingUtils.java
index 306ef5e..62c33aa 100644
--- a/android_icu4j/src/main/java/android/icu/impl/number/parse/ParsingUtils.java
+++ b/android_icu4j/src/main/java/android/icu/impl/number/parse/ParsingUtils.java
@@ -35,7 +35,9 @@
output.add(range.codepoint, range.codepointEnd);
}
for (String str : input.strings()) {
- output.add(str.codePointAt(0));
+ if (!str.isEmpty()) {
+ output.add(str.codePointAt(0));
+ }
}
}
@@ -44,10 +46,4 @@
output.add(input.codePointAt(0));
}
}
-
- // TODO: Remove this helper function (and update call sites) when #13805 is fixed
- public static boolean safeContains(UnicodeSet uniset, CharSequence str) {
- return str.length() != 0 && uniset.contains(str);
- }
-
}
diff --git a/android_icu4j/src/main/java/android/icu/impl/number/parse/PlusSignMatcher.java b/android_icu4j/src/main/java/android/icu/impl/number/parse/PlusSignMatcher.java
index 0abdfd9..53f6a33 100644
--- a/android_icu4j/src/main/java/android/icu/impl/number/parse/PlusSignMatcher.java
+++ b/android_icu4j/src/main/java/android/icu/impl/number/parse/PlusSignMatcher.java
@@ -3,8 +3,6 @@
// License & terms of use: http://www.unicode.org/copyright.html
package android.icu.impl.number.parse;
-import static android.icu.impl.number.parse.ParsingUtils.safeContains;
-
import android.icu.impl.StaticUnicodeSets;
import android.icu.impl.StringSegment;
import android.icu.text.DecimalFormatSymbols;
@@ -21,7 +19,7 @@
public static PlusSignMatcher getInstance(DecimalFormatSymbols symbols, boolean allowTrailing) {
String symbolString = symbols.getPlusSignString();
- if (safeContains(DEFAULT.uniSet, symbolString)) {
+ if (DEFAULT.uniSet.contains(symbolString)) {
return allowTrailing ? DEFAULT_ALLOW_TRAILING : DEFAULT;
} else {
return new PlusSignMatcher(symbolString, allowTrailing);
diff --git a/android_icu4j/src/main/java/android/icu/impl/number/parse/ScientificMatcher.java b/android_icu4j/src/main/java/android/icu/impl/number/parse/ScientificMatcher.java
index 88af56c..3d4d582 100644
--- a/android_icu4j/src/main/java/android/icu/impl/number/parse/ScientificMatcher.java
+++ b/android_icu4j/src/main/java/android/icu/impl/number/parse/ScientificMatcher.java
@@ -3,8 +3,6 @@
// License & terms of use: http://www.unicode.org/copyright.html
package android.icu.impl.number.parse;
-import static android.icu.impl.number.parse.ParsingUtils.safeContains;
-
import android.icu.impl.StaticUnicodeSets;
import android.icu.impl.StringSegment;
import android.icu.impl.number.DecimalQuantity_DualStorageBCD;
@@ -38,9 +36,9 @@
ignorablesMatcher = IgnorablesMatcher.getInstance(ParsingUtils.PARSE_FLAG_STRICT_IGNORABLES);
String minusSign = symbols.getMinusSignString();
- customMinusSign = safeContains(minusSignSet(), minusSign) ? null : minusSign;
+ customMinusSign = minusSignSet().contains(minusSign) ? null : minusSign;
String plusSign = symbols.getPlusSignString();
- customPlusSign = safeContains(plusSignSet(), plusSign) ? null : plusSign;
+ customPlusSign = plusSignSet().contains(plusSign) ? null : plusSign;
}
private static UnicodeSet minusSignSet() {
diff --git a/android_icu4j/src/main/java/android/icu/impl/number/range/PrefixInfixSuffixLengthHelper.java b/android_icu4j/src/main/java/android/icu/impl/number/range/PrefixInfixSuffixLengthHelper.java
index 22b4172..f2af55b 100644
--- a/android_icu4j/src/main/java/android/icu/impl/number/range/PrefixInfixSuffixLengthHelper.java
+++ b/android_icu4j/src/main/java/android/icu/impl/number/range/PrefixInfixSuffixLengthHelper.java
@@ -29,4 +29,8 @@
public int index3() {
return lengthPrefix + length1 + lengthInfix + length2;
}
+
+ public int index4() {
+ return lengthPrefix + length1 + lengthInfix + length2 + lengthSuffix;
+ }
}
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 109f8f5..2d8bcf1 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
@@ -4,6 +4,7 @@
package android.icu.impl.units;
import java.math.BigDecimal;
+import java.math.BigInteger;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Collections;
@@ -13,50 +14,99 @@
import android.icu.impl.number.DecimalQuantity_DualStorageBCD;
import android.icu.number.Precision;
import android.icu.util.Measure;
-import android.icu.util.MeasureUnit;
/**
- * Converts from single or compound unit to single, compound or mixed units.
- * For example, from `meter` to `foot+inch`.
+ * Converts from single or compound unit to single, compound or mixed units. For example, from `meter` to `foot+inch`.
* <p>
- * DESIGN:
- * This class uses `UnitConverter` in order to perform the single converter (i.e. converters from a
- * single unit to another single unit). Therefore, `ComplexUnitsConverter` class contains multiple
- * instances of the `UnitConverter` to perform the conversion.
+ * DESIGN: This class uses <code>UnitsConverter</code> in order to perform the single converter (i.e. converters from
+ * a single unit to another single unit). Therefore, <code>ComplexUnitsConverter</code> class contains multiple
+ * instances of the <code>UnitsConverter</code> to perform the conversion.
* @hide Only a subset of ICU is exposed in Android
*/
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<UnitConverter> unitConverters_;
- // Individual units of mixed units, sorted big to small
- private ArrayList<MeasureUnitImpl> units_;
- // Individual units of mixed units, sorted in desired output order
- private ArrayList<MeasureUnit> outputUnits_;
+ private 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_;
+ private MeasureUnitImpl inputUnit_;
/**
- * Constructor of `ComplexUnitsConverter`.
- * NOTE:
- * - inputUnit and outputUnits must be under the same category
- * - e.g. meter to feet and inches --> all of them are length units.
+ * Constructs <code>ComplexUnitsConverter</code> for an <code>inputUnit</code> that could be Single, Compound or
+ * Mixed. In case of: 1- Single and Compound units, the conversion will not perform anything, the input will be
+ * equal to the output. 2- Mixed Unit the conversion will consider the input in the biggest unit. and will convert
+ * it to be spread throw the input units. For example: if input unit is "inch-and-foot", and the input is 2.5. The
+ * converter will consider the input value in "foot", because foot is the biggest unit. Then, it will convert 2.5
+ * feet to "inch-and-foot".
*
- * @param inputUnit represents the source unit. (should be single or compound unit).
- * @param outputUnits represents the output unit. could be any type. (single, compound or mixed).
+ * @param targetUnit
+ * represents the input unit. could be any type. (single, compound or mixed).
+ */
+ public ComplexUnitsConverter(MeasureUnitImpl targetUnit, ConversionRates conversionRates) {
+ this.units_ = targetUnit.extractIndividualUnitsWithIndices();
+ assert (!this.units_.isEmpty());
+
+ // Assign the biggest unit to inputUnit_.
+ this.inputUnit_ = this.units_.get(0).unitImpl;
+ MeasureUnitImpl.MeasureUnitImplComparator comparator = new MeasureUnitImpl.MeasureUnitImplComparator(
+ conversionRates);
+ for (MeasureUnitImpl.MeasureUnitImplWithIndex unitWithIndex : this.units_) {
+ if (comparator.compare(unitWithIndex.unitImpl, this.inputUnit_) > 0) {
+ this.inputUnit_ = unitWithIndex.unitImpl;
+ }
+ }
+
+ this.init(conversionRates);
+ }
+
+ /**
+ * Constructs <code>ComplexUnitsConverter</code> NOTE: - inputUnit and outputUnits must be under the same category -
+ * e.g. meter to feet and inches --> all of them are length units.
+ *
+ * @param inputUnitIdentifier
+ * represents the source unit identifier. (should be single or compound unit).
+ * @param outputUnitsIdentifier
+ * represents the output unit identifier. could be any type. (single, compound or mixed).
+ */
+ public ComplexUnitsConverter(String inputUnitIdentifier, String outputUnitsIdentifier) {
+ this(
+ MeasureUnitImpl.forIdentifier(inputUnitIdentifier),
+ MeasureUnitImpl.forIdentifier(outputUnitsIdentifier),
+ new ConversionRates()
+ );
+ }
+
+ /**
+ * Constructs <code>ComplexUnitsConverter</code> NOTE: - inputUnit and outputUnits must be under the same category -
+ * e.g. meter to feet and inches --> all of them are length units.
+ *
+ * @param inputUnit
+ * represents the source unit. (should be single or compound unit).
+ * @param outputUnits
+ * represents the output unit. could be any type. (single, compound or mixed).
+ * @param conversionRates
+ * a ConversionRates instance containing the unit conversion rates.
*/
public ComplexUnitsConverter(MeasureUnitImpl inputUnit, MeasureUnitImpl outputUnits,
- ConversionRates conversionRates) {
- units_ = outputUnits.extractIndividualUnits();
- outputUnits_ = new ArrayList<>(units_.size());
- for (MeasureUnitImpl itr : units_) {
- outputUnits_.add(itr.build());
- }
- assert (!units_.isEmpty());
+ ConversionRates conversionRates) {
+ this.inputUnit_ = inputUnit;
+ this.units_ = outputUnits.extractIndividualUnitsWithIndices();
+ assert (!this.units_.isEmpty());
+ this.init(conversionRates);
+ }
+
+ /**
+ * Sorts units_, which must be populated before calling this, and populates
+ * unitsConverters_.
+ */
+ private void init(ConversionRates conversionRates) {
// Sort the units in a descending order.
- Collections.sort(
- this.units_,
- Collections.reverseOrder(new MeasureUnitImpl.MeasureUnitImplComparator(conversionRates)));
-
+ Collections.sort(this.units_,
+ Collections.reverseOrder(new MeasureUnitImpl.MeasureUnitImplWithIndexComparator(conversionRates)));
// If the `outputUnits` is `UMEASURE_UNIT_MIXED` such as `foot+inch`. Thus means there is more than one unit
// and In this case we need more converters to convert from the `inputUnit` to the first unit in the
@@ -72,29 +122,42 @@
// 2. convert the residual of 6.56168 feet (0.56168) to inches, which will be (6.74016
// inches)
// 3. then, the final result will be (6 feet and 6.74016 inches)
- unitConverters_ = new ArrayList<>();
+ unitsConverters_ = new ArrayList<>();
for (int i = 0, n = units_.size(); i < n; i++) {
if (i == 0) { // first element
- unitConverters_.add(new UnitConverter(inputUnit, units_.get(i), conversionRates));
+ unitsConverters_.add(new UnitsConverter(this.inputUnit_, units_.get(i).unitImpl, conversionRates));
} else {
- unitConverters_.add(new UnitConverter(units_.get(i - 1), units_.get(i), conversionRates));
+ unitsConverters_
+ .add(new UnitsConverter(units_.get(i - 1).unitImpl, units_.get(i).unitImpl, conversionRates));
}
}
}
/**
- * Returns true if the specified `quantity` of the `inputUnit`, expressed in terms of the biggest
- * unit in the MeasureUnit `outputUnit`, is greater than or equal to `limit`.
+ * Returns true if the specified `quantity` of the `inputUnit`, expressed in terms of the biggest unit in the
+ * MeasureUnit `outputUnit`, is greater than or equal to `limit`.
* <p>
- * For example, if the input unit is `meter` and the target unit is `foot+inch`. Therefore, this
- * function will convert the `quantity` from `meter` to `foot`, then, it will compare the value in
- * `foot` with the `limit`.
+ * For example, if the input unit is `meter` and the target unit is `foot+inch`. Therefore, this function will
+ * convert the `quantity` from `meter` to `foot`, then, it will compare the value in `foot` with the `limit`.
*/
public boolean greaterThanOrEqual(BigDecimal quantity, BigDecimal limit) {
assert !units_.isEmpty();
// NOTE: First converter converts to the biggest quantity.
- return unitConverters_.get(0).convert(quantity).multiply(EPSILON_MULTIPLIER).compareTo(limit) >= 0;
+ return unitsConverters_.get(0).convert(quantity).multiply(EPSILON_MULTIPLIER).compareTo(limit) >= 0;
+ }
+
+ /**
+ * @hide Only a subset of ICU is exposed in Android
+ */
+ public static class ComplexConverterResult {
+ public final int indexOfQuantity;
+ public final List<Measure> measures;
+
+ ComplexConverterResult(int indexOfQuantity, List<Measure> measures) {
+ this.indexOfQuantity = indexOfQuantity;
+ this.measures = measures;
+ }
}
/**
@@ -105,9 +168,8 @@
* the smallest element is the only element that could have fractional values. And all
* other elements are floored to the nearest integer
*/
- public List<Measure> convert(BigDecimal quantity, Precision rounder) {
- List<Measure> result = new ArrayList<>(unitConverters_.size());
- BigDecimal sign = BigDecimal.ONE;
+ public ComplexConverterResult convert(BigDecimal quantity, Precision rounder) {
+ BigInteger sign = BigInteger.ONE;
if (quantity.compareTo(BigDecimal.ZERO) < 0) {
quantity = quantity.abs();
sign = sign.negate();
@@ -119,10 +181,9 @@
// - N-1 converters convert to bigger units for which we want integers,
// - the Nth converter (index N-1) converts to the smallest unit, which
// isn't (necessarily) an integer.
- List<BigDecimal> intValues = new ArrayList<>(unitConverters_.size() - 1);
-
- for (int i = 0, n = unitConverters_.size(); i < n; ++i) {
- quantity = (unitConverters_.get(i)).convert(quantity);
+ List<BigInteger> intValues = new ArrayList<>(unitsConverters_.size() - 1);
+ for (int i = 0, n = unitsConverters_.size(); i < n; ++i) {
+ quantity = (unitsConverters_.get(i)).convert(quantity);
if (i < n - 1) {
// The double type has 15 decimal digits of precision. For choosing
@@ -131,85 +192,93 @@
// decision is made. However after the thresholding, we use the
// original values to ensure unbiased accuracy (to the extent of
// double's capabilities).
- BigDecimal roundedQuantity =
- quantity.multiply(EPSILON_MULTIPLIER).setScale(0, RoundingMode.FLOOR);
- intValues.add(roundedQuantity);
+ BigInteger flooredQuantity = quantity.multiply(EPSILON_MULTIPLIER).setScale(0, RoundingMode.FLOOR).toBigInteger();
+ intValues.add(flooredQuantity);
// Keep the residual of the quantity.
- // For example: `3.6 feet`, keep only `0.6 feet`
- quantity = quantity.subtract(roundedQuantity);
- if (quantity.compareTo(BigDecimal.ZERO) == -1) {
+ // For example: `3.6 feet`, keep only `0.6 feet`
+ BigDecimal remainder = quantity.subtract(BigDecimal.valueOf(flooredQuantity.longValue()));
+ if (remainder.compareTo(BigDecimal.ZERO) == -1) {
quantity = BigDecimal.ZERO;
- }
- } else { // LAST ELEMENT
- if (rounder == null) {
- // Nothing to do for the last element.
- break;
- }
-
- // Round the last value
- // TODO(ICU-21288): get smarter about precision for mixed units.
- DecimalQuantity quant = new DecimalQuantity_DualStorageBCD(quantity);
- rounder.apply(quant);
- quantity = quant.toBigDecimal();
- if (i == 0) {
- // Last element is also the first element, so we're done
- break;
- }
-
- // Check if there's a carry, and bubble it back up the resulting intValues.
- BigDecimal carry = unitConverters_.get(i)
- .convertInverse(quantity)
- .multiply(EPSILON_MULTIPLIER)
- .setScale(0, RoundingMode.FLOOR);
- if (carry.compareTo(BigDecimal.ZERO) <= 0) { // carry is not greater than zero
- break;
- }
- quantity = quantity.subtract(unitConverters_.get(i).convert(carry));
- intValues.set(i - 1, intValues.get(i - 1).add(carry));
-
- // We don't use the first converter: that one is for the input unit
- for (int j = i - 1; j > 0; j--) {
- carry = unitConverters_.get(j)
- .convertInverse(intValues.get(j))
- .multiply(EPSILON_MULTIPLIER)
- .setScale(0, RoundingMode.FLOOR);
- if (carry.compareTo(BigDecimal.ZERO) <= 0) { // carry is not greater than zero
- break;
- }
- intValues.set(j, intValues.get(j).subtract(unitConverters_.get(j).convert(carry)));
- intValues.set(j - 1, intValues.get(j - 1).add(carry));
+ } else {
+ quantity = remainder;
}
}
}
- // Package values into Measure instances in result:
- for (int i = 0, n = unitConverters_.size(); i < n; ++i) {
+ quantity = applyRounder(intValues, quantity, rounder);
+
+ // Initialize empty measures.
+ List<Measure> measures = new ArrayList<>(unitsConverters_.size());
+ for (int i = 0; i < unitsConverters_.size(); i++) {
+ measures.add(null);
+ }
+
+ // Package values into Measure instances in measures:
+ int indexOfQuantity = -1;
+ for (int i = 0, n = unitsConverters_.size(); i < n; ++i) {
if (i < n - 1) {
- result.add(new Measure(intValues.get(i).multiply(sign), units_.get(i).build()));
+ Measure measure = new Measure(intValues.get(i).multiply(sign), units_.get(i).unitImpl.build());
+ measures.set(units_.get(i).index, measure);
} else {
- result.add(new Measure(quantity.multiply(sign), units_.get(i).build()));
+ indexOfQuantity = units_.get(i).index;
+ Measure measure =
+ new Measure(quantity.multiply(BigDecimal.valueOf(sign.longValue())),
+ units_.get(i).unitImpl.build());
+ measures.set(indexOfQuantity, measure);
}
}
- for (int i = 0; i < result.size(); i++) {
- for (int j = i; j < result.size(); j++) {
- // Find the next expected unit, and swap it into place.
- if (result.get(j).getUnit().equals(outputUnits_.get(i))) {
- if (j != i) {
- Measure tmp = result.get(j);
- result.set(j, result.get(i));
- result.set(i, tmp);
- }
- }
- }
+ return new ComplexConverterResult(indexOfQuantity , measures);
+ }
+
+ /**
+ * Applies the rounder to the quantity (last element) and bubble up any carried value to all the intValues.
+ *
+ * @return the rounded quantity
+ */
+ private BigDecimal applyRounder(List<BigInteger> intValues, BigDecimal quantity, Precision rounder) {
+ if (rounder == null) {
+ return quantity;
}
-
- return result;
+
+ DecimalQuantity quantityBCD = new DecimalQuantity_DualStorageBCD(quantity);
+ rounder.apply(quantityBCD);
+ quantity = quantityBCD.toBigDecimal();
+
+ if (intValues.size() == 0) {
+ // There is only one element, Therefore, nothing to be done
+ return quantity;
+ }
+
+ // Check if there's a carry, and bubble it back up the resulting intValues.
+ int lastIndex = unitsConverters_.size() - 1;
+ BigDecimal carry = unitsConverters_.get(lastIndex).convertInverse(quantity).multiply(EPSILON_MULTIPLIER)
+ .setScale(0, RoundingMode.FLOOR);
+ if (carry.compareTo(BigDecimal.ZERO) <= 0) { // carry is not greater than zero
+ return quantity;
+ }
+ quantity = quantity.subtract(unitsConverters_.get(lastIndex).convert(carry));
+ intValues.set(lastIndex - 1, intValues.get(lastIndex - 1).add(carry.toBigInteger()));
+
+ // We don't use the first converter: that one is for the input unit
+ for (int j = lastIndex - 1; j > 0; j--) {
+ carry = unitsConverters_.get(j)
+ .convertInverse(BigDecimal.valueOf(intValues.get(j).longValue()))
+ .multiply(EPSILON_MULTIPLIER)
+ .setScale(0, RoundingMode.FLOOR);
+ if (carry.compareTo(BigDecimal.ZERO) <= 0) { // carry is not greater than zero
+ break;
+ }
+ intValues.set(j, intValues.get(j).subtract(unitsConverters_.get(j).convert(carry).toBigInteger()));
+ intValues.set(j - 1, intValues.get(j - 1).add(carry.toBigInteger()));
+ }
+
+ return quantity;
}
@Override
public String toString() {
- return "ComplexUnitsConverter [unitConverters_=" + unitConverters_ + ", units_=" + units_ + "]";
+ return "ComplexUnitsConverter [unitsConverters_=" + unitsConverters_ + ", units_=" + units_ + "]";
}
}
diff --git a/android_icu4j/src/main/java/android/icu/impl/units/ConversionRates.java b/android_icu4j/src/main/java/android/icu/impl/units/ConversionRates.java
index 036b9a0..4e6c60f 100644
--- a/android_icu4j/src/main/java/android/icu/impl/units/ConversionRates.java
+++ b/android_icu4j/src/main/java/android/icu/impl/units/ConversionRates.java
@@ -41,16 +41,20 @@
* @param singleUnit
* @return
*/
- private UnitConverter.Factor getFactorToBase(SingleUnitImpl singleUnit) {
+ // In ICU4C, this is called loadCompoundFactor().
+ private UnitsConverter.Factor getFactorToBase(SingleUnitImpl singleUnit) {
int power = singleUnit.getDimensionality();
- MeasureUnit.SIPrefix siPrefix = singleUnit.getSiPrefix();
- UnitConverter.Factor result = UnitConverter.Factor.processFactor(mapToConversionRate.get(singleUnit.getSimpleUnit()).getConversionRate());
+ MeasureUnit.MeasurePrefix unitPrefix = singleUnit.getPrefix();
+ UnitsConverter.Factor result = UnitsConverter.Factor.processFactor(mapToConversionRate.get(singleUnit.getSimpleUnitID()).getConversionRate());
- return result.applySiPrefix(siPrefix).power(power); // NOTE: you must apply the SI prefixes before the power.
+ // Prefix before power, because:
+ // - square-kilometer to square-meter: (1000)^2
+ // - square-kilometer to square-foot (approximate): (3.28*1000)^2
+ return result.applyPrefix(unitPrefix).power(power);
}
- public UnitConverter.Factor getFactorToBase(MeasureUnitImpl measureUnit) {
- UnitConverter.Factor result = new UnitConverter.Factor();
+ public UnitsConverter.Factor getFactorToBase(MeasureUnitImpl measureUnit) {
+ UnitsConverter.Factor result = new UnitsConverter.Factor();
for (SingleUnitImpl singleUnit :
measureUnit.getSingleUnits()) {
result = result.multiply(getFactorToBase(singleUnit));
@@ -59,13 +63,14 @@
return result;
}
- protected BigDecimal getOffset(MeasureUnitImpl source, MeasureUnitImpl target, UnitConverter.Factor
- sourceToBase, UnitConverter.Factor targetToBase, UnitConverter.Convertibility convertibility) {
- if (convertibility != UnitConverter.Convertibility.CONVERTIBLE) return BigDecimal.valueOf(0);
+ // In ICU4C, this functionality is found in loadConversionRate().
+ protected BigDecimal getOffset(MeasureUnitImpl source, MeasureUnitImpl target, UnitsConverter.Factor
+ sourceToBase, UnitsConverter.Factor targetToBase, UnitsConverter.Convertibility convertibility) {
+ if (convertibility != UnitsConverter.Convertibility.CONVERTIBLE) return BigDecimal.valueOf(0);
if (!(checkSimpleUnit(source) && checkSimpleUnit(target))) return BigDecimal.valueOf(0);
- String sourceSimpleIdentifier = source.getSingleUnits().get(0).getSimpleUnit();
- String targetSimpleIdentifier = target.getSingleUnits().get(0).getSimpleUnit();
+ String sourceSimpleIdentifier = source.getSingleUnits().get(0).getSimpleUnitID();
+ String targetSimpleIdentifier = target.getSingleUnits().get(0).getSimpleUnitID();
BigDecimal sourceOffset = this.mapToConversionRate.get(sourceSimpleIdentifier).getOffset();
BigDecimal targetOffset = this.mapToConversionRate.get(targetSimpleIdentifier).getOffset();
@@ -107,7 +112,7 @@
* This method is helpful when checking the convertibility because no need to check convertibility.
*/
public ArrayList<SingleUnitImpl> extractBaseUnits(SingleUnitImpl singleUnit) {
- String target = mapToConversionRate.get(singleUnit.getSimpleUnit()).getTarget();
+ String target = mapToConversionRate.get(singleUnit.getSimpleUnitID()).getTarget();
MeasureUnitImpl targetImpl = MeasureUnitImpl.UnitsParser.parseForIdentifier(target);
// Each unit must be powered by the same dimension
@@ -128,7 +133,7 @@
if (measureUnitImpl.getComplexity() != MeasureUnit.Complexity.SINGLE) return false;
SingleUnitImpl singleUnit = measureUnitImpl.getSingleUnits().get(0);
- if (singleUnit.getSiPrefix() != MeasureUnit.SIPrefix.ONE) return false;
+ if (singleUnit.getPrefix() != MeasureUnit.MeasurePrefix.ONE) return false;
if (singleUnit.getDimensionality() != 1) return false;
return true;
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 135d3ba..824539e 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
@@ -21,21 +21,18 @@
public class MeasureUnitImpl {
/**
- * The full unit identifier. Null if not computed.
+ * The full unit identifier. Null if not computed.
*/
private String identifier = null;
-
/**
* The complexity, either SINGLE, COMPOUND, or MIXED.
*/
private MeasureUnit.Complexity complexity = MeasureUnit.Complexity.SINGLE;
-
/**
- * The list of simple units. These may be summed or multiplied, based on the
+ * The list of single units. These may be summed or multiplied, based on the
* value of the complexity field.
* <p>
- * The "dimensionless" unit (SingleUnitImpl default constructor) must not be
- * added to this list.
+ * The "dimensionless" unit (SingleUnitImpl default constructor) must not be added to this list.
* <p>
* The "dimensionless" <code>MeasureUnitImpl</code> has an empty <code>singleUnits</code>.
*/
@@ -74,13 +71,46 @@
MeasureUnitImpl result = new MeasureUnitImpl();
result.complexity = this.complexity;
result.identifier = this.identifier;
- for (SingleUnitImpl single : this.singleUnits) {
- result.singleUnits.add(single.copy());
+ for (SingleUnitImpl singleUnit : this.singleUnits) {
+ result.singleUnits.add(singleUnit.copy());
}
return result;
}
/**
+ * Returns a simplified version of the unit.
+ * NOTE: the simplification happen when there are two units equals in their base unit and their
+ * prefixes.
+ *
+ * Example 1: "square-meter-per-meter" --> "meter"
+ * Example 2: "square-millimeter-per-meter" --> "square-millimeter-per-meter"
+ */
+ public MeasureUnitImpl copyAndSimplify() {
+ MeasureUnitImpl result = new MeasureUnitImpl();
+ for (SingleUnitImpl singleUnit : this.getSingleUnits()) {
+ // This `for` loop will cause time complexity to be O(n^2).
+ // However, n is very small (number of units, generally, at maximum equal to 10)
+ boolean unitExist = false;
+ for (SingleUnitImpl resultSingleUnit : result.getSingleUnits()) {
+ if(resultSingleUnit.getSimpleUnitID().compareTo(singleUnit.getSimpleUnitID()) == 0
+ &&
+ resultSingleUnit.getPrefix().getIdentifier().compareTo(singleUnit.getPrefix().getIdentifier()) == 0
+ ) {
+ unitExist = true;
+ resultSingleUnit.setDimensionality(resultSingleUnit.getDimensionality() + singleUnit.getDimensionality());
+ break;
+ }
+ }
+
+ if(!unitExist) {
+ result.appendSingleUnit(singleUnit);
+ }
+ }
+
+ return result;
+ }
+
+ /**
* Returns the list of simple units.
*/
public ArrayList<SingleUnitImpl> getSingleUnits() {
@@ -98,29 +128,20 @@
}
}
- /**
- * Extracts the list of all the individual units inside the `MeasureUnitImpl`.
- * For example:
- * - if the <code>MeasureUnitImpl</code> is <code>foot-per-hour</code>
- * it will return a list of 1 <code>{foot-per-hour}</code>
- * - if the <code>MeasureUnitImpl</code> is <code>foot-and-inch</code>
- * it will return a list of 2 <code>{ foot, inch}</code>
- *
- * @return a list of <code>MeasureUnitImpl</code>
- */
- public ArrayList<MeasureUnitImpl> extractIndividualUnits() {
- ArrayList<MeasureUnitImpl> result = new ArrayList<>();
+ public ArrayList<MeasureUnitImplWithIndex> extractIndividualUnitsWithIndices() {
+ ArrayList<MeasureUnitImplWithIndex> result = new ArrayList<>();
if (this.getComplexity() == MeasureUnit.Complexity.MIXED) {
// In case of mixed units, each single unit can be considered as a stand alone MeasureUnitImpl.
+ int i = 0;
for (SingleUnitImpl singleUnit :
this.getSingleUnits()) {
- result.add(new MeasureUnitImpl(singleUnit));
+ result.add(new MeasureUnitImplWithIndex(i++, new MeasureUnitImpl(singleUnit)));
}
return result;
}
- result.add(this.copy());
+ result.add(new MeasureUnitImplWithIndex(0, this.copy()));
return result;
}
@@ -145,7 +166,7 @@
identifier = null;
if (singleUnit == null) {
- // We don't append dimensionless units.
+ // Do not append dimensionless units.
return false;
}
@@ -169,8 +190,8 @@
// Add a copy of singleUnit
this.singleUnits.add(singleUnit.copy());
- // If the MeasureUnitImpl is `UMEASURE_UNIT_SINGLE` and after the appending a unit, the singleUnits are more
- // than one singleUnit. thus means the complexity should be `UMEASURE_UNIT_COMPOUND`
+ // If the MeasureUnitImpl is `UMEASURE_UNIT_SINGLE` and after the appending a unit, the singleUnits contains
+ // more than one. thus means the complexity should be `UMEASURE_UNIT_COMPOUND`
if (this.singleUnits.size() > 1 && this.complexity == MeasureUnit.Complexity.SINGLE) {
this.setComplexity(MeasureUnit.Complexity.COMPOUND);
}
@@ -202,7 +223,6 @@
throw new UnsupportedOperationException();
}
-
/**
* Returns the CLDR unit identifier and null if not computed.
*/
@@ -227,6 +247,8 @@
// to this.result, we wish it to contain the zero-length string.
return;
}
+
+
if (this.complexity == MeasureUnit.Complexity.COMPOUND) {
// Note: don't sort a MIXED unit
Collections.sort(this.getSingleUnits(), new SingleUnitComparator());
@@ -244,7 +266,6 @@
firstTimeNegativeDimension = false;
}
- String singleUnitIdentifier = singleUnit.getNeutralIdentifier();
if (this.getComplexity() == MeasureUnit.Complexity.MIXED) {
if (result.length() != 0) {
result.append("-and-");
@@ -263,12 +284,17 @@
}
}
- result.append(singleUnitIdentifier);
+ result.append(singleUnit.getNeutralIdentifier());
}
this.identifier = result.toString();
}
+ @Override
+ public String toString() {
+ return "MeasureUnitImpl [" + build().getIdentifier() + "]";
+ }
+
/**
* @hide Only a subset of ICU is exposed in Android
*/
@@ -384,6 +410,19 @@
/**
* @hide Only a subset of ICU is exposed in Android
*/
+ public static class MeasureUnitImplWithIndex {
+ int index;
+ MeasureUnitImpl unitImpl;
+
+ MeasureUnitImplWithIndex(int index, MeasureUnitImpl unitImpl) {
+ this.index = index;
+ this.unitImpl = unitImpl;
+ }
+ }
+
+ /**
+ * @hide Only a subset of ICU is exposed in Android
+ */
public static class UnitsParser {
// This used only to not build the trie each time we use the parser
private volatile static CharsTrie savedTrie = null;
@@ -401,6 +440,11 @@
// * unit", sawAnd is set to true. If not, it is left as is.
private boolean fSawAnd = false;
+ // Cache the MeasurePrefix values array to make getPrefixFromTrieIndex()
+ // more efficient
+ private static MeasureUnit.MeasurePrefix[] measurePrefixValues =
+ MeasureUnit.MeasurePrefix.values();
+
private UnitsParser(String identifier) {
this.fSource = identifier;
@@ -416,6 +460,11 @@
CharsTrieBuilder trieBuilder;
trieBuilder = new CharsTrieBuilder();
+ // Add SI and binary prefixes
+ for (MeasureUnit.MeasurePrefix unitPrefix : measurePrefixValues) {
+ trieBuilder.add(unitPrefix.getIdentifier(), getTrieIndexForPrefix(unitPrefix));
+ }
+
// Add syntax parts (compound, power prefixes)
trieBuilder.add("-per-", CompoundPart.PER.getTrieIndex());
trieBuilder.add("-", CompoundPart.TIMES.getTrieIndex());
@@ -438,12 +487,6 @@
trieBuilder.add("pow14-", PowerPart.P14.getTrieIndex());
trieBuilder.add("pow15-", PowerPart.P15.getTrieIndex());
- // Add SI prefixes
- for (MeasureUnit.SIPrefix siPrefix :
- MeasureUnit.SIPrefix.values()) {
- trieBuilder.add(siPrefix.getIdentifier(), getTrieIndex(siPrefix));
- }
-
// Add simple units
String[] simpleUnits = UnitsData.getSimpleUnits();
for (int i = 0; i < simpleUnits.length; i++) {
@@ -472,18 +515,12 @@
}
- private static MeasureUnit.SIPrefix getSiPrefixFromTrieIndex(int trieIndex) {
- for (MeasureUnit.SIPrefix element :
- MeasureUnit.SIPrefix.values()) {
- if (getTrieIndex(element) == trieIndex)
- return element;
- }
-
- throw new IllegalArgumentException("Incorrect trieIndex");
+ private static MeasureUnit.MeasurePrefix getPrefixFromTrieIndex(int trieIndex) {
+ return measurePrefixValues[trieIndex - UnitsData.Constants.kPrefixOffset];
}
- private static int getTrieIndex(MeasureUnit.SIPrefix prefix) {
- return prefix.getPower() + UnitsData.Constants.kSIPrefixOffset;
+ private static int getTrieIndexForPrefix(MeasureUnit.MeasurePrefix prefix) {
+ return prefix.ordinal() + UnitsData.Constants.kPrefixOffset;
}
private MeasureUnitImpl parse() {
@@ -511,7 +548,7 @@
MeasureUnit.Complexity complexity =
fSawAnd ? MeasureUnit.Complexity.MIXED : MeasureUnit.Complexity.COMPOUND;
if (result.getSingleUnits().size() == 2) {
- // After appending two singleUnits, the complexity will be `UMEASURE_UNIT_COMPOUND`
+ // After appending two singleUnits, the complexity will be MeasureUnit.Complexity.COMPOUND
assert result.getComplexity() == MeasureUnit.Complexity.COMPOUND;
result.setComplexity(complexity);
} else if (result.getComplexity() != complexity) {
@@ -537,9 +574,9 @@
SingleUnitImpl result = new SingleUnitImpl();
// state:
- // 0 = no tokens seen yet (will accept power, SI prefix, or simple unit)
+ // 0 = no tokens seen yet (will accept power, SI or binary prefix, or simple unit)
// 1 = power token seen (will not accept another power token)
- // 2 = SI prefix token seen (will not accept a power or SI prefix token)
+ // 2 = SI or binary prefix token seen (will not accept a power, or SI or binary prefix token)
int state = 0;
boolean atStart = fIndex == 0;
@@ -604,12 +641,12 @@
state = 1;
break;
- case TYPE_SI_PREFIX:
+ case TYPE_PREFIX:
if (state > 1) {
throw new IllegalArgumentException();
}
- result.setSiPrefix(token.getSIPrefix());
+ result.setPrefix(token.getPrefix());
state = 2;
break;
@@ -687,9 +724,9 @@
return this.type;
}
- public MeasureUnit.SIPrefix getSIPrefix() {
- assert this.type == Type.TYPE_SI_PREFIX;
- return getSiPrefixFromTrieIndex(this.fMatch);
+ public MeasureUnit.MeasurePrefix getPrefix() {
+ assert this.type == Type.TYPE_PREFIX;
+ return getPrefixFromTrieIndex(this.fMatch);
}
// Valid only for tokens with type TYPE_COMPOUND_PART.
@@ -713,6 +750,7 @@
}
public int getSimpleUnitIndex() {
+ assert this.type == Type.TYPE_SIMPLE_UNIT;
return this.fMatch - UnitsData.Constants.kSimpleUnitOffset;
}
@@ -724,7 +762,7 @@
}
if (fMatch < UnitsData.Constants.kCompoundPartOffset) {
- return Type.TYPE_SI_PREFIX;
+ return Type.TYPE_PREFIX;
}
if (fMatch < UnitsData.Constants.kInitialCompoundPartOffset) {
return Type.TYPE_COMPOUND_PART;
@@ -741,7 +779,7 @@
enum Type {
TYPE_UNDEFINED,
- TYPE_SI_PREFIX,
+ TYPE_PREFIX,
// Token type for "-per-", "-", and "-and-".
TYPE_COMPOUND_PART,
// Token type for "per-".
@@ -761,8 +799,23 @@
@Override
public int compare(MeasureUnitImpl o1, MeasureUnitImpl o2) {
- UnitConverter fromO1toO2 = new UnitConverter(o1, o2, conversionRates);
- return fromO1toO2.convert(BigDecimal.valueOf(1)).compareTo(BigDecimal.valueOf(1));
+ BigDecimal factor1 = this.conversionRates.getFactorToBase(o1).getConversionRate();
+ BigDecimal factor2 = this.conversionRates.getFactorToBase(o2).getConversionRate();
+
+ return factor1.compareTo(factor2);
+ }
+ }
+
+ static class MeasureUnitImplWithIndexComparator implements Comparator<MeasureUnitImplWithIndex> {
+ private MeasureUnitImplComparator measureUnitImplComparator;
+
+ public MeasureUnitImplWithIndexComparator(ConversionRates conversionRates) {
+ this.measureUnitImplComparator = new MeasureUnitImplComparator(conversionRates);
+ }
+
+ @Override
+ public int compare(MeasureUnitImplWithIndex o1, MeasureUnitImplWithIndex o2) {
+ return this.measureUnitImplComparator.compare(o1.unitImpl, o2.unitImpl);
}
}
@@ -772,9 +825,4 @@
return o1.compareTo(o2);
}
}
-
- @Override
- public String toString() {
- return "MeasureUnitImpl [" + build().getIdentifier() + "]";
- }
}
diff --git a/android_icu4j/src/main/java/android/icu/impl/units/SingleUnitImpl.java b/android_icu4j/src/main/java/android/icu/impl/units/SingleUnitImpl.java
index 22ffe07..3fc1b4e 100644
--- a/android_icu4j/src/main/java/android/icu/impl/units/SingleUnitImpl.java
+++ b/android_icu4j/src/main/java/android/icu/impl/units/SingleUnitImpl.java
@@ -6,7 +6,11 @@
import android.icu.util.MeasureUnit;
+// TODO: revisit documentation in this file. E.g. we don't do dimensionless
+// units in Java? We use null instead.
+
/**
+ * A class representing a single unit (optional SI or binary prefix, and dimensionality).
* @hide Only a subset of ICU is exposed in Android
*/
public class SingleUnitImpl {
@@ -24,7 +28,7 @@
* The default value is "", meaning the dimensionless unit:
* isDimensionless() will return true, until index is changed.
*/
- private String simpleUnit = "";
+ private String simpleUnitID = "";
/**
* Determine the power of the `SingleUnit`. For example, for "square-meter", the dimensionality will be `2`.
* <p>
@@ -33,16 +37,16 @@
*/
private int dimensionality = 1;
/**
- * SI Prefix
+ * SI or binary prefix.
*/
- private MeasureUnit.SIPrefix siPrefix = MeasureUnit.SIPrefix.ONE;
+ private MeasureUnit.MeasurePrefix unitPrefix = MeasureUnit.MeasurePrefix.ONE;
public SingleUnitImpl copy() {
SingleUnitImpl result = new SingleUnitImpl();
result.index = this.index;
result.dimensionality = this.dimensionality;
- result.simpleUnit = this.simpleUnit;
- result.siPrefix = this.siPrefix;
+ result.simpleUnitID = this.simpleUnitID;
+ result.unitPrefix = this.unitPrefix;
return result;
}
@@ -53,31 +57,30 @@
}
/**
- * Generates an neutral identifier string for a single unit which means we do not include the dimension signal.
+ * Generates a neutral identifier string for a single unit which means we do not include the dimension signal.
*/
public String getNeutralIdentifier() {
StringBuilder result = new StringBuilder();
- int posPower = Math.abs(this.getDimensionality());
+ int absPower = Math.abs(this.getDimensionality());
- assert posPower > 0 : "getIdentifier does not support the dimensionless";
+ assert absPower > 0 : "this function does not support the dimensionless single units";
- if (posPower == 1) {
+ if (absPower == 1) {
// no-op
- } else if (posPower == 2) {
+ } else if (absPower == 2) {
result.append("square-");
- } else if (posPower == 3) {
+ } else if (absPower == 3) {
result.append("cubic-");
- } else if (posPower <= 15) {
+ } else if (absPower <= 15) {
result.append("pow");
- result.append(posPower);
+ result.append(absPower);
result.append('-');
} else {
- // TODO: IllegalArgumentException might not be appropriate here
throw new IllegalArgumentException("Unit Identifier Syntax Error");
}
- result.append(this.getSiPrefix().getIdentifier());
- result.append(this.getSimpleUnit());
+ result.append(this.getPrefix().getIdentifier());
+ result.append(this.getSimpleUnitID());
return result.toString();
}
@@ -86,6 +89,9 @@
* Compare this SingleUnitImpl to another SingleUnitImpl for the sake of
* sorting and coalescing.
* <p>
+ * Sort order of units is specified by UTS #35
+ * (https://unicode.org/reports/tr35/tr35-info.html#Unit_Identifier_Normalization).
+ * <p>
* Takes the sign of dimensionality into account, but not the absolute
* value: per-meter is not considered the same as meter, but meter is
* considered the same as square-meter.
@@ -102,38 +108,71 @@
if (dimensionality > 0 && other.dimensionality < 0) {
return -1;
}
+ // Sort by official quantity order
+ int thisCategoryIndex = UnitsData.getCategoryIndexOfSimpleUnit(index);
+ int otherCategoryIndex = UnitsData.getCategoryIndexOfSimpleUnit(other.index);
+ if (thisCategoryIndex < otherCategoryIndex) {
+ return -1;
+ }
+ if (thisCategoryIndex > otherCategoryIndex) {
+ return 1;
+ }
+ // If quantity order didn't help, then we go by index.
if (index < other.index) {
return -1;
}
if (index > other.index) {
return 1;
}
- if (this.getSiPrefix().getPower() < other.getSiPrefix().getPower()) {
- return -1;
- }
- if (this.getSiPrefix().getPower() > other.getSiPrefix().getPower()) {
+
+ // When comparing binary prefixes vs SI prefixes, instead of comparing the actual values, we can
+ // multiply the binary prefix power by 3 and compare the powers. if they are equal, we can can
+ // compare the bases.
+ // NOTE: this methodology will fail if the binary prefix more than or equal 98.
+ int unitBase = this.unitPrefix.getBase();
+ int otherUnitBase = other.unitPrefix.getBase();
+ // Values for comparison purposes only.
+ int unitPowerComp =
+ unitBase == 1024 /* Binary Prefix */ ? this.unitPrefix.getPower() * 3
+ : this.unitPrefix.getPower();
+ int otherUnitPowerComp =
+ otherUnitBase == 1024 /* Binary Prefix */ ? other.unitPrefix.getPower() * 3
+ : other.unitPrefix.getPower();
+
+ if (unitPowerComp < otherUnitPowerComp) {
return 1;
}
+ if (unitPowerComp > otherUnitPowerComp) {
+ return -1;
+ }
+
+ if (unitBase < otherUnitBase) {
+ return 1;
+ }
+ if (unitBase > otherUnitBase) {
+ return -1;
+ }
+
return 0;
}
/**
* Checks whether this SingleUnitImpl is compatible with another for the purpose of coalescing.
* <p>
- * Units with the same base unit and SI prefix should match, except that they must also have
- * the same dimensionality sign, such that we don't merge numerator and denominator.
+ * Units with the same base unit and SI or binary prefix should match, except that they must also
+ * have the same dimensionality sign, such that we don't merge numerator and denominator.
*/
boolean isCompatibleWith(SingleUnitImpl other) {
return (compareTo(other) == 0);
}
- public String getSimpleUnit() {
- return simpleUnit;
+ public String getSimpleUnitID() {
+ return simpleUnitID;
}
public void setSimpleUnit(int simpleUnitIndex, String[] simpleUnits) {
this.index = simpleUnitIndex;
- this.simpleUnit = simpleUnits[simpleUnitIndex];
+ this.simpleUnitID = simpleUnits[simpleUnitIndex];
}
public int getDimensionality() {
@@ -144,14 +183,15 @@
this.dimensionality = dimensionality;
}
- public MeasureUnit.SIPrefix getSiPrefix() {
- return siPrefix;
+ public MeasureUnit.MeasurePrefix getPrefix() {
+ return unitPrefix;
}
- public void setSiPrefix(MeasureUnit.SIPrefix siPrefix) {
- this.siPrefix = siPrefix;
+ public void setPrefix(MeasureUnit.MeasurePrefix unitPrefix) {
+ this.unitPrefix = unitPrefix;
}
+ // TODO: unused? Delete?
public int getIndex() {
return index;
}
diff --git a/android_icu4j/src/main/java/android/icu/impl/units/UnitConverter.java b/android_icu4j/src/main/java/android/icu/impl/units/UnitConverter.java
deleted file mode 100644
index 81bdc5e..0000000
--- a/android_icu4j/src/main/java/android/icu/impl/units/UnitConverter.java
+++ /dev/null
@@ -1,323 +0,0 @@
-/* GENERATED SOURCE. DO NOT MODIFY. */
-// © 2020 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html
-package android.icu.impl.units;
-
-import static java.math.MathContext.DECIMAL128;
-
-import java.math.BigDecimal;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.regex.Pattern;
-
-import android.icu.util.MeasureUnit;
-
-/**
- * @hide Only a subset of ICU is exposed in Android
- */
-public class UnitConverter {
- private BigDecimal conversionRate;
- private BigDecimal offset;
-
- /**
- * Constructor of `UnitConverter`.
- * NOTE:
- * - source and target must be under the same category
- * - e.g. meter to mile --> both of them are length units.
- *
- * @param source represents the source unit.
- * @param target represents the target unit.
- * @param conversionRates contains all the needed conversion rates.
- */
- public UnitConverter(MeasureUnitImpl source, MeasureUnitImpl target, ConversionRates conversionRates) {
- Convertibility convertibility = extractConvertibility(source, target, conversionRates);
- assert (convertibility == Convertibility.CONVERTIBLE || convertibility == Convertibility.RECIPROCAL);
-
- Factor sourceToBase = conversionRates.getFactorToBase(source);
- Factor targetToBase = conversionRates.getFactorToBase(target);
-
- if (convertibility == Convertibility.CONVERTIBLE) {
- this.conversionRate = sourceToBase.divide(targetToBase).getConversionRate();
- } else {
- this.conversionRate = sourceToBase.multiply(targetToBase).getConversionRate();
- }
-
- // calculate the offset
- this.offset = conversionRates.getOffset(source, target, sourceToBase, targetToBase, convertibility);
- }
-
- static public Convertibility extractConvertibility(MeasureUnitImpl source, MeasureUnitImpl target, ConversionRates conversionRates) {
- ArrayList<SingleUnitImpl> sourceSingleUnits = conversionRates.extractBaseUnits(source);
- ArrayList<SingleUnitImpl> targetSingleUnits = conversionRates.extractBaseUnits(target);
-
- HashMap<String, Integer> dimensionMap = new HashMap<>();
-
- insertInMap(dimensionMap, sourceSingleUnits, 1);
- insertInMap(dimensionMap, targetSingleUnits, -1);
-
- if (areDimensionsZeroes(dimensionMap)) return Convertibility.CONVERTIBLE;
-
- insertInMap(dimensionMap, targetSingleUnits, 2);
- if (areDimensionsZeroes(dimensionMap)) return Convertibility.RECIPROCAL;
-
- return Convertibility.UNCONVERTIBLE;
- }
-
- /**
- * Helpers
- */
- private static void insertInMap(HashMap<String, Integer> dimensionMap, ArrayList<SingleUnitImpl> singleUnits, int multiplier) {
- for (SingleUnitImpl singleUnit :
- singleUnits) {
- if (dimensionMap.containsKey(singleUnit.getSimpleUnit())) {
- dimensionMap.put(singleUnit.getSimpleUnit(), dimensionMap.get(singleUnit.getSimpleUnit()) + singleUnit.getDimensionality() * multiplier);
- } else {
- dimensionMap.put(singleUnit.getSimpleUnit(), singleUnit.getDimensionality() * multiplier);
- }
- }
- }
-
- private static boolean areDimensionsZeroes(HashMap<String, Integer> dimensionMap) {
- for (Integer value :
- dimensionMap.values()) {
- if (!value.equals(0)) return false;
- }
-
- return true;
- }
-
- public BigDecimal convert(BigDecimal inputValue) {
- return inputValue.multiply(this.conversionRate).add(offset);
- }
-
- public BigDecimal convertInverse(BigDecimal inputValue) {
- return inputValue.subtract(offset).divide(this.conversionRate, DECIMAL128);
- }
-
- /**
- * @hide Only a subset of ICU is exposed in Android
- */
- public enum Convertibility {
- CONVERTIBLE,
- RECIPROCAL,
- UNCONVERTIBLE,
- }
-
- // TODO: improve documentation and Constant implementation
-
- /**
- * Responsible for all the Factor operation
- * NOTE:
- * This class is immutable
- */
- static class Factor {
- private BigDecimal factorNum;
- private BigDecimal factorDen;
- /* FACTOR CONSTANTS */
- private int
- CONSTANT_FT2M = 0, // ft2m stands for foot to meter.
- CONSTANT_PI = 0, // PI
- CONSTANT_GRAVITY = 0, // Gravity
- CONSTANT_G = 0,
- CONSTANT_GAL_IMP2M3 = 0, // Gallon imp to m3
- CONSTANT_LB2KG = 0; // Pound to Kilogram
-
-
- /**
- * Creates Empty Factor
- */
- public Factor() {
- this.factorNum = BigDecimal.valueOf(1);
- this.factorDen = BigDecimal.valueOf(1);
- }
-
- public static Factor processFactor(String factor) {
- assert (!factor.isEmpty());
-
- // Remove all spaces in the factor
- factor = factor.replaceAll("\\s+", "");
-
- String[] fractions = factor.split("/");
- assert (fractions.length == 1 || fractions.length == 2);
-
- if (fractions.length == 1) {
- return processFactorWithoutDivision(fractions[0]);
- }
-
- Factor num = processFactorWithoutDivision(fractions[0]);
- Factor den = processFactorWithoutDivision(fractions[1]);
- return num.divide(den);
- }
-
- private static Factor processFactorWithoutDivision(String factorWithoutDivision) {
- Factor result = new Factor();
- for (String poweredEntity :
- factorWithoutDivision.split(Pattern.quote("*"))) {
- result.addPoweredEntity(poweredEntity);
- }
-
- return result;
- }
-
- /**
- * Copy this <code>Factor</code>.
- */
- protected Factor copy() {
- Factor result = new Factor();
- result.factorNum = this.factorNum;
- result.factorDen = this.factorDen;
-
- result.CONSTANT_FT2M = this.CONSTANT_FT2M;
- result.CONSTANT_PI = this.CONSTANT_PI;
- result.CONSTANT_GRAVITY = this.CONSTANT_GRAVITY;
- result.CONSTANT_G = this.CONSTANT_G;
- result.CONSTANT_GAL_IMP2M3 = this.CONSTANT_GAL_IMP2M3;
- result.CONSTANT_LB2KG = this.CONSTANT_LB2KG;
-
- return result;
- }
-
- /**
- * Returns a single `BigDecimal` that represent the conversion rate after substituting all the constants.
- */
- public BigDecimal getConversionRate() {
- Factor resultCollector = this.copy();
-
- resultCollector.substitute(new BigDecimal("0.3048"), this.CONSTANT_FT2M);
- resultCollector.substitute(new BigDecimal("411557987.0").divide(new BigDecimal("131002976.0"), DECIMAL128), this.CONSTANT_PI);
- resultCollector.substitute(new BigDecimal("9.80665"), this.CONSTANT_GRAVITY);
- resultCollector.substitute(new BigDecimal("6.67408E-11"), this.CONSTANT_G);
- resultCollector.substitute(new BigDecimal("0.00454609"), this.CONSTANT_GAL_IMP2M3);
- resultCollector.substitute(new BigDecimal("0.45359237"), this.CONSTANT_LB2KG);
-
- return resultCollector.factorNum.divide(resultCollector.factorDen, DECIMAL128);
- }
-
- private void substitute(BigDecimal value, int power) {
- if (power == 0) return;
-
- BigDecimal absPoweredValue = value.pow(Math.abs(power), DECIMAL128);
- if (power > 0) {
- this.factorNum = this.factorNum.multiply(absPoweredValue);
- } else {
- this.factorDen = this.factorDen.multiply(absPoweredValue);
- }
- }
-
- public Factor applySiPrefix(MeasureUnit.SIPrefix siPrefix) {
- Factor result = this.copy();
- if (siPrefix == MeasureUnit.SIPrefix.ONE) {
- return result;
- }
-
- BigDecimal siApplied = BigDecimal.valueOf(Math.pow(10.0, Math.abs(siPrefix.getPower())));
-
- if (siPrefix.getPower() < 0) {
- result.factorDen = this.factorDen.multiply(siApplied);
- return result;
- }
-
- result.factorNum = this.factorNum.multiply(siApplied);
- return result;
- }
-
- public Factor power(int power) {
- Factor result = new Factor();
- if (power == 0) return result;
- if (power > 0) {
- result.factorNum = this.factorNum.pow(power);
- result.factorDen = this.factorDen.pow(power);
- } else {
- result.factorNum = this.factorDen.pow(power * -1);
- result.factorDen = this.factorNum.pow(power * -1);
- }
-
- result.CONSTANT_FT2M = this.CONSTANT_FT2M * power;
- result.CONSTANT_PI = this.CONSTANT_PI * power;
- result.CONSTANT_GRAVITY = this.CONSTANT_GRAVITY * power;
- result.CONSTANT_G = this.CONSTANT_G * power;
- result.CONSTANT_GAL_IMP2M3 = this.CONSTANT_GAL_IMP2M3 * power;
- result.CONSTANT_LB2KG = this.CONSTANT_LB2KG * power;
-
- return result;
- }
-
- public Factor divide(Factor other) {
- Factor result = new Factor();
- result.factorNum = this.factorNum.multiply(other.factorDen);
- result.factorDen = this.factorDen.multiply(other.factorNum);
-
- result.CONSTANT_FT2M = this.CONSTANT_FT2M - other.CONSTANT_FT2M;
- result.CONSTANT_PI = this.CONSTANT_PI - other.CONSTANT_PI;
- result.CONSTANT_GRAVITY = this.CONSTANT_GRAVITY - other.CONSTANT_GRAVITY;
- result.CONSTANT_G = this.CONSTANT_G - other.CONSTANT_G;
- result.CONSTANT_GAL_IMP2M3 = this.CONSTANT_GAL_IMP2M3 - other.CONSTANT_GAL_IMP2M3;
- result.CONSTANT_LB2KG = this.CONSTANT_LB2KG - other.CONSTANT_LB2KG;
-
- return result;
- }
-
- public Factor multiply(Factor other) {
- Factor result = new Factor();
- result.factorNum = this.factorNum.multiply(other.factorNum);
- result.factorDen = this.factorDen.multiply(other.factorDen);
-
- result.CONSTANT_FT2M = this.CONSTANT_FT2M + other.CONSTANT_FT2M;
- result.CONSTANT_PI = this.CONSTANT_PI + other.CONSTANT_PI;
- result.CONSTANT_GRAVITY = this.CONSTANT_GRAVITY + other.CONSTANT_GRAVITY;
- result.CONSTANT_G = this.CONSTANT_G + other.CONSTANT_G;
- result.CONSTANT_GAL_IMP2M3 = this.CONSTANT_GAL_IMP2M3 + other.CONSTANT_GAL_IMP2M3;
- result.CONSTANT_LB2KG = this.CONSTANT_LB2KG + other.CONSTANT_LB2KG;
-
- return result;
- }
-
- /**
- * Adds Entity with power or not. For example, `12 ^ 3` or `12`.
- *
- * @param poweredEntity
- */
- private void addPoweredEntity(String poweredEntity) {
- String[] entities = poweredEntity.split(Pattern.quote("^"));
- assert (entities.length == 1 || entities.length == 2);
-
- int power = entities.length == 2 ? Integer.parseInt(entities[1]) : 1;
- this.addEntity(entities[0], power);
- }
-
- private void addEntity(String entity, int power) {
- if ("ft_to_m".equals(entity)) {
- this.CONSTANT_FT2M += power;
- } else if ("ft2_to_m2".equals(entity)) {
- this.CONSTANT_FT2M += 2 * power;
- } else if ("ft3_to_m3".equals(entity)) {
- this.CONSTANT_FT2M += 3 * power;
- } else if ("in3_to_m3".equals(entity)) {
- this.CONSTANT_FT2M += 3 * power;
- this.factorDen = this.factorDen.multiply(BigDecimal.valueOf(Math.pow(12, 3)));
- } else if ("gal_to_m3".equals(entity)) {
- this.factorNum = this.factorNum.multiply(BigDecimal.valueOf(231));
- this.CONSTANT_FT2M += 3 * power;
- this.factorDen = this.factorDen.multiply(BigDecimal.valueOf(12 * 12 * 12));
- } else if ("gal_imp_to_m3".equals(entity)) {
- this.CONSTANT_GAL_IMP2M3 += power;
- } else if ("G".equals(entity)) {
- this.CONSTANT_G += power;
- } else if ("gravity".equals(entity)) {
- this.CONSTANT_GRAVITY += power;
- } else if ("lb_to_kg".equals(entity)) {
- this.CONSTANT_LB2KG += power;
- } else if ("PI".equals(entity)) {
- this.CONSTANT_PI += power;
- } else {
- BigDecimal decimalEntity = new BigDecimal(entity).pow(power, DECIMAL128);
- this.factorNum = this.factorNum.multiply(decimalEntity);
- }
- }
- }
-
- @Override
- public String toString() {
- return "UnitConverter [conversionRate=" + conversionRate + ", offset=" + offset + "]";
- }
-}
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 c8deed7..b6ef278 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
@@ -68,7 +68,9 @@
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.
assert (result != null) : "At least the category must be exist";
return result;
}
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
new file mode 100644
index 0000000..91c3968
--- /dev/null
+++ b/android_icu4j/src/main/java/android/icu/impl/units/UnitsConverter.java
@@ -0,0 +1,432 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+// © 2020 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html
+package android.icu.impl.units;
+
+import static java.math.MathContext.DECIMAL128;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.regex.Pattern;
+
+import android.icu.impl.IllegalIcuArgumentException;
+import android.icu.util.MeasureUnit;
+
+/**
+ * @hide Only a subset of ICU is exposed in Android
+ */
+public class UnitsConverter {
+ private BigDecimal conversionRate;
+ private boolean reciprocal;
+ private BigDecimal offset;
+
+ /**
+ * Constructor of <code>UnitsConverter</code>.
+ * NOTE:
+ * - source and target must be under the same category
+ * - e.g. meter to mile --> both of them are length units.
+ * <p>
+ * NOTE:
+ * This constructor creates an instance of <code>UnitsConverter</code> internally.
+ *
+ * @param sourceIdentifier represents the source unit identifier.
+ * @param targetIdentifier represents the target unit identifier.
+ */
+ public UnitsConverter(String sourceIdentifier, String targetIdentifier) {
+ this(
+ MeasureUnitImpl.forIdentifier(sourceIdentifier),
+ MeasureUnitImpl.forIdentifier(targetIdentifier),
+ new ConversionRates()
+ );
+ }
+
+ /**
+ * Constructor of <code>UnitsConverter</code>.
+ * NOTE:
+ * - source and target must be under the same category
+ * - e.g. meter to mile --> both of them are length units.
+ *
+ * @param source represents the source unit.
+ * @param target represents the target unit.
+ * @param conversionRates contains all the needed conversion rates.
+ */
+ public UnitsConverter(MeasureUnitImpl source, MeasureUnitImpl target, ConversionRates conversionRates) {
+ Convertibility convertibility = extractConvertibility(source, target, conversionRates);
+ if (convertibility != Convertibility.CONVERTIBLE && convertibility != Convertibility.RECIPROCAL) {
+ throw new IllegalIcuArgumentException("input units must be convertible or reciprocal");
+ }
+
+ Factor sourceToBase = conversionRates.getFactorToBase(source);
+ Factor targetToBase = conversionRates.getFactorToBase(target);
+
+ if (convertibility == Convertibility.CONVERTIBLE) {
+ this.conversionRate = sourceToBase.divide(targetToBase).getConversionRate();
+ } else {
+ assert convertibility == Convertibility.RECIPROCAL;
+ this.conversionRate = sourceToBase.multiply(targetToBase).getConversionRate();
+ }
+ this.reciprocal = convertibility == Convertibility.RECIPROCAL;
+
+ // calculate the offset
+ this.offset = conversionRates.getOffset(source, target, sourceToBase, targetToBase, convertibility);
+ // We should see no offsets for reciprocal conversions - they don't make sense:
+ assert convertibility != Convertibility.RECIPROCAL || this.offset == BigDecimal.ZERO;
+ }
+
+ static public Convertibility extractConvertibility(MeasureUnitImpl source, MeasureUnitImpl target, ConversionRates conversionRates) {
+ ArrayList<SingleUnitImpl> sourceSingleUnits = conversionRates.extractBaseUnits(source);
+ ArrayList<SingleUnitImpl> targetSingleUnits = conversionRates.extractBaseUnits(target);
+
+ HashMap<String, Integer> dimensionMap = new HashMap<>();
+
+ insertInMap(dimensionMap, sourceSingleUnits, 1);
+ insertInMap(dimensionMap, targetSingleUnits, -1);
+
+ if (areDimensionsZeroes(dimensionMap)) return Convertibility.CONVERTIBLE;
+
+ insertInMap(dimensionMap, targetSingleUnits, 2);
+ if (areDimensionsZeroes(dimensionMap)) return Convertibility.RECIPROCAL;
+
+ return Convertibility.UNCONVERTIBLE;
+ }
+
+ /**
+ * Helpers
+ */
+ private static void insertInMap(HashMap<String, Integer> dimensionMap, ArrayList<SingleUnitImpl> singleUnits, int multiplier) {
+ for (SingleUnitImpl singleUnit :
+ singleUnits) {
+ if (dimensionMap.containsKey(singleUnit.getSimpleUnitID())) {
+ dimensionMap.put(singleUnit.getSimpleUnitID(), dimensionMap.get(singleUnit.getSimpleUnitID()) + singleUnit.getDimensionality() * multiplier);
+ } else {
+ dimensionMap.put(singleUnit.getSimpleUnitID(), singleUnit.getDimensionality() * multiplier);
+ }
+ }
+ }
+
+ private static boolean areDimensionsZeroes(HashMap<String, Integer> dimensionMap) {
+ for (Integer value :
+ dimensionMap.values()) {
+ if (!value.equals(0)) return false;
+ }
+
+ return true;
+ }
+
+ public BigDecimal convert(BigDecimal inputValue) {
+ BigDecimal result = inputValue.multiply(this.conversionRate).add(offset);
+ 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.)
+ return BigDecimal.ZERO;
+ }
+ result = BigDecimal.ONE.divide(result, DECIMAL128);
+ }
+ return result;
+ }
+
+ public BigDecimal convertInverse(BigDecimal inputValue) {
+ BigDecimal result = inputValue;
+ 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.)
+ return BigDecimal.ZERO;
+ }
+ result = BigDecimal.ONE.divide(result, DECIMAL128);
+ }
+ result = result.subtract(offset).divide(this.conversionRate, DECIMAL128);
+ return result;
+ }
+
+ /**
+ * @hide Only a subset of ICU is exposed in Android
+ */
+ public enum Convertibility {
+ CONVERTIBLE,
+ RECIPROCAL,
+ UNCONVERTIBLE,
+ }
+
+ public ConversionInfo getConversionInfo() {
+ ConversionInfo result = new ConversionInfo();
+ result.conversionRate = this.conversionRate;
+ result.offset = this.offset;
+ result.reciprocal = this.reciprocal;
+
+ return result;
+ }
+
+ /**
+ * @hide Only a subset of ICU is exposed in Android
+ */
+ public static class ConversionInfo {
+ public BigDecimal conversionRate;
+ public BigDecimal offset;
+ public boolean reciprocal;
+ }
+
+ /**
+ * Responsible for all the Factor operation
+ * NOTE:
+ * This class is immutable
+ */
+ static class Factor {
+ private BigDecimal factorNum;
+ private BigDecimal factorDen;
+
+ // The exponents below correspond to ICU4C's Factor::exponents[].
+
+ /** Exponent for the ft_to_m constant */
+ private int exponentFtToM = 0;
+ /** Exponent for PI */
+ private int exponentPi = 0;
+ /** Exponent for gravity (gravity-of-earth, "g") */
+ private int exponentGravity = 0;
+ /** Exponent for Newtonian constant of gravitation "G". */
+ private int exponentG = 0;
+ /** Exponent for the imperial-gallon to cubic-meter conversion rate constant */
+ private int exponentGalImpToM3 = 0;
+ /** Exponent for the pound to kilogram conversion rate constant */
+ private int exponentLbToKg = 0;
+ /** Exponent for the glucose molar mass conversion rate constant */
+ private int exponentGlucoseMolarMass = 0;
+ /** Exponent for the item per mole conversion rate constant */
+ private int exponentItemPerMole = 0;
+
+ /**
+ * Creates Empty Factor
+ */
+ public Factor() {
+ this.factorNum = BigDecimal.valueOf(1);
+ this.factorDen = BigDecimal.valueOf(1);
+ }
+
+ public static Factor processFactor(String factor) {
+ assert (!factor.isEmpty());
+
+ // Remove all spaces in the factor
+ factor = factor.replaceAll("\\s+", "");
+
+ String[] fractions = factor.split("/");
+ assert (fractions.length == 1 || fractions.length == 2);
+
+ if (fractions.length == 1) {
+ return processFactorWithoutDivision(fractions[0]);
+ }
+
+ Factor num = processFactorWithoutDivision(fractions[0]);
+ Factor den = processFactorWithoutDivision(fractions[1]);
+ return num.divide(den);
+ }
+
+ private static Factor processFactorWithoutDivision(String factorWithoutDivision) {
+ Factor result = new Factor();
+ for (String poweredEntity :
+ factorWithoutDivision.split(Pattern.quote("*"))) {
+ result.addPoweredEntity(poweredEntity);
+ }
+
+ return result;
+ }
+
+ /**
+ * Copy this <code>Factor</code>.
+ */
+ protected Factor copy() {
+ Factor result = new Factor();
+ result.factorNum = this.factorNum;
+ result.factorDen = this.factorDen;
+
+ result.exponentFtToM = this.exponentFtToM;
+ result.exponentPi = this.exponentPi;
+ result.exponentGravity = this.exponentGravity;
+ result.exponentG = this.exponentG;
+ result.exponentGalImpToM3 = this.exponentGalImpToM3;
+ result.exponentLbToKg = this.exponentLbToKg;
+ result.exponentGlucoseMolarMass = this.exponentGlucoseMolarMass;
+ result.exponentItemPerMole = this.exponentItemPerMole;
+
+ return result;
+ }
+
+ /**
+ * Returns a single `BigDecimal` that represent the conversion rate after substituting all the constants.
+ *
+ * In ICU4C, see Factor::substituteConstants().
+ */
+ public BigDecimal getConversionRate() {
+ // TODO: this copies all the exponents then doesn't use them at all.
+ Factor resultCollector = this.copy();
+
+ // TODO(icu-units#92): port C++ unit tests to Java.
+ // These values are a hard-coded subset of unitConstants in the
+ // units resources file. A unit test should check that all constants
+ // in the resource file are at least recognised by the code.
+ // In ICU4C, these constants live in constantsValues[].
+ resultCollector.multiply(new BigDecimal("0.3048"), this.exponentFtToM);
+ // TODO: this recalculates this division every time this is called.
+ resultCollector.multiply(new BigDecimal("411557987.0").divide(new BigDecimal("131002976.0"), DECIMAL128), this.exponentPi);
+ resultCollector.multiply(new BigDecimal("9.80665"), this.exponentGravity);
+ resultCollector.multiply(new BigDecimal("6.67408E-11"), this.exponentG);
+ resultCollector.multiply(new BigDecimal("0.00454609"), this.exponentGalImpToM3);
+ 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);
+
+ return resultCollector.factorNum.divide(resultCollector.factorDen, DECIMAL128);
+ }
+
+ /** Multiplies the Factor instance by value^power. */
+ private void multiply(BigDecimal value, int power) {
+ if (power == 0) return;
+
+ BigDecimal absPoweredValue = value.pow(Math.abs(power), DECIMAL128);
+ if (power > 0) {
+ this.factorNum = this.factorNum.multiply(absPoweredValue);
+ } else {
+ this.factorDen = this.factorDen.multiply(absPoweredValue);
+ }
+ }
+
+ /** Apply SI or binary prefix to the Factor. */
+ public Factor applyPrefix(MeasureUnit.MeasurePrefix unitPrefix) {
+ Factor result = this.copy();
+ if (unitPrefix == MeasureUnit.MeasurePrefix.ONE) {
+ return result;
+ }
+
+ int base = unitPrefix.getBase();
+ int power = unitPrefix.getPower();
+ BigDecimal absFactor =
+ BigDecimal.valueOf(base).pow(Math.abs(power), DECIMAL128);
+
+ if (power < 0) {
+ result.factorDen = this.factorDen.multiply(absFactor);
+ return result;
+ }
+
+ result.factorNum = this.factorNum.multiply(absFactor);
+ return result;
+ }
+
+ public Factor power(int power) {
+ Factor result = new Factor();
+ if (power == 0) return result;
+ if (power > 0) {
+ result.factorNum = this.factorNum.pow(power);
+ result.factorDen = this.factorDen.pow(power);
+ } else {
+ result.factorNum = this.factorDen.pow(power * -1);
+ result.factorDen = this.factorNum.pow(power * -1);
+ }
+
+ result.exponentFtToM = this.exponentFtToM * power;
+ result.exponentPi = this.exponentPi * power;
+ result.exponentGravity = this.exponentGravity * power;
+ result.exponentG = this.exponentG * power;
+ result.exponentGalImpToM3 = this.exponentGalImpToM3 * power;
+ result.exponentLbToKg = this.exponentLbToKg * power;
+ result.exponentGlucoseMolarMass = this.exponentGlucoseMolarMass * power;
+ result.exponentItemPerMole = this.exponentItemPerMole * power;
+
+ return result;
+ }
+
+ public Factor divide(Factor other) {
+ Factor result = new Factor();
+ result.factorNum = this.factorNum.multiply(other.factorDen);
+ result.factorDen = this.factorDen.multiply(other.factorNum);
+
+ result.exponentFtToM = this.exponentFtToM - other.exponentFtToM;
+ result.exponentPi = this.exponentPi - other.exponentPi;
+ result.exponentGravity = this.exponentGravity - other.exponentGravity;
+ result.exponentG = this.exponentG - other.exponentG;
+ result.exponentGalImpToM3 = this.exponentGalImpToM3 - other.exponentGalImpToM3;
+ result.exponentLbToKg = this.exponentLbToKg - other.exponentLbToKg;
+ result.exponentGlucoseMolarMass =
+ this.exponentGlucoseMolarMass - other.exponentGlucoseMolarMass;
+ result.exponentItemPerMole = this.exponentItemPerMole - other.exponentItemPerMole;
+
+ return result;
+ }
+
+ public Factor multiply(Factor other) {
+ Factor result = new Factor();
+ result.factorNum = this.factorNum.multiply(other.factorNum);
+ result.factorDen = this.factorDen.multiply(other.factorDen);
+
+ result.exponentFtToM = this.exponentFtToM + other.exponentFtToM;
+ result.exponentPi = this.exponentPi + other.exponentPi;
+ result.exponentGravity = this.exponentGravity + other.exponentGravity;
+ result.exponentG = this.exponentG + other.exponentG;
+ result.exponentGalImpToM3 = this.exponentGalImpToM3 + other.exponentGalImpToM3;
+ result.exponentLbToKg = this.exponentLbToKg + other.exponentLbToKg;
+ result.exponentGlucoseMolarMass =
+ this.exponentGlucoseMolarMass + other.exponentGlucoseMolarMass;
+ result.exponentItemPerMole = this.exponentItemPerMole + other.exponentItemPerMole;
+
+ return result;
+ }
+
+ /**
+ * Adds Entity with power or not. For example, `12 ^ 3` or `12`.
+ *
+ * @param poweredEntity
+ */
+ private void addPoweredEntity(String poweredEntity) {
+ String[] entities = poweredEntity.split(Pattern.quote("^"));
+ assert (entities.length == 1 || entities.length == 2);
+
+ int power = entities.length == 2 ? Integer.parseInt(entities[1]) : 1;
+ this.addEntity(entities[0], power);
+ }
+
+ private void addEntity(String entity, int power) {
+ if ("ft_to_m".equals(entity)) {
+ this.exponentFtToM += power;
+ } else if ("ft2_to_m2".equals(entity)) {
+ this.exponentFtToM += 2 * power;
+ } else if ("ft3_to_m3".equals(entity)) {
+ this.exponentFtToM += 3 * power;
+ } else if ("in3_to_m3".equals(entity)) {
+ this.exponentFtToM += 3 * power;
+ this.factorDen = this.factorDen.multiply(BigDecimal.valueOf(Math.pow(12, 3)));
+ } else if ("gal_to_m3".equals(entity)) {
+ this.factorNum = this.factorNum.multiply(BigDecimal.valueOf(231));
+ this.exponentFtToM += 3 * power;
+ this.factorDen = this.factorDen.multiply(BigDecimal.valueOf(12 * 12 * 12));
+ } else if ("gal_imp_to_m3".equals(entity)) {
+ this.exponentGalImpToM3 += power;
+ } else if ("G".equals(entity)) {
+ this.exponentG += power;
+ } else if ("gravity".equals(entity)) {
+ this.exponentGravity += power;
+ } else if ("lb_to_kg".equals(entity)) {
+ this.exponentLbToKg += power;
+ } else if ("glucose_molar_mass".equals(entity)) {
+ this.exponentGlucoseMolarMass += power;
+ } else if ("item_per_mole".equals(entity)) {
+ this.exponentItemPerMole += power;
+ } else if ("PI".equals(entity)) {
+ this.exponentPi += power;
+ } else {
+ BigDecimal decimalEntity = new BigDecimal(entity).pow(power, DECIMAL128);
+ this.factorNum = this.factorNum.multiply(decimalEntity);
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "UnitsConverter [conversionRate=" + conversionRate + ", offset=" + offset + "]";
+ }
+}
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 e637315..373be1c 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
@@ -2,16 +2,16 @@
// © 2020 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
-
package android.icu.impl.units;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.Iterator;
import android.icu.impl.ICUData;
import android.icu.impl.ICUResourceBundle;
+import android.icu.impl.IllegalIcuArgumentException;
import android.icu.impl.UResource;
-import android.icu.util.MeasureUnit;
import android.icu.util.UResourceBundle;
/**
@@ -19,33 +19,38 @@
* @hide Only a subset of ICU is exposed in Android
*/
public class UnitsData {
- private volatile static String[] simpleUnits = null;
+ // TODO(icu-units#122): this class can use static initialization to load the
+ // data once, and provide access to it via static methods. (Partial change
+ // has been done already.)
+
+ // Array of simple unit IDs.
+ private static String[] simpleUnits = null;
+
+ // Maps from the value associated with each simple unit ID to a category
+ // index number.
+ private static int[] simpleUnitCategories = null;
+
private ConversionRates conversionRates;
private UnitPreferences unitPreferences;
- /**
- * Pairs of categories and the corresponding base units.
- */
- private Categories categories;
+
public UnitsData() {
this.conversionRates = new ConversionRates();
this.unitPreferences = new UnitPreferences();
- this.categories = new Categories();
}
public static String[] getSimpleUnits() {
- if (simpleUnits != null) {
- return simpleUnits;
- }
+ return simpleUnits;
+ }
+ static {
// Read simple units
ICUResourceBundle resource;
resource = (ICUResourceBundle) UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME, "units");
SimpleUnitIdentifiersSink sink = new SimpleUnitIdentifiersSink();
resource.getAllItemsWithFallback("convertUnits", sink);
simpleUnits = sink.simpleUnits;
-
- return simpleUnits;
+ simpleUnitCategories = sink.simpleUnitCategories;
}
public ConversionRates getConversionRates() {
@@ -56,24 +61,54 @@
return unitPreferences;
}
+ public static int getCategoryIndexOfSimpleUnit(int simpleUnitIndex) {
+ return simpleUnitCategories[simpleUnitIndex];
+ }
+
/**
* @param measureUnit An instance of MeasureUnitImpl.
* @return the corresponding category.
*/
public String getCategory(MeasureUnitImpl measureUnit) {
- MeasureUnitImpl baseMeasureUnit
+ MeasureUnitImpl baseMeasureUnitImpl
= this.getConversionRates().extractCompoundBaseUnit(measureUnit);
- String baseUnitIdentifier = MeasureUnit.fromMeasureUnitImpl(baseMeasureUnit).getIdentifier();
+ baseMeasureUnitImpl.serialize();
+ String identifier = baseMeasureUnitImpl.getIdentifier();
- if (baseUnitIdentifier.equals("meter-per-cubic-meter")) {
- // TODO(CLDR-13787,hugovdm): special-casing the consumption-inverse
- // case. Once CLDR-13787 is clarified, this should be generalised (or
- // possibly removed):
- return "consumption-inverse";
+ Integer index = Categories.baseUnitToIndex.get(identifier);
+
+ // In case the base unit identifier did not match any entry.
+ if (index == null) {
+ baseMeasureUnitImpl.takeReciprocal();
+ baseMeasureUnitImpl.serialize();
+ identifier = baseMeasureUnitImpl.getIdentifier();
+ index = Categories.baseUnitToIndex.get(identifier);
}
- return this.categories.mapFromUnitToCategory.get(baseUnitIdentifier);
+ // In case the reciprocal of the base unit identifier did not match any entry.
+ baseMeasureUnitImpl.takeReciprocal(); // return to original form
+ MeasureUnitImpl simplifiedUnit = baseMeasureUnitImpl.copyAndSimplify();
+ if (index == null) {
+ simplifiedUnit.serialize();
+ identifier = simplifiedUnit.getIdentifier();
+ index = Categories.baseUnitToIndex.get(identifier);
+ }
+
+ // In case the simplified base unit identifier did not match any entry.
+ if (index == null) {
+ simplifiedUnit.takeReciprocal();
+ simplifiedUnit.serialize();
+ identifier = simplifiedUnit.getIdentifier();
+ index = Categories.baseUnitToIndex.get(identifier);
+ }
+
+ // If there is no match at all, throw an exception.
+ if (index == null) {
+ throw new IllegalIcuArgumentException("This unit does not has a category" + measureUnit.getIdentifier());
+ }
+
+ return Categories.indexToCategory[index];
}
public UnitPreferences.UnitPreference[] getPreferencesFor(String category, String usage, String region) {
@@ -85,6 +120,7 @@
*/
public static class SimpleUnitIdentifiersSink extends UResource.Sink {
String[] simpleUnits = null;
+ int[] simpleUnitCategories = null;
@Override
public void put(UResource.Key key, UResource.Value value, boolean noFallback) {
@@ -93,6 +129,7 @@
UResource.Table simpleUnitsTable = value.getTable();
ArrayList<String> simpleUnits = new ArrayList<>();
+ ArrayList<Integer> simpleUnitCategories = new ArrayList<>();
for (int i = 0; simpleUnitsTable.getKeyAndValue(i, key, value); i++) {
if (key.toString().equals("kilogram")) {
@@ -103,10 +140,28 @@
continue;
}
+ // Find the base target unit for this simple unit
+ UResource.Table table = value.getTable();
+ if (!table.findValue("target", value)) {
+ // TODO: is there a more idiomatic way to deal with Resource
+ // Sink data errors in ICU4J? For now we just assert-fail,
+ // and otherwise skip bad data:
+ assert false : "Could not find \"target\" for simple unit: " + key;
+ continue;
+ }
+ String target = value.getString();
+
simpleUnits.add(key.toString());
+ simpleUnitCategories.add(Categories.baseUnitToIndex.get(target));
}
this.simpleUnits = simpleUnits.toArray(new String[0]);
+ this.simpleUnitCategories = new int[simpleUnitCategories.size()];
+ Iterator<Integer> iter = simpleUnitCategories.iterator();
+ for (int i = 0; i < this.simpleUnitCategories.length; i++)
+ {
+ this.simpleUnitCategories[i] = iter.next().intValue();
+ }
}
}
@@ -115,6 +170,9 @@
* @hide Only a subset of ICU is exposed in Android
*/
public static class Constants {
+ // TODO: consider moving the Trie-offset-related constants into
+ // MeasureUnitImpl.java, the only place they're being used?
+
// Trie value offset for simple units, e.g. "gram", "nautical-mile",
// "fluid-ounce-imperial".
public static final int kSimpleUnitOffset = 512;
@@ -129,9 +187,9 @@
// Trie value offset for compound parts, e.g. "-per-", "-", "-and-".
public final static int kCompoundPartOffset = 128;
- // Trie value offset for SI Prefixes. This is big enough to ensure we only
- // insert positive integers into the trie.
- public static final int kSIPrefixOffset = 64;
+ // Trie value offset for SI or binary prefixes. This is big enough to
+ // ensure we only insert positive integers into the trie.
+ public static final int kPrefixOffset = 64;
/* Tables Names*/
@@ -142,29 +200,44 @@
public static final String DEFAULT_USAGE = "default";
}
+ // Deals with base units and categories, e.g. "meter-per-second" --> "speed".
/**
* @hide Only a subset of ICU is exposed in Android
*/
public static class Categories {
+ /**
+ * Maps from base unit to an index value: an index into the
+ * indexToCategory array.
+ */
+ static HashMap<String, Integer> baseUnitToIndex;
/**
- * Contains the map between units in their base units into their category.
- * For example: meter-per-second --> "speed"
+ * Our official array of category strings - categories are identified by
+ * indeces into this array.
*/
- HashMap<String, String> mapFromUnitToCategory;
+ static String[] indexToCategory;
-
- public Categories() {
+ static {
// Read unit Categories
ICUResourceBundle resource;
resource = (ICUResourceBundle) UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME, "units");
CategoriesSink sink = new CategoriesSink();
resource.getAllItemsWithFallback(Constants.CATEGORY_TABLE_NAME, sink);
- this.mapFromUnitToCategory = sink.getMapFromUnitToCategory();
+ baseUnitToIndex = sink.mapFromUnitToIndex;
+ indexToCategory = sink.categories.toArray(new String[0]);
}
}
/**
+ * A Resource Sink that collects information from `unitQuantities` in the
+ * `units` resource to provide key->value lookups from base unit to
+ * category, as well as preserving ordering information for these
+ * categories. See `units.txt`.
+ *
+ * For example: "kilogram" -> "mass", "meter-per-second" -> "speed".
+ *
+ * In Java unitQuantity values are collected in order into an ArrayList,
+ * while unitQuantity key-to-index lookups are handled with a HashMap.
* @hide Only a subset of ICU is exposed in Android
*/
public static class CategoriesSink extends UResource.Sink {
@@ -172,26 +245,30 @@
* Contains the map between units in their base units into their category.
* For example: meter-per-second --> "speed"
*/
- HashMap<String, String> mapFromUnitToCategory;
+ HashMap<String, Integer> mapFromUnitToIndex;
+ ArrayList<String> categories;
public CategoriesSink() {
- mapFromUnitToCategory = new HashMap<>();
+ mapFromUnitToIndex = new HashMap<>();
+ categories = new ArrayList<>();
}
@Override
public void put(UResource.Key key, UResource.Value value, boolean noFallback) {
assert (key.toString().equals(Constants.CATEGORY_TABLE_NAME));
- assert (value.getType() == UResourceBundle.TABLE);
+ assert (value.getType() == UResourceBundle.ARRAY);
- UResource.Table categoryTable = value.getTable();
- for (int i = 0; categoryTable.getKeyAndValue(i, key, value); i++) {
- assert (value.getType() == UResourceBundle.STRING);
- mapFromUnitToCategory.put(key.toString(), value.toString());
+ UResource.Array categoryArray = value.getArray();
+ for (int i=0; categoryArray.getValue(i, value); i++) {
+ assert (value.getType() == UResourceBundle.TABLE);
+ UResource.Table table = value.getTable();
+ assert (table.getSize() == 1)
+ : "expecting single-entry table, got size: " + table.getSize();
+ table.getKeyAndValue(0, key, value);
+ assert value.getType() == UResourceBundle.STRING : "expecting category string";
+ mapFromUnitToIndex.put(key.toString(), categories.size());
+ categories.add(value.toString());
}
}
-
- public HashMap<String, String> getMapFromUnitToCategory() {
- return mapFromUnitToCategory;
- }
}
}
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 9a6a696..d1909fa 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
@@ -10,7 +10,6 @@
import android.icu.impl.IllegalIcuArgumentException;
import android.icu.impl.number.MicroProps;
import android.icu.number.Precision;
-import android.icu.util.Measure;
import android.icu.util.MeasureUnit;
/**
@@ -49,13 +48,16 @@
private ArrayList<MeasureUnit> outputUnits_ = new ArrayList<>();
private ArrayList<ConverterPreference> converterPreferences_ = new ArrayList<>();
- public UnitsRouter(MeasureUnitImpl inputUnitImpl, String region, String usage) {
+ public UnitsRouter(String inputUnitIdentifier, String region, String usage) {
+ this(MeasureUnitImpl.forIdentifier(inputUnitIdentifier), region, usage);
+ }
+
+ public UnitsRouter(MeasureUnitImpl inputUnit, String region, 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();
- //MeasureUnitImpl inputUnitImpl = MeasureUnitImpl.forMeasureUnitMaybeCopy(inputUnit);
- String category = data.getCategory(inputUnitImpl);
+ String category = data.getCategory(inputUnit);
UnitPreferences.UnitPreference[] unitPreferences = data.getPreferencesFor(category, usage, region);
for (int i = 0; i < unitPreferences.length; ++i) {
@@ -76,7 +78,7 @@
}
outputUnits_.add(complexTargetUnitImpl.build());
- converterPreferences_.add(new ConverterPreference(inputUnitImpl, complexTargetUnitImpl,
+ converterPreferences_.add(new ConverterPreference(inputUnit, complexTargetUnitImpl,
preference.getGeq(), precision,
data.getConversionRates()));
}
@@ -182,19 +184,15 @@
* @hide Only a subset of ICU is exposed in Android
*/
public class RouteResult {
- // A list of measures: a single measure for single units, multiple measures
- // for mixed units.
- //
- // TODO(icu-units/icu#21): figure out the right mixed unit API.
- public final List<Measure> measures;
+ public final ComplexUnitsConverter.ComplexConverterResult complexConverterResult;
// The output unit for this RouteResult. This may be a MIXED unit - for
// example: "yard-and-foot-and-inch", for which `measures` will have three
// elements.
public final MeasureUnitImpl outputUnit;
- RouteResult(List<Measure> measures, MeasureUnitImpl outputUnit) {
- this.measures = measures;
+ RouteResult(ComplexUnitsConverter.ComplexConverterResult complexConverterResult, MeasureUnitImpl outputUnit) {
+ this.complexConverterResult = complexConverterResult;
this.outputUnit = outputUnit;
}
}
diff --git a/android_icu4j/src/main/java/android/icu/lang/CharacterProperties.java b/android_icu4j/src/main/java/android/icu/lang/CharacterProperties.java
index 9e57d12..d6c4d89 100644
--- a/android_icu4j/src/main/java/android/icu/lang/CharacterProperties.java
+++ b/android_icu4j/src/main/java/android/icu/lang/CharacterProperties.java
@@ -5,6 +5,7 @@
package android.icu.lang;
import android.icu.impl.CharacterPropertiesImpl;
+import android.icu.impl.EmojiProps;
import android.icu.text.UnicodeSet;
import android.icu.util.CodePointMap;
import android.icu.util.CodePointTrie;
@@ -30,6 +31,15 @@
private static UnicodeSet makeSet(int property) {
UnicodeSet set = new UnicodeSet();
+ if (UProperty.BASIC_EMOJI <= property && property <= UProperty.RGI_EMOJI) {
+ // property of strings
+ EmojiProps.INSTANCE.addStrings(property, set);
+ if (property != UProperty.BASIC_EMOJI && property != UProperty.RGI_EMOJI) {
+ // property of _only_ strings
+ return set.freeze();
+ }
+ }
+
UnicodeSet inclusions = CharacterPropertiesImpl.getInclusionsForProperty(property);
int numRanges = inclusions.getRangeCount();
int startHasProperty = -1;
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 1e32bc8..da7dda7 100644
--- a/android_icu4j/src/main/java/android/icu/lang/UCharacter.java
+++ b/android_icu4j/src/main/java/android/icu/lang/UCharacter.java
@@ -17,6 +17,7 @@
import java.util.Map;
import android.icu.impl.CaseMapImpl;
+import android.icu.impl.EmojiProps;
import android.icu.impl.IllegalIcuArgumentException;
import android.icu.impl.Trie2;
import android.icu.impl.UBiDiProps;
@@ -85,9 +86,9 @@
* For more information see
* <a href="http://www.unicode/org/ucd/">"About the Unicode Character Database"</a>
* (http://www.unicode.org/ucd/)
- * and the <a href="http://www.icu-project.org/userguide/properties.html">ICU
+ * and the <a href="https://unicode-org.github.io/icu/userguide/strings/properties">ICU
* User Guide chapter on Properties</a>
- * (http://www.icu-project.org/userguide/properties.html).
+ * (https://unicode-org.github.io/icu/userguide/strings/properties).
* <p>
* There are also functions that provide easy migration from C/POSIX functions
* like isblank(). Their use is generally discouraged because the C/POSIX
@@ -145,6 +146,28 @@
public final class UCharacter implements ECharacterCategory, ECharacterDirection
{
+ /**
+ * Lead surrogate bitmask
+ */
+ private static final int LEAD_SURROGATE_BITMASK = 0xFFFFFC00;
+
+ /**
+ * Trail surrogate bitmask
+ */
+ private static final int TRAIL_SURROGATE_BITMASK = 0xFFFFFC00;
+
+ /**
+ * Lead surrogate bits
+ */
+ private static final int LEAD_SURROGATE_BITS = 0xD800;
+
+ /**
+ * Trail surrogate bits
+ */
+ private static final int TRAIL_SURROGATE_BITS = 0xDC00;
+
+ private static final int U16_SURROGATE_OFFSET = ((0xd800 << 10) + 0xdc00 - 0x10000);
+
// public inner classes ----------------------------------------------
/**
@@ -1051,6 +1074,33 @@
/***/
public static final int YEZIDI_ID = 308; /*[10E80]*/
+ // New blocks in Unicode 14.0
+
+ /***/
+ public static final int ARABIC_EXTENDED_B_ID = 309; /*[0870]*/
+ /***/
+ public static final int CYPRO_MINOAN_ID = 310; /*[12F90]*/
+ /***/
+ public static final int ETHIOPIC_EXTENDED_B_ID = 311; /*[1E7E0]*/
+ /***/
+ public static final int KANA_EXTENDED_B_ID = 312; /*[1AFF0]*/
+ /***/
+ public static final int LATIN_EXTENDED_F_ID = 313; /*[10780]*/
+ /***/
+ public static final int LATIN_EXTENDED_G_ID = 314; /*[1DF00]*/
+ /***/
+ public static final int OLD_UYGHUR_ID = 315; /*[10F70]*/
+ /***/
+ public static final int TANGSA_ID = 316; /*[16A70]*/
+ /***/
+ public static final int TOTO_ID = 317; /*[1E290]*/
+ /***/
+ public static final int UNIFIED_CANADIAN_ABORIGINAL_SYLLABICS_EXTENDED_A_ID = 318; /*[11AB0]*/
+ /***/
+ public static final int VITHKUQI_ID = 319; /*[10570]*/
+ /***/
+ public static final int ZNAMENNY_MUSICAL_NOTATION_ID = 320; /*[1CF00]*/
+
/**
* One more than the highest normal UnicodeBlock value.
* The highest value is available via UCharacter.getIntPropertyMaxValue(UProperty.BLOCK).
@@ -1059,7 +1109,7 @@
* @hide unsupported on Android
*/
@Deprecated
- public static final int COUNT = 309;
+ public static final int COUNT = 321;
// blocks objects ---------------------------------------------------
@@ -2253,6 +2303,45 @@
/***/
public static final UnicodeBlock YEZIDI = new UnicodeBlock("YEZIDI", YEZIDI_ID); /*[10E80]*/
+ // New blocks in Unicode 14.0
+
+ /***/
+ public static final UnicodeBlock ARABIC_EXTENDED_B =
+ new UnicodeBlock("ARABIC_EXTENDED_B", ARABIC_EXTENDED_B_ID); /*[0870]*/
+ /***/
+ public static final UnicodeBlock CYPRO_MINOAN =
+ new UnicodeBlock("CYPRO_MINOAN", CYPRO_MINOAN_ID); /*[12F90]*/
+ /***/
+ public static final UnicodeBlock ETHIOPIC_EXTENDED_B =
+ new UnicodeBlock("ETHIOPIC_EXTENDED_B", ETHIOPIC_EXTENDED_B_ID); /*[1E7E0]*/
+ /***/
+ public static final UnicodeBlock KANA_EXTENDED_B =
+ new UnicodeBlock("KANA_EXTENDED_B", KANA_EXTENDED_B_ID); /*[1AFF0]*/
+ /***/
+ public static final UnicodeBlock LATIN_EXTENDED_F =
+ new UnicodeBlock("LATIN_EXTENDED_F", LATIN_EXTENDED_F_ID); /*[10780]*/
+ /***/
+ public static final UnicodeBlock LATIN_EXTENDED_G =
+ new UnicodeBlock("LATIN_EXTENDED_G", LATIN_EXTENDED_G_ID); /*[1DF00]*/
+ /***/
+ public static final UnicodeBlock OLD_UYGHUR =
+ new UnicodeBlock("OLD_UYGHUR", OLD_UYGHUR_ID); /*[10F70]*/
+ /***/
+ public static final UnicodeBlock TANGSA = new UnicodeBlock("TANGSA", TANGSA_ID); /*[16A70]*/
+ /***/
+ public static final UnicodeBlock TOTO = new UnicodeBlock("TOTO", TOTO_ID); /*[1E290]*/
+ /***/
+ public static final UnicodeBlock UNIFIED_CANADIAN_ABORIGINAL_SYLLABICS_EXTENDED_A =
+ new UnicodeBlock("UNIFIED_CANADIAN_ABORIGINAL_SYLLABICS_EXTENDED_A",
+ UNIFIED_CANADIAN_ABORIGINAL_SYLLABICS_EXTENDED_A_ID); /*[11AB0]*/
+ /***/
+ public static final UnicodeBlock VITHKUQI =
+ new UnicodeBlock("VITHKUQI", VITHKUQI_ID); /*[10570]*/
+ /***/
+ public static final UnicodeBlock ZNAMENNY_MUSICAL_NOTATION =
+ new UnicodeBlock("ZNAMENNY_MUSICAL_NOTATION",
+ ZNAMENNY_MUSICAL_NOTATION_ID); /*[1CF00]*/
+
/**
*/
public static final UnicodeBlock INVALID_CODE
@@ -2795,6 +2884,11 @@
/***/
public static final int HANIFI_ROHINGYA_PA = 101;
+ /***/
+ public static final int THIN_YEH = 102;
+ /***/
+ public static final int VERTICAL_TAIL = 103;
+
/**
* One more than the highest normal JoiningGroup value.
* The highest value is available via UCharacter.getIntPropertyMaxValue(UProperty.JoiningGroup).
@@ -2803,7 +2897,7 @@
* @hide unsupported on Android
*/
@Deprecated
- public static final int COUNT = 102;
+ public static final int COUNT = 104;
}
/**
@@ -3726,7 +3820,7 @@
* one-to-one mappings; it also omits information about context-sensitive
* case mappings.<br> For more information about Unicode case mapping
* please refer to the
- * <a href=http://www.unicode.org/unicode/reports/tr21/>Technical report
+ * <a href=https://www.unicode.org/reports/tr21/>Technical report
* #21</a>.<br>
* Up-to-date Unicode implementation of java.lang.Character.isLowerCase()
* @param ch code point to determine if it is in lowercase
@@ -3802,7 +3896,7 @@
* one-to-one mappings; it also omits information about context-sensitive
* case mappings.<br>
* For more information about Unicode case mapping please refer to the
- * <a href=http://www.unicode.org/unicode/reports/tr21/>
+ * <a href=https://www.unicode.org/reports/tr21/>
* Technical report #21</a>.<br>
* Up-to-date Unicode implementation of java.lang.Character.isTitleCase().
* @param ch code point to determine if it is in title case
@@ -3834,7 +3928,7 @@
* </ul>
* Up-to-date Unicode implementation of
* java.lang.Character.isUnicodeIdentifierPart().<br>
- * See <a href=http://www.unicode.org/unicode/reports/tr8/>UTR #8</a>.
+ * See <a href=https://www.unicode.org/reports/tr8/>UTR #8</a>.
* @param ch code point to determine if is can be part of a Unicode
* identifier
* @return true if code point is any character belonging a unicode
@@ -3872,7 +3966,7 @@
* </ul>
* Up-to-date Unicode implementation of
* java.lang.Character.isUnicodeIdentifierStart().<br>
- * See <a href=http://www.unicode.org/unicode/reports/tr8/>UTR #8</a>.
+ * See <a href=https://www.unicode.org/reports/tr8/>UTR #8</a>.
* @param ch code point to determine if it can start a Unicode identifier
* @return true if code point is the first character belonging a unicode
* identifier
@@ -3898,7 +3992,7 @@
* U+0000..U+0008, U+000E..U+001B, U+007F..U+009F.<br>
* Up-to-date Unicode implementation of
* java.lang.Character.isIdentifierIgnorable().<br>
- * See <a href=http://www.unicode.org/unicode/reports/tr8/>UTR #8</a>.
+ * See <a href=https://www.unicode.org/reports/tr8/>UTR #8</a>.
* <p>Note that Unicode just recommends to ignore Cf (format controls).
* @param ch code point to be determined if it can be ignored in a Unicode
* identifier.
@@ -3926,7 +4020,7 @@
* For example, the case conversion for dot-less i and dotted I in Turkish,
* or for final sigma in Greek.
* For more information about Unicode case mapping please refer to the
- * <a href=http://www.unicode.org/unicode/reports/tr21/>
+ * <a href=https://www.unicode.org/reports/tr21/>
* Technical report #21</a>.<br>
* Up-to-date Unicode implementation of java.lang.Character.isUpperCase().
* @param ch code point to determine if it is in uppercase
@@ -3951,7 +4045,7 @@
* Full case mappings are applied by the case mapping functions
* that take String parameters rather than code points (int).
* See also the User Guide chapter on C/POSIX migration:
- * http://www.icu-project.org/userguide/posix.html#case_mappings
+ * https://unicode-org.github.io/icu/userguide/icu/posix#case-mappings
*
* @param ch code point whose lowercase equivalent is to be retrieved
* @return the lowercase equivalent code point
@@ -3998,7 +4092,7 @@
* Full case mappings are applied by the case mapping functions
* that take String parameters rather than code points (int).
* See also the User Guide chapter on C/POSIX migration:
- * http://www.icu-project.org/userguide/posix.html#case_mappings
+ * https://unicode-org.github.io/icu/userguide/icu/posix#case-mappings
*
* @param ch code point whose title case is to be retrieved
* @return titlecase code point
@@ -4020,7 +4114,7 @@
* Full case mappings are applied by the case mapping functions
* that take String parameters rather than code points (int).
* See also the User Guide chapter on C/POSIX migration:
- * http://www.icu-project.org/userguide/posix.html#case_mappings
+ * https://unicode-org.github.io/icu/userguide/icu/posix#case-mappings
*
* @param ch code point whose uppercase is to be retrieved
* @return uppercase code point
@@ -4241,7 +4335,7 @@
* UCharacter.MIN_VALUE and UCharacter.MAX_VALUE or does not have a name.
* <br>
* Note calling any methods related to code point names, e.g. get*Name*()
- * incurs a one-time initialisation cost to construct the name tables.
+ * incurs a one-time initialization cost to construct the name tables.
* @param ch the code point for which to get the name
* @return most current Unicode name
*/
@@ -4297,7 +4391,7 @@
* "<codepoint_type-codepoint_hex_digits>". E.g., <noncharacter-fffe>
* </ul>
* Note calling any methods related to code point names, e.g. get*Name*()
- * incurs a one-time initialisation cost to construct the name tables.
+ * incurs a one-time initialization cost to construct the name tables.
* @param ch the code point for which to get the name
* @return a name for the argument codepoint
*/
@@ -4311,7 +4405,7 @@
* UCharacter.MIN_VALUE and UCharacter.MAX_VALUE or does not have a name.
* <br>
* Note calling any methods related to code point names, e.g. get*Name*()
- * incurs a one-time initialisation cost to construct the name tables.
+ * incurs a one-time initialization cost to construct the name tables.
* @param ch the code point for which to get the name alias
* @return Unicode name alias, or null
*/
@@ -4341,7 +4435,7 @@
* <strong>[icu]</strong> <p>Finds a Unicode code point by its most current Unicode name and
* return its code point value. All Unicode names are in uppercase.
* Note calling any methods related to code point names, e.g. get*Name*()
- * incurs a one-time initialisation cost to construct the name tables.
+ * incurs a one-time initialization cost to construct the name tables.
* @param name most current Unicode character name whose code point is to
* be returned
* @return code point or -1 if name is not found
@@ -4380,7 +4474,7 @@
* "<codepoint_type-codepoint_hex_digits>". E.g. <noncharacter-FFFE>
* </ul>
* Note calling any methods related to code point names, e.g. get*Name*()
- * incurs a one-time initialisation cost to construct the name tables.
+ * incurs a one-time initialization cost to construct the name tables.
* @param name codepoint name
* @return code point associated with the name or -1 if the name is not
* found.
@@ -4394,7 +4488,7 @@
* <strong>[icu]</strong> <p>Find a Unicode character by its corrected name alias and return
* its code point value. All Unicode names are in uppercase.
* Note calling any methods related to code point names, e.g. get*Name*()
- * incurs a one-time initialisation cost to construct the name tables.
+ * incurs a one-time initialization cost to construct the name tables.
* @param name Unicode name alias whose code point is to be returned
* @return code point or -1 if name is not found
*/
@@ -4596,6 +4690,26 @@
/**
* <strong>[icu]</strong> Returns a code point corresponding to the two surrogate code units.
*
+ * @param lead the lead unit
+ * (In ICU 2.1-69 the type of both parameters was <code>char</code>.)
+ * @param trail the trail unit
+ * @return code point if lead and trail form a valid surrogate pair.
+ * @exception IllegalArgumentException thrown when the code units do
+ * not form a valid surrogate pair
+ * @see #toCodePoint(int, int)
+ */
+ public static int getCodePoint(int lead, int trail)
+ {
+ if (isHighSurrogate(lead) && isLowSurrogate(trail)) {
+ return toCodePoint(lead, trail);
+ }
+ throw new IllegalArgumentException("Not a valid surrogate pair");
+ }
+
+ // BEGIN Android patch: Keep the `char` version on Android. See ICU-21655
+ /**
+ * <strong>[icu]</strong> Returns a code point corresponding to the two surrogate code units.
+ *
* @param lead the lead char
* @param trail the trail char
* @return code point if surrogate characters are valid.
@@ -4604,11 +4718,9 @@
*/
public static int getCodePoint(char lead, char trail)
{
- if (Character.isSurrogatePair(lead, trail)) {
- return Character.toCodePoint(lead, trail);
- }
- throw new IllegalArgumentException("Illegal surrogate characters");
+ return getCodePoint((int) lead, (int) trail);
}
+ // END Android patch: Keep the `char` version on Android. See ICU-21655
/**
* <strong>[icu]</strong> Returns the code point corresponding to the BMP code point.
@@ -4856,7 +4968,7 @@
* Full case mappings are applied by the case mapping functions
* that take String parameters rather than code points (int).
* See also the User Guide chapter on C/POSIX migration:
- * http://www.icu-project.org/userguide/posix.html#case_mappings
+ * https://unicode-org.github.io/icu/userguide/icu/posix#case-mappings
*
* @param ch the character to be converted
* @param defaultmapping Indicates whether the default mappings defined in
@@ -4923,7 +5035,7 @@
* Full case mappings are applied by the case mapping functions
* that take String parameters rather than code points (int).
* See also the User Guide chapter on C/POSIX migration:
- * http://www.icu-project.org/userguide/posix.html#case_mappings
+ * https://unicode-org.github.io/icu/userguide/icu/posix#case-mappings
*
* @param ch the character to be converted
* @param options A bit set for special processing. Currently the recognised options
@@ -5201,6 +5313,43 @@
}
/**
+ * <strong>[icu]</strong> Returns true if the property is true for the string.
+ * Same as {@link #hasBinaryProperty(int, int)}
+ * if the string contains exactly one code point.
+ *
+ * <p>Most properties apply only to single code points.
+ * <a href="https://www.unicode.org/reports/tr51/#Emoji_Sets">UTS #51 Unicode Emoji</a>
+ * defines several properties of strings.
+ *
+ * @param s String to test.
+ * @param property UProperty selector constant, identifies which binary property to check.
+ * Must be BINARY_START<=which<BINARY_LIMIT.
+ * @return true or false according to the binary Unicode property value for the string.
+ * Also false if <code>property</code> is out of bounds or if the Unicode version
+ * 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();
+ if (length == 1) {
+ return hasBinaryProperty(s.charAt(0), property); // single code point
+ } else if (length == 2) {
+ // first code point
+ int c = Character.codePointAt(s, 0);
+ if (Character.charCount(c) == length) {
+ return hasBinaryProperty(c, property); // single code point
+ }
+ }
+ // Only call into EmojiProps for a relevant property,
+ // so that we not unnecessarily try to load its data file.
+ return UProperty.BASIC_EMOJI <= property && property <= UProperty.RGI_EMOJI &&
+ EmojiProps.INSTANCE.hasBinaryProperty(s, property);
+ }
+
+ /**
* <strong>[icu]</strong> <p>Check if a code point has the Alphabetic Unicode property.
* <p>Same as UCharacter.hasBinaryProperty(ch, UProperty.ALPHABETIC).
* <p>Different from UCharacter.isLetter(ch)!
@@ -5456,25 +5605,67 @@
}
/**
- * Same as {@link Character#isHighSurrogate}.
+ * Same as {@link Character#isHighSurrogate},
+ * except that the ICU version accepts <code>int</code> for code points.
+ *
+ * @param codePoint the code point to check
+ * (In ICU 3.0-69 the type of this parameter was <code>char</code>.)
+ * @return true if codePoint is a high (lead) surrogate
+ */
+ public static boolean isHighSurrogate(int codePoint) {
+ return (codePoint & LEAD_SURROGATE_BITMASK) == LEAD_SURROGATE_BITS;
+ }
+
+ // BEGIN Android patch: Keep the `char` version on Android. See ICU-21655
+ /**
+ * Same as {@link Character#isHighSurrogate},
*
* @param ch the char to check
* @return true if ch is a high (lead) surrogate
*/
public static boolean isHighSurrogate(char ch) {
- return Character.isHighSurrogate(ch);
+ return isHighSurrogate((int) ch);
}
+ // END Android patch: Keep the `char` version on Android. See ICU-21655
/**
- * Same as {@link Character#isLowSurrogate}.
+ * Same as {@link Character#isLowSurrogate},
+ * except that the ICU version accepts <code>int</code> for code points.
+ *
+ * @param codePoint the code point to check
+ * (In ICU 3.0-69 the type of this parameter was <code>char</code>.)
+ * @return true if codePoint is a low (trail) surrogate
+ */
+ public static boolean isLowSurrogate(int codePoint) {
+ return (codePoint & TRAIL_SURROGATE_BITMASK) == TRAIL_SURROGATE_BITS;
+ }
+
+ // BEGIN Android patch: Keep the `char` version on Android. See ICU-21655
+ /**
+ * Same as {@link Character#isLowSurrogate},
*
* @param ch the char to check
* @return true if ch is a low (trail) surrogate
*/
public static boolean isLowSurrogate(char ch) {
- return Character.isLowSurrogate(ch);
+ return isLowSurrogate((int) ch);
+ }
+ // END Android patch: Keep the `char` version on Android. See ICU-21655
+
+ /**
+ * Same as {@link Character#isSurrogatePair},
+ * except that the ICU version accepts <code>int</code> for code points.
+ *
+ * @param high the high (lead) unit
+ * (In ICU 3.0-69 the type of both parameters was <code>char</code>.)
+ * @param low the low (trail) unit
+ * @return true if high, low form a surrogate pair
+ */
+ public static final boolean isSurrogatePair(int high, int low) {
+ return isHighSurrogate(high) && isLowSurrogate(low);
}
+ // BEGIN Android patch: Keep the `char` version on Android. See ICU-21655
/**
* Same as {@link Character#isSurrogatePair}.
*
@@ -5483,8 +5674,9 @@
* @return true if high, low form a surrogate pair
*/
public static final boolean isSurrogatePair(char high, char low) {
- return Character.isSurrogatePair(high, low);
+ return isSurrogatePair((int) high, (int) low);
}
+ // END Android patch: Keep the `char` version on Android. See ICU-21655
/**
* Same as {@link Character#charCount}.
@@ -5499,6 +5691,24 @@
}
/**
+ * Same as {@link Character#toCodePoint},
+ * except that the ICU version accepts <code>int</code> for code points.
+ * Returns the code point represented by the two surrogate code units.
+ * This does not check the surrogate pair for validity.
+ *
+ * @param high the high (lead) surrogate
+ * (In ICU 3.0-69 the type of both parameters was <code>char</code>.)
+ * @param low the low (trail) surrogate
+ * @return the code point formed by the surrogate pair
+ * @see #getCodePoint(int, int)
+ */
+ public static final int toCodePoint(int high, int low) {
+ // see ICU4C U16_GET_SUPPLEMENTARY()
+ return (high << 10) + low - U16_SURROGATE_OFFSET;
+ }
+
+ // BEGIN Android patch: Keep the `char` version on Android. See ICU-21655
+ /**
* Same as {@link Character#toCodePoint}.
* Returns the code point represented by the two surrogate code units.
* This does not check the surrogate pair for validity.
@@ -5508,8 +5718,9 @@
* @return the code point formed by the surrogate pair
*/
public static final int toCodePoint(char high, char low) {
- return Character.toCodePoint(high, low);
+ return toCodePoint((int) high, (int) low);
}
+ // END Android patch: Keep the `char` version on Android. See ICU-21655
/**
* Same as {@link Character#codePointAt(CharSequence, int)}.
diff --git a/android_icu4j/src/main/java/android/icu/lang/UCharacterCategory.java b/android_icu4j/src/main/java/android/icu/lang/UCharacterCategory.java
index 179ffed..c4328ec 100644
--- a/android_icu4j/src/main/java/android/icu/lang/UCharacterCategory.java
+++ b/android_icu4j/src/main/java/android/icu/lang/UCharacterCategory.java
@@ -104,7 +104,7 @@
// private constructor -----------------------------------------------
///CLOVER:OFF
/**
- * Private constructor to prevent initialisation
+ * Private constructor to prevent initialization
*/
private UCharacterCategory()
{
diff --git a/android_icu4j/src/main/java/android/icu/lang/UCharacterDirection.java b/android_icu4j/src/main/java/android/icu/lang/UCharacterDirection.java
index e2390e2..3d6541c 100644
--- a/android_icu4j/src/main/java/android/icu/lang/UCharacterDirection.java
+++ b/android_icu4j/src/main/java/android/icu/lang/UCharacterDirection.java
@@ -26,7 +26,7 @@
// private constructor =========================================
///CLOVER:OFF
/**
- * Private constructor to prevent initialisation
+ * Private constructor to prevent initialization
*/
private UCharacterDirection()
{
diff --git a/android_icu4j/src/main/java/android/icu/lang/UCharacterEnums.java b/android_icu4j/src/main/java/android/icu/lang/UCharacterEnums.java
index 3b6bdce..64ebf7e 100644
--- a/android_icu4j/src/main/java/android/icu/lang/UCharacterEnums.java
+++ b/android_icu4j/src/main/java/android/icu/lang/UCharacterEnums.java
@@ -15,7 +15,7 @@
*/
public class UCharacterEnums {
- /** This is just a namespace, it is not instantiatable. */
+ /** This is just a namespace, it is not instantiable. */
///CLOVER:OFF
private UCharacterEnums() {}
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 1be09f4..504d8d6 100644
--- a/android_icu4j/src/main/java/android/icu/lang/UProperty.java
+++ b/android_icu4j/src/main/java/android/icu/lang/UProperty.java
@@ -492,6 +492,55 @@
* See http://www.unicode.org/reports/tr51/#Emoji_Properties
*/
public static final int EXTENDED_PICTOGRAPHIC=64;
+ /**
+ * 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;
/**
* One more than the last constant for binary Unicode properties.
@@ -499,7 +548,7 @@
* @hide unsupported on Android
*/
@Deprecated
- public static final int BINARY_LIMIT = 65;
+ public static final int BINARY_LIMIT = 72;
/**
* Enumerated property Bidi_Class.
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 a4ab1d9..2d95fab 100644
--- a/android_icu4j/src/main/java/android/icu/lang/UScript.java
+++ b/android_icu4j/src/main/java/android/icu/lang/UScript.java
@@ -864,6 +864,17 @@
/***/
public static final int YEZIDI = 192; /* Yezi */
+ /***/
+ public static final int CYPRO_MINOAN = 193; /* Cpmn */
+ /***/
+ public static final int OLD_UYGHUR = 194; /* Ougr */
+ /***/
+ public static final int TANGSA = 195; /* Tnsa */
+ /***/
+ public static final int TOTO = 196; /* Toto */
+ /***/
+ public static final int VITHKUQI = 197; /* Vith */
+
/**
* One more than the highest normal UScript code.
* The highest value is available via UCharacter.getIntPropertyMaxValue(UProperty.SCRIPT).
@@ -872,7 +883,7 @@
* @hide unsupported on Android
*/
@Deprecated
- public static final int CODE_LIMIT = 193;
+ public static final int CODE_LIMIT = 198;
private static int[] getCodesFromLocale(ULocale locale) {
// Multi-script languages, equivalent to the LocaleScript data
@@ -1338,6 +1349,11 @@
0x1190C | EXCLUSION, // Diak
0x18C65 | EXCLUSION | LB_LETTERS, // Kits
0x10E88 | EXCLUSION | RTL, // Yezi
+ 0x12FE5 | EXCLUSION, // Cpmn
+ 0x10F7C | EXCLUSION | RTL, // Ougr
+ 0x16ABC | EXCLUSION, // Tnsa
+ 0x1E290 | EXCLUSION, // Toto
+ 0x10582 | EXCLUSION | CASED, // Vith
// End copy-paste from parsescriptmetadata.py
};
diff --git a/android_icu4j/src/main/java/android/icu/lang/UScriptRun.java b/android_icu4j/src/main/java/android/icu/lang/UScriptRun.java
index 1c89c5c..abd529c 100644
--- a/android_icu4j/src/main/java/android/icu/lang/UScriptRun.java
+++ b/android_icu4j/src/main/java/android/icu/lang/UScriptRun.java
@@ -19,7 +19,7 @@
* the same script, as defined in the <code>UScript</code> class.
* It implements a simple iterator over an array of characters.
* The iterator will assign <code>COMMON</code> and <code>INHERITED</code>
- * characters to the same script as the preceeding characters. If the
+ * characters to the same script as the preceding characters. If the
* COMMON and INHERITED characters are first, they will be assigned to
* the same script as the following characters.
*
@@ -88,7 +88,7 @@
/**
* Construct a <code>UScriptRun</code> object which iterates over a subrange
- * of the characetrs in the given string.
+ * of the characters in the given string.
*
* @param text the string of characters over which to iterate.
* @param start the index of the first character over which to iterate
@@ -105,7 +105,7 @@
/**
* Construct a <code>UScriptRun</code> object which iterates over the given
- * characetrs.
+ * characters.
*
* @param chars the array of characters over which to iterate.
*
@@ -120,7 +120,7 @@
/**
* Construct a <code>UScriptRun</code> object which iterates over a subrange
- * of the given characetrs.
+ * of the given characters.
*
* @param chars the array of characters over which to iterate.
* @param start the index of the first character over which to iterate
diff --git a/android_icu4j/src/main/java/android/icu/math/BigDecimal.java b/android_icu4j/src/main/java/android/icu/math/BigDecimal.java
index 2eaf4a8..9909e66 100644
--- a/android_icu4j/src/main/java/android/icu/math/BigDecimal.java
+++ b/android_icu4j/src/main/java/android/icu/math/BigDecimal.java
@@ -91,7 +91,7 @@
/* constructor, no blanks in string constructor, add */
/* offset and length version of char[] constructor; */
/* add valueOf(double); drop booleanValue, charValue; */
-/* add ...Exact versions of remaining convertors */
+/* add ...Exact versions of remaining converters */
/* 1999.03.13 add toBigIntegerExact */
/* 1999.03.13 1.00 release to IBM Centre for Java Technology */
/* 1999.05.27 1.01 correct 0-0.2 bug under scaled arithmetic */
diff --git a/android_icu4j/src/main/java/android/icu/number/CompactNotation.java b/android_icu4j/src/main/java/android/icu/number/CompactNotation.java
index 0faca2b..03c92fd 100644
--- a/android_icu4j/src/main/java/android/icu/number/CompactNotation.java
+++ b/android_icu4j/src/main/java/android/icu/number/CompactNotation.java
@@ -8,7 +8,6 @@
import java.util.Map;
import java.util.Set;
-import android.icu.impl.StandardPlural;
import android.icu.impl.number.CompactData;
import android.icu.impl.number.CompactData.CompactType;
import android.icu.impl.number.DecimalFormatProperties;
@@ -138,8 +137,7 @@
magnitude -= multiplier;
}
- StandardPlural plural = quantity.getStandardPlural(rules);
- String patternString = data.getPattern(magnitude, plural);
+ String patternString = data.getPattern(magnitude, rules, quantity);
if (patternString == null) {
// Use the default (non-compact) modifier.
// No need to take any action.
diff --git a/android_icu4j/src/main/java/android/icu/number/CurrencyPrecision.java b/android_icu4j/src/main/java/android/icu/number/CurrencyPrecision.java
index 4564171..db6b570 100644
--- a/android_icu4j/src/main/java/android/icu/number/CurrencyPrecision.java
+++ b/android_icu4j/src/main/java/android/icu/number/CurrencyPrecision.java
@@ -39,7 +39,9 @@
*/
public Precision withCurrency(Currency currency) {
if (currency != null) {
- return constructFromCurrency(this, currency);
+ Precision retval = constructFromCurrency(this, currency);
+ retval.trailingZeroDisplay = trailingZeroDisplay;
+ return retval;
} else {
throw new IllegalArgumentException("Currency must not be null");
}
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 9537cc1..0f10b8a 100644
--- a/android_icu4j/src/main/java/android/icu/number/FormattedNumber.java
+++ b/android_icu4j/src/main/java/android/icu/number/FormattedNumber.java
@@ -28,10 +28,14 @@
final DecimalQuantity fq;
final MeasureUnit outputUnit;
- FormattedNumber(FormattedStringBuilder nsb, DecimalQuantity fq, MeasureUnit outputUnit) {
+ // Grammatical gender of the formatted result.
+ final String gender;
+
+ FormattedNumber(FormattedStringBuilder nsb, DecimalQuantity fq, MeasureUnit outputUnit, String gender) {
this.string = nsb;
this.fq = fq;
this.outputUnit = outputUnit;
+ this.gender = gender;
}
/**
@@ -111,13 +115,26 @@
* as "foot-and-inch" or "hour-and-minute-and-second".
*
* @return `MeasureUnit`.
- * @hide draft / provisional / internal are hidden on Android
*/
public MeasureUnit getOutputUnit() {
return this.outputUnit;
}
/**
+ * The gender of the formatted output.
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public String getGender() {
+ if (this.gender == null) {
+ return "";
+ }
+ return this.gender;
+ }
+
+ /**
* @deprecated This API is ICU internal only.
* @hide draft / provisional / internal are hidden on Android
*/
diff --git a/android_icu4j/src/main/java/android/icu/number/FormattedNumberRange.java b/android_icu4j/src/main/java/android/icu/number/FormattedNumberRange.java
index bae5b9b..06ed9d6 100644
--- a/android_icu4j/src/main/java/android/icu/number/FormattedNumberRange.java
+++ b/android_icu4j/src/main/java/android/icu/number/FormattedNumberRange.java
@@ -164,8 +164,7 @@
// FormattedStringBuilder and BigDecimal are mutable, so we can't call
// #equals() or #hashCode() on them directly.
FormattedNumberRange _other = (FormattedNumberRange) other;
- return Arrays.equals(string.toCharArray(), _other.string.toCharArray())
- && Arrays.equals(string.toFieldArray(), _other.string.toFieldArray())
+ return string.contentEquals(_other.string)
&& quantity1.toBigDecimal().equals(_other.quantity1.toBigDecimal())
&& quantity2.toBigDecimal().equals(_other.quantity2.toBigDecimal());
}
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 ff4c28c..ced2a35 100644
--- a/android_icu4j/src/main/java/android/icu/number/FractionPrecision.java
+++ b/android_icu4j/src/main/java/android/icu/number/FractionPrecision.java
@@ -20,6 +20,36 @@
}
/**
+ * Override maximum fraction digits with maximum significant digits depending on the magnitude
+ * of the number. See UNumberRoundingPriority.
+ *
+ * @param minSignificantDigits
+ * Pad trailing zeros to achieve this minimum number of significant digits.
+ * @param maxSignificantDigits
+ * Round the number to achieve this maximum number of significant digits.
+ * @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,
+ int maxSignificantDigits,
+ NumberFormatter.RoundingPriority priority) {
+ if (maxSignificantDigits >= 1 &&
+ maxSignificantDigits >= minSignificantDigits &&
+ maxSignificantDigits <= RoundingUtils.MAX_INT_FRAC_SIG) {
+ return constructFractionSignificant(
+ this, minSignificantDigits, maxSignificantDigits, priority);
+ } else {
+ throw new IllegalArgumentException("Significant digits must be between 1 and "
+ + RoundingUtils.MAX_INT_FRAC_SIG
+ + " (inclusive)");
+ }
+ }
+
+ /**
* Ensure that no less than this number of significant digits are retained when rounding according to
* fraction rules.
*
@@ -31,6 +61,9 @@
* This setting does not affect the number of trailing zeros. For example, 3.01 would print as "3",
* not "3.0".
*
+ * <p>
+ * This is equivalent to `withSignificantDigits(1, minSignificantDigits, RELAXED)`.
+ *
* @param minSignificantDigits
* The number of significant figures to guarantee.
* @return A Precision for chaining or passing to the NumberFormatter rounding() setter.
@@ -39,7 +72,8 @@
*/
public Precision withMinDigits(int minSignificantDigits) {
if (minSignificantDigits >= 1 && minSignificantDigits <= RoundingUtils.MAX_INT_FRAC_SIG) {
- return constructFractionSignificant(this, minSignificantDigits, -1);
+ return constructFractionSignificant(
+ this, 1, minSignificantDigits, NumberFormatter.RoundingPriority.RELAXED);
} else {
throw new IllegalArgumentException("Significant digits must be between 1 and "
+ RoundingUtils.MAX_INT_FRAC_SIG
@@ -59,6 +93,9 @@
* This setting does not affect the number of trailing zeros. For example, with fixed fraction of 2,
* 123.4 would become "120.00".
*
+ * <p>
+ * This is equivalent to `withSignificantDigits(1, maxSignificantDigits, STRICT)`.
+ *
* @param maxSignificantDigits
* Round the number to no more than this number of significant figures.
* @return A Precision for chaining or passing to the NumberFormatter rounding() setter.
@@ -67,7 +104,8 @@
*/
public Precision withMaxDigits(int maxSignificantDigits) {
if (maxSignificantDigits >= 1 && maxSignificantDigits <= RoundingUtils.MAX_INT_FRAC_SIG) {
- return constructFractionSignificant(this, -1, maxSignificantDigits);
+ return constructFractionSignificant(
+ this, 1, maxSignificantDigits, NumberFormatter.RoundingPriority.STRICT);
} 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/LocalizedNumberFormatter.java b/android_icu4j/src/main/java/android/icu/number/LocalizedNumberFormatter.java
index c290a27..48a4e44 100644
--- a/android_icu4j/src/main/java/android/icu/number/LocalizedNumberFormatter.java
+++ b/android_icu4j/src/main/java/android/icu/number/LocalizedNumberFormatter.java
@@ -98,7 +98,7 @@
MeasureUnit unit = input.getUnit();
FormattedStringBuilder string = new FormattedStringBuilder();
MicroProps micros = formatImpl(fq, unit, string);
- return new FormattedNumber(string, fq, micros.outputUnit);
+ return new FormattedNumber(string, fq, micros.outputUnit, micros.gender);
}
/**
@@ -122,7 +122,7 @@
private FormattedNumber format(DecimalQuantity fq) {
FormattedStringBuilder string = new FormattedStringBuilder();
MicroProps micros = formatImpl(fq, string);
- return new FormattedNumber(string, fq, micros.outputUnit);
+ return new FormattedNumber(string, fq, micros.outputUnit, micros.gender);
}
/**
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 92f2027..101b06e 100644
--- a/android_icu4j/src/main/java/android/icu/number/NumberFormatter.java
+++ b/android_icu4j/src/main/java/android/icu/number/NumberFormatter.java
@@ -10,8 +10,9 @@
import android.icu.util.ULocale;
/**
- * The main entrypoint to the localized number formatting library introduced in ICU 60. Basic usage
- * examples:
+ * All-in-one formatter for localized numbers, currencies, and units.
+ *
+ * For a full list of options, see {@link NumberFormatterSettings}.
*
* <pre>
* // Most basic usage:
@@ -68,6 +69,67 @@
private static final UnlocalizedNumberFormatter BASE = new UnlocalizedNumberFormatter();
/**
+ * An enum declaring how to resolve conflicts between maximum fraction digits and maximum
+ * significant digits.
+ *
+ * <p>There are two modes, RELAXED and STRICT:
+ *
+ * <ul>
+ * <li> RELAXED: Relax one of the two constraints (fraction digits or significant digits) in order
+ * to round the number to a higher level of precision.
+ * <li> STRICT: Enforce both constraints, resulting in the number being rounded to a lower
+ * level of precision.
+ * </ul>
+ *
+ * <p>The default settings for compact notation rounding are Max-Fraction = 0 (round to the nearest
+ * integer), Max-Significant = 2 (round to 2 significant digits), and priority RELAXED (choose
+ * the constraint that results in more digits being displayed).
+ *
+ * <p>Conflicting *minimum* fraction and significant digits are always resolved in the direction that
+ * results in more trailing zeros.
+ *
+ * <p>Example 1: Consider the number 3.141, with various different settings:
+ *
+ * <ul>
+ * <li> Max-Fraction = 1: "3.1"
+ * <li> Max-Significant = 3: "3.14"
+ * </ul>
+ *
+ * <p>The rounding priority determines how to resolve the conflict when both Max-Fraction and
+ * Max-Significant are set. With RELAXED, the less-strict setting (the one that causes more digits
+ * to be displayed) will be used; Max-Significant wins. With STRICT, the more-strict setting (the
+ * one that causes fewer digits to be displayed) will be used; Max-Fraction wins.
+ *
+ * <p>Example 2: Consider the number 8317, with various different settings:
+ *
+ * <ul>
+ * <li> Max-Fraction = 1: "8317"
+ * <li> Max-Significant = 3: "8320"
+ * </ul>
+ *
+ * <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,
+ }
+
+ /**
* An enum declaring how to render units, including currencies. Example outputs when formatting 123
* USD and 123 meters in <em>en-CA</em>:
*
@@ -147,7 +209,6 @@
* Behavior of this option with non-currency units is not defined at this time.
*
* @see NumberFormatter
- * @hide draft / provisional / internal are hidden on Android
*/
FORMAL,
@@ -159,7 +220,6 @@
* Behavior of this option with non-currency units is not defined at this time.
*
* @see NumberFormatter
- * @hide draft / provisional / internal are hidden on Android
*/
VARIANT,
@@ -287,6 +347,9 @@
* Show the minus sign on negative numbers, and do not show the sign on positive numbers. This is
* the default behavior.
*
+ * If using this option, a sign will be displayed on negative zero, including negative numbers
+ * that round to zero. To hide the sign on negative zero, use the NEGATIVE option.
+ *
* @see NumberFormatter
*/
AUTO,
@@ -349,6 +412,20 @@
* @see NumberFormatter
*/
ACCOUNTING_EXCEPT_ZERO,
+
+ /**
+ * 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,
}
/**
@@ -380,6 +457,33 @@
}
/**
+ * An enum declaring how to render trailing zeros.
+ *
+ * <ul>
+ * <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,
+ }
+
+ /**
* Use a default threshold of 3. This means that the third time .format() is called, the data
* structures get built using the "safe" code path. The first two calls to .format() will trigger the
* unsafe code path.
@@ -430,6 +534,9 @@
* Call this method at the beginning of a NumberFormatter fluent chain to create an instance based
* on a given number skeleton string.
*
+ * For more information on number skeleton strings, see:
+ * https://unicode-org.github.io/icu/userguide/format_parse/numbers/skeletons.html
+ *
* @param skeleton
* The skeleton string off of which to base this NumberFormatter.
* @return An {@link UnlocalizedNumberFormatter}, to be used for chaining.
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 566f90a..698d42a 100644
--- a/android_icu4j/src/main/java/android/icu/number/NumberFormatterImpl.java
+++ b/android_icu4j/src/main/java/android/icu/number/NumberFormatterImpl.java
@@ -6,6 +6,7 @@
import android.icu.impl.FormattedStringBuilder;
import android.icu.impl.IllegalIcuArgumentException;
import android.icu.impl.StandardPlural;
+import android.icu.impl.number.AffixPatternProvider;
import android.icu.impl.number.CompactData.CompactType;
import android.icu.impl.number.ConstantAffixModifier;
import android.icu.impl.number.DecimalQuantity;
@@ -37,6 +38,7 @@
import android.icu.util.Currency;
import android.icu.util.MeasureUnit;
+
/**
* This is the "brain" of the number formatting pipeline. It ties all the pieces together, taking in a
* MacroProps and a DecimalQuantity and outputting a properly formatted number string.
@@ -62,10 +64,10 @@
MacroProps macros,
DecimalQuantity inValue,
FormattedStringBuilder outString) {
- MicroProps micros = preProcessUnsafe(macros, inValue);
- int length = writeNumber(micros, inValue, outString, 0);
- writeAffixes(micros, outString, 0, length);
- return micros;
+ MicroProps result = preProcessUnsafe(macros, inValue);
+ int length = writeNumber(result, inValue, outString, 0);
+ writeAffixes(result, outString, 0, length);
+ return result;
}
/**
@@ -93,10 +95,10 @@
* Evaluates the "safe" MicroPropsGenerator created by "fromMacros".
*/
public MicroProps format(DecimalQuantity inValue, FormattedStringBuilder outString) {
- MicroProps micros = preProcess(inValue);
- int length = writeNumber(micros, inValue, outString, 0);
- writeAffixes(micros, outString, 0, length);
- return micros;
+ MicroProps result = preProcess(inValue);
+ int length = writeNumber(result, inValue, outString, 0);
+ writeAffixes(result, outString, 0, length);
+ return result;
}
/**
@@ -193,7 +195,8 @@
boolean isCompactNotation = (macros.notation instanceof CompactNotation);
boolean isAccounting = macros.sign == SignDisplay.ACCOUNTING
|| macros.sign == SignDisplay.ACCOUNTING_ALWAYS
- || macros.sign == SignDisplay.ACCOUNTING_EXCEPT_ZERO;
+ || macros.sign == SignDisplay.ACCOUNTING_EXCEPT_ZERO
+ || macros.sign == SignDisplay.ACCOUNTING_NEGATIVE;
Currency currency = isCurrency ? (Currency) macros.unit : DEFAULT_CURRENCY;
UnitWidth unitWidth = UnitWidth.SHORT;
if (macros.unitWidth != null) {
@@ -226,6 +229,9 @@
}
micros.nsName = ns.getName();
+ // Default gender: none.
+ micros.gender = "";
+
// Resolve the symbols. Do this here because currency may need to customize them.
if (macros.symbols instanceof DecimalFormatSymbols) {
micros.symbols = (DecimalFormatSymbols) macros.symbols;
@@ -275,9 +281,7 @@
}
chain = usagePrefsHandler = new UsagePrefsHandler(macros.loc, macros.unit, macros.usage, chain);
} else if (isMixedUnit) {
- // TODO(icu-units#97): The input unit should be the largest unit, not the first unit, in the identifier.
- MeasureUnit inputUnit = macros.unit.splitToSingleUnits().get(0);
- chain = new UnitConversionHandler(inputUnit, macros.unit, chain);
+ chain = new UnitConversionHandler(macros.unit, chain);
}
// Multiplier
@@ -359,8 +363,13 @@
// Middle modifier (patterns, positive/negative, currency symbols, percent)
// The default middle modifier is weak (thus the false argument).
MutablePatternModifier patternMod = new MutablePatternModifier(false);
- patternMod.setPatternInfo((macros.affixProvider != null) ? macros.affixProvider : patternInfo, null);
- patternMod.setPatternAttributes(micros.sign, isPermille);
+ AffixPatternProvider affixProvider =
+ (macros.affixProvider != null)
+ ? macros.affixProvider
+ : patternInfo;
+ patternMod.setPatternInfo(affixProvider, null);
+ boolean approximately = (macros.approximately != null) ? macros.approximately : false;
+ patternMod.setPatternAttributes(micros.sign, isPermille, approximately);
if (patternMod.needsPlurals()) {
if (rules == null) {
// Lazily create PluralRules
@@ -375,8 +384,17 @@
immPatternMod = patternMod.createImmutable();
}
+ // currencyAsDecimal
+ if (affixProvider.currencyAsDecimal()) {
+ micros.currencyAsDecimal = patternMod.getCurrencySymbolForUnitWidth();
+ }
+
// Outer modifier (CLDR units and currency long names)
if (isCldrUnit) {
+ String unitDisplayCase = null;
+ if (macros.unitDisplayCase != null) {
+ unitDisplayCase = macros.unitDisplayCase;
+ }
if (rules == null) {
// Lazily create PluralRules
rules = PluralRules.forLocale(macros.loc);
@@ -391,6 +409,7 @@
macros.loc,
usagePrefsHandler.getOutputUnits(),
unitWidth,
+ unitDisplayCase,
pluralRules,
chain);
} else if (isMixedUnit) {
@@ -398,13 +417,27 @@
macros.loc,
macros.unit,
unitWidth,
+ unitDisplayCase,
pluralRules,
chain);
} else {
- chain = LongNameHandler.forMeasureUnit(macros.loc,
- macros.unit,
- macros.perUnit,
+ MeasureUnit unit = macros.unit;
+ if (macros.perUnit != null) {
+ unit = unit.product(macros.perUnit.reciprocal());
+ // This isn't strictly necessary, but was what we specced
+ // out when perUnit became a backward-compatibility thing:
+ // unit/perUnit use case is only valid if both units are
+ // built-ins, or the product is a built-in.
+ if (unit.getType() == null && (macros.unit.getType() == null || macros.perUnit.getType() == null)) {
+ throw new UnsupportedOperationException(
+ "perUnit() can only be used if unit and perUnit are both built-ins, or the combination is a built-in");
+ }
+ }
+ chain = LongNameHandler.forMeasureUnit(
+ macros.loc,
+ unit,
unitWidth,
+ unitDisplayCase,
pluralRules,
chain);
}
@@ -491,10 +524,24 @@
// Add the decimal point
if (quantity.getLowerDisplayMagnitude() < 0
|| micros.decimal == DecimalSeparatorDisplay.ALWAYS) {
- length += string.insert(length + index,
- micros.useCurrency ? micros.symbols.getMonetaryDecimalSeparatorString()
- : micros.symbols.getDecimalSeparatorString(),
+ if (micros.currencyAsDecimal != null) {
+ // Note: This unconditionally substitutes the standard short symbol.
+ // TODO: Should we support narrow or other variants?
+ length += string.insert(
+ length + index,
+ micros.currencyAsDecimal,
+ NumberFormat.Field.CURRENCY);
+ } else if (micros.useCurrency) {
+ length += string.insert(
+ length + index,
+ micros.symbols.getMonetaryDecimalSeparatorString(),
NumberFormat.Field.DECIMAL_SEPARATOR);
+ } else {
+ length += string.insert(
+ length + index,
+ micros.symbols.getDecimalSeparatorString(),
+ NumberFormat.Field.DECIMAL_SEPARATOR);
+ }
}
// Add the fraction digits
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 24d3af8..2efa445 100644
--- a/android_icu4j/src/main/java/android/icu/number/NumberFormatterSettings.java
+++ b/android_icu4j/src/main/java/android/icu/number/NumberFormatterSettings.java
@@ -45,7 +45,8 @@
static final int KEY_THRESHOLD = 14;
static final int KEY_PER_UNIT = 15;
static final int KEY_USAGE = 16;
- static final int KEY_MAX = 17;
+ static final int KEY_UNIT_DISPLAY_CASE = 17;
+ static final int KEY_MAX = 18;
private final NumberFormatterSettings<?> parent;
private final int key;
@@ -518,13 +519,29 @@
* @param usage A usage parameter from the units resource.
* @return The fluent chain
* @throws IllegalArgumentException in case of Setting a usage string but not a correct input unit.
- * @hide draft / provisional / internal are hidden on Android
*/
public T usage(String usage) {
+ if (usage != null && usage.isEmpty()) {
+ return create(KEY_USAGE, null);
+ }
+
return create(KEY_USAGE, usage);
}
/**
+ * Specifies the desired case for a unit formatter's output (e.g.
+ * accusative, dative, genitive).
+ *
+ * @return The fluent chain
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public T unitDisplayCase(String unitDisplayCase) {
+ return create(KEY_UNIT_DISPLAY_CASE, unitDisplayCase);
+ }
+
+ /**
* Internal method to set a starting macros.
*
* @deprecated ICU 60 This API is ICU internal only.
@@ -568,6 +585,9 @@
* <p>
* The returned skeleton is in normalized form, such that two number formatters with equivalent
* behavior should produce the same skeleton.
+ * <p>
+ * For more information on number skeleton strings, see:
+ * https://unicode-org.github.io/icu/userguide/format_parse/numbers/skeletons.html
*
* @return A number skeleton string with behavior corresponding to this number formatter.
* @throws UnsupportedOperationException
@@ -651,6 +671,9 @@
case KEY_USAGE:
macros.usage = (String) current.value;
break;
+ case KEY_UNIT_DISPLAY_CASE:
+ macros.unitDisplayCase = (String) current.value;
+ break;
default:
throw new AssertionError("Unknown key: " + current.key);
}
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 343ea32..8ba138b 100644
--- a/android_icu4j/src/main/java/android/icu/number/NumberPropertyMapper.java
+++ b/android_icu4j/src/main/java/android/icu/number/NumberPropertyMapper.java
@@ -176,6 +176,9 @@
if (PatternStringUtils.ignoreRoundingIncrement(roundingIncrement, maxFrac)) {
rounding = Precision.constructFraction(minFrac, maxFrac);
} else {
+ if (minFrac > roundingIncrement.scale()) {
+ roundingIncrement = roundingIncrement.setScale(minFrac);
+ }
rounding = Precision.constructIncrement(roundingIncrement);
}
} else if (explicitMinMaxSig) {
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 3f690a9..9618b5a 100644
--- a/android_icu4j/src/main/java/android/icu/number/NumberRangeFormatter.java
+++ b/android_icu4j/src/main/java/android/icu/number/NumberRangeFormatter.java
@@ -3,8 +3,10 @@
// License & terms of use: http://www.unicode.org/copyright.html
package android.icu.number;
+import java.io.InvalidObjectException;
import java.util.Locale;
+import android.icu.text.UFormat;
import android.icu.util.ULocale;
/**
@@ -139,6 +141,45 @@
NOT_EQUAL
}
+ /**
+ * 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;
+
+ /**
+ * The concrete field used for spans in FormattedNumberRange.
+ *
+ * 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");
+
+ private SpanField(String name) {
+ super(name);
+ }
+
+ /**
+ * Serialization method resolve instances to the constant
+ * NumberRangeFormatter.SpanField values
+ * @deprecated This API is ICU internal only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ @Override
+ protected Object readResolve() throws InvalidObjectException {
+ if (this.getName().equals(NUMBER_RANGE_SPAN.getName()))
+ return NUMBER_RANGE_SPAN;
+
+ throw new InvalidObjectException("An invalid object.");
+ }
+ }
+
private static final UnlocalizedNumberRangeFormatter BASE = new UnlocalizedNumberRangeFormatter();
/**
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 1cb5ddd..bff637e 100644
--- a/android_icu4j/src/main/java/android/icu/number/NumberRangeFormatterImpl.java
+++ b/android_icu4j/src/main/java/android/icu/number/NumberRangeFormatterImpl.java
@@ -6,6 +6,7 @@
import java.util.MissingResourceException;
import android.icu.impl.FormattedStringBuilder;
+import android.icu.impl.FormattedValueStringBuilderImpl;
import android.icu.impl.ICUData;
import android.icu.impl.ICUResourceBundle;
import android.icu.impl.PatternProps;
@@ -13,6 +14,7 @@
import android.icu.impl.StandardPlural;
import android.icu.impl.UResource;
import android.icu.impl.number.DecimalQuantity;
+import android.icu.impl.number.MacroProps;
import android.icu.impl.number.MicroProps;
import android.icu.impl.number.Modifier;
import android.icu.impl.number.SimpleModifier;
@@ -38,10 +40,10 @@
final NumberRangeFormatter.RangeCollapse fCollapse;
final NumberRangeFormatter.RangeIdentityFallback fIdentityFallback;
- // Should be final, but they are set in a helper function, not the constructor proper.
- // TODO: Clean up to make these fields actually final.
+ // Should be final, but it is set in a helper function, not the constructor proper.
+ // TODO: Clean up to make this field actually final.
/* final */ String fRangePattern;
- /* final */ SimpleModifier fApproximatelyModifier;
+ final NumberFormatterImpl fApproximatelyFormatter;
final StandardPluralRanges fPluralRanges;
@@ -55,7 +57,8 @@
private static final class NumberRangeDataSink extends UResource.Sink {
String rangePattern;
- String approximatelyPattern;
+ // Note: approximatelyPattern is unused since ICU 69.
+ // String approximatelyPattern;
// For use with SimpleFormatterImpl
StringBuilder sb;
@@ -72,10 +75,13 @@
String pattern = value.getString();
rangePattern = SimpleFormatterImpl.compileToStringMinMaxArguments(pattern, sb, 2, 2);
}
+ /*
+ // Note: approximatelyPattern is unused since ICU 69.
if (key.contentEquals("approximately") && !hasApproxData()) {
String pattern = value.getString();
approximatelyPattern = SimpleFormatterImpl.compileToStringMinMaxArguments(pattern, sb, 1, 1); // 1 arg, as in "~{0}"
}
+ */
}
}
@@ -83,21 +89,26 @@
return rangePattern != null;
}
+ /*
+ // Note: approximatelyPattern is unused since ICU 69.
private boolean hasApproxData() {
return approximatelyPattern != null;
}
+ */
public boolean isComplete() {
- return hasRangeData() && hasApproxData();
+ return hasRangeData() /* && hasApproxData() */;
}
public void fillInDefaults() {
if (!hasRangeData()) {
rangePattern = SimpleFormatterImpl.compileToStringMinMaxArguments("{0}–{1}", sb, 2, 2);
}
+ /*
if (!hasApproxData()) {
approximatelyPattern = SimpleFormatterImpl.compileToStringMinMaxArguments("~{0}", sb, 1, 1);
}
+ */
}
}
@@ -127,16 +138,20 @@
sink.fillInDefaults();
out.fRangePattern = sink.rangePattern;
- out.fApproximatelyModifier = new SimpleModifier(sink.approximatelyPattern, null, false);
+ // out.fApproximatelyModifier = new SimpleModifier(sink.approximatelyPattern, null, false);
}
////////////////////
public NumberRangeFormatterImpl(RangeMacroProps macros) {
- formatterImpl1 = new NumberFormatterImpl(macros.formatter1 != null ? macros.formatter1.resolve()
- : NumberFormatter.withLocale(macros.loc).resolve());
- formatterImpl2 = new NumberFormatterImpl(macros.formatter2 != null ? macros.formatter2.resolve()
- : NumberFormatter.withLocale(macros.loc).resolve());
+ LocalizedNumberFormatter formatter1 = macros.formatter1 != null
+ ? macros.formatter1.locale(macros.loc)
+ : NumberFormatter.withLocale(macros.loc);
+ LocalizedNumberFormatter formatter2 = macros.formatter2 != null
+ ? macros.formatter2.locale(macros.loc)
+ : NumberFormatter.withLocale(macros.loc);
+ formatterImpl1 = new NumberFormatterImpl(formatter1.resolve());
+ formatterImpl2 = new NumberFormatterImpl(formatter2.resolve());
fSameFormatters = macros.sameFormatters != 0;
fCollapse = macros.collapse != null ? macros.collapse : NumberRangeFormatter.RangeCollapse.AUTO;
fIdentityFallback = macros.identityFallback != null ? macros.identityFallback
@@ -148,6 +163,17 @@
}
getNumberRangeData(macros.loc, nsName, this);
+ if (fSameFormatters && (
+ fIdentityFallback == RangeIdentityFallback.APPROXIMATELY ||
+ fIdentityFallback == RangeIdentityFallback.APPROXIMATELY_OR_SINGLE_VALUE)) {
+ MacroProps approximatelyMacros = new MacroProps();
+ approximatelyMacros.approximately = true;
+ fApproximatelyFormatter = new NumberFormatterImpl(
+ formatter1.macros(approximatelyMacros).resolve());
+ } else {
+ fApproximatelyFormatter = null;
+ }
+
// TODO: Get locale from PluralRules instead?
fPluralRanges = StandardPluralRanges.forLocale(macros.loc);
}
@@ -230,12 +256,14 @@
private void formatApproximately(DecimalQuantity quantity1, DecimalQuantity quantity2, FormattedStringBuilder string,
MicroProps micros1, MicroProps micros2) {
if (fSameFormatters) {
- int length = NumberFormatterImpl.writeNumber(micros1, quantity1, string, 0);
+ // Re-format using the approximately formatter:
+ quantity1.resetExponent();
+ MicroProps microsAppx = fApproximatelyFormatter.preProcess(quantity1);
+ int length = NumberFormatterImpl.writeNumber(microsAppx, quantity1, string, 0);
// HEURISTIC: Desired modifier order: inner, middle, approximately, outer.
- length += micros1.modInner.apply(string, 0, length);
- length += micros1.modMiddle.apply(string, 0, length);
- length += fApproximatelyModifier.apply(string, 0, length);
- micros1.modOuter.apply(string, 0, length);
+ length += microsAppx.modInner.apply(string, 0, length);
+ length += microsAppx.modMiddle.apply(string, 0, length);
+ microsAppx.modOuter.apply(string, 0, length);
} else {
formatRange(quantity1, quantity2, string, micros1, micros2);
}
@@ -298,7 +326,7 @@
// INNER MODIFIER
collapseInner = micros1.modInner.semanticallyEquivalent(micros2.modInner);
- // All done checking for collapsability.
+ // All done checking for collapsibility.
break;
}
@@ -337,36 +365,56 @@
}
h.length1 += NumberFormatterImpl.writeNumber(micros1, quantity1, string, h.index0());
- h.length2 += NumberFormatterImpl.writeNumber(micros2, quantity2, string, h.index2());
+ // ICU-21684: Write the second number to a temp string to avoid repeated insert operations
+ FormattedStringBuilder tempString = new FormattedStringBuilder();
+ NumberFormatterImpl.writeNumber(micros2, quantity2, tempString, 0);
+ h.length2 += string.insert(h.index2(), tempString);
// TODO: Support padding?
if (collapseInner) {
- // Note: this is actually a mix of prefix and suffix, but adding to infix length works
Modifier mod = resolveModifierPlurals(micros1.modInner, micros2.modInner);
- h.lengthInfix += mod.apply(string, h.index0(), h.index3());
+ h.lengthSuffix += mod.apply(string, h.index0(), h.index4());
+ h.lengthPrefix += mod.getPrefixLength();
+ h.lengthSuffix -= mod.getPrefixLength();
} else {
h.length1 += micros1.modInner.apply(string, h.index0(), h.index1());
- h.length2 += micros2.modInner.apply(string, h.index2(), h.index3());
+ h.length2 += micros2.modInner.apply(string, h.index2(), h.index4());
}
if (collapseMiddle) {
- // Note: this is actually a mix of prefix and suffix, but adding to infix length works
Modifier mod = resolveModifierPlurals(micros1.modMiddle, micros2.modMiddle);
- h.lengthInfix += mod.apply(string, h.index0(), h.index3());
+ h.lengthSuffix += mod.apply(string, h.index0(), h.index4());
+ h.lengthPrefix += mod.getPrefixLength();
+ h.lengthSuffix -= mod.getPrefixLength();
} else {
h.length1 += micros1.modMiddle.apply(string, h.index0(), h.index1());
- h.length2 += micros2.modMiddle.apply(string, h.index2(), h.index3());
+ h.length2 += micros2.modMiddle.apply(string, h.index2(), h.index4());
}
if (collapseOuter) {
- // Note: this is actually a mix of prefix and suffix, but adding to infix length works
Modifier mod = resolveModifierPlurals(micros1.modOuter, micros2.modOuter);
- h.lengthInfix += mod.apply(string, h.index0(), h.index3());
+ h.lengthSuffix += mod.apply(string, h.index0(), h.index4());
+ h.lengthPrefix += mod.getPrefixLength();
+ h.lengthSuffix -= mod.getPrefixLength();
} else {
h.length1 += micros1.modOuter.apply(string, h.index0(), h.index1());
- h.length2 += micros2.modOuter.apply(string, h.index2(), h.index3());
+ h.length2 += micros2.modOuter.apply(string, h.index2(), h.index4());
}
+
+ // Now that all pieces are added, save the span info.
+ FormattedValueStringBuilderImpl.applySpanRange(
+ string,
+ NumberRangeFormatter.SpanField.NUMBER_RANGE_SPAN,
+ 0,
+ h.index0(),
+ h.index1());
+ FormattedValueStringBuilderImpl.applySpanRange(
+ string,
+ NumberRangeFormatter.SpanField.NUMBER_RANGE_SPAN,
+ 1,
+ h.index2(),
+ h.index3());
}
Modifier resolveModifierPlurals(Modifier first, Modifier second) {
diff --git a/android_icu4j/src/main/java/android/icu/number/NumberRangeFormatterSettings.java b/android_icu4j/src/main/java/android/icu/number/NumberRangeFormatterSettings.java
index 226e75f..d545e8b 100644
--- a/android_icu4j/src/main/java/android/icu/number/NumberRangeFormatterSettings.java
+++ b/android_icu4j/src/main/java/android/icu/number/NumberRangeFormatterSettings.java
@@ -144,41 +144,37 @@
// of a MacroProps object at each step.
// TODO: Remove the reference to the parent after the macros are resolved?
RangeMacroProps macros = new RangeMacroProps();
+ // Bitmap: 1 if seen; 0 if unseen
+ long seen = 0;
NumberRangeFormatterSettings<?> current = this;
while (current != null) {
+ long keyBitmask = (1L << current.key);
+ if (0 != (seen & keyBitmask)) {
+ current = current.parent;
+ continue;
+ }
+ seen |= keyBitmask;
switch (current.key) {
case KEY_MACROS:
// ignored for now
break;
case KEY_LOCALE:
- if (macros.loc == null) {
- macros.loc = (ULocale) current.value;
- }
+ macros.loc = (ULocale) current.value;
break;
case KEY_FORMATTER_1:
- if (macros.formatter1 == null) {
- macros.formatter1 = (UnlocalizedNumberFormatter) current.value;
- }
+ macros.formatter1 = (UnlocalizedNumberFormatter) current.value;
break;
case KEY_FORMATTER_2:
- if (macros.formatter2 == null) {
- macros.formatter2 = (UnlocalizedNumberFormatter) current.value;
- }
+ macros.formatter2 = (UnlocalizedNumberFormatter) current.value;
break;
case KEY_SAME_FORMATTERS:
- if (macros.sameFormatters == -1) {
- macros.sameFormatters = (boolean) current.value ? 1 : 0;
- }
+ macros.sameFormatters = (boolean) current.value ? 1 : 0;
break;
case KEY_COLLAPSE:
- if (macros.collapse == null) {
- macros.collapse = (RangeCollapse) current.value;
- }
+ macros.collapse = (RangeCollapse) current.value;
break;
case KEY_IDENTITY_FALLBACK:
- if (macros.identityFallback == null) {
- macros.identityFallback = (RangeIdentityFallback) current.value;
- }
+ macros.identityFallback = (RangeIdentityFallback) current.value;
break;
default:
throw new AssertionError("Unknown key: " + current.key);
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 2d7640d..6356939 100644
--- a/android_icu4j/src/main/java/android/icu/number/NumberSkeletonImpl.java
+++ b/android_icu4j/src/main/java/android/icu/number/NumberSkeletonImpl.java
@@ -13,11 +13,11 @@
import android.icu.impl.StringSegment;
import android.icu.impl.number.MacroProps;
import android.icu.impl.number.RoundingUtils;
-import android.icu.impl.units.MeasureUnitImpl;
-import android.icu.impl.units.SingleUnitImpl;
import android.icu.number.NumberFormatter.DecimalSeparatorDisplay;
import android.icu.number.NumberFormatter.GroupingStrategy;
+import android.icu.number.NumberFormatter.RoundingPriority;
import android.icu.number.NumberFormatter.SignDisplay;
+import android.icu.number.NumberFormatter.TrailingZeroDisplay;
import android.icu.number.NumberFormatter.UnitWidth;
import android.icu.text.DecimalFormatSymbols;
import android.icu.text.NumberingSystem;
@@ -53,6 +53,7 @@
// Section 1: We might accept an option, but it is not required:
STATE_SCIENTIFIC,
STATE_FRACTION_PRECISION,
+ STATE_PRECISION,
// Section 2: An option is required:
STATE_INCREMENT_PRECISION,
@@ -96,6 +97,7 @@
STEM_ROUNDING_MODE_HALF_DOWN,
STEM_ROUNDING_MODE_HALF_UP,
STEM_ROUNDING_MODE_UNNECESSARY,
+ STEM_INTEGER_WIDTH_TRUNC,
STEM_GROUP_OFF,
STEM_GROUP_MIN2,
STEM_GROUP_AUTO,
@@ -116,6 +118,8 @@
STEM_SIGN_ACCOUNTING_ALWAYS,
STEM_SIGN_EXCEPT_ZERO,
STEM_SIGN_ACCOUNTING_EXCEPT_ZERO,
+ STEM_SIGN_NEGATIVE,
+ STEM_SIGN_ACCOUNTING_NEGATIVE,
STEM_DECIMAL_AUTO,
STEM_DECIMAL_ALWAYS,
@@ -172,6 +176,7 @@
b.add("rounding-mode-half-down", StemEnum.STEM_ROUNDING_MODE_HALF_DOWN.ordinal());
b.add("rounding-mode-half-up", StemEnum.STEM_ROUNDING_MODE_HALF_UP.ordinal());
b.add("rounding-mode-unnecessary", StemEnum.STEM_ROUNDING_MODE_UNNECESSARY.ordinal());
+ b.add("integer-width-trunc", StemEnum.STEM_INTEGER_WIDTH_TRUNC.ordinal());
b.add("group-off", StemEnum.STEM_GROUP_OFF.ordinal());
b.add("group-min2", StemEnum.STEM_GROUP_MIN2.ordinal());
b.add("group-auto", StemEnum.STEM_GROUP_AUTO.ordinal());
@@ -192,6 +197,8 @@
b.add("sign-accounting-always", StemEnum.STEM_SIGN_ACCOUNTING_ALWAYS.ordinal());
b.add("sign-except-zero", StemEnum.STEM_SIGN_EXCEPT_ZERO.ordinal());
b.add("sign-accounting-except-zero", StemEnum.STEM_SIGN_ACCOUNTING_EXCEPT_ZERO.ordinal());
+ b.add("sign-negative", StemEnum.STEM_SIGN_NEGATIVE.ordinal());
+ b.add("sign-accounting-negative", StemEnum.STEM_SIGN_ACCOUNTING_NEGATIVE.ordinal());
b.add("decimal-auto", StemEnum.STEM_DECIMAL_AUTO.ordinal());
b.add("decimal-always", StemEnum.STEM_DECIMAL_ALWAYS.ordinal());
@@ -220,6 +227,8 @@
b.add("()!", StemEnum.STEM_SIGN_ACCOUNTING_ALWAYS.ordinal());
b.add("+?", StemEnum.STEM_SIGN_EXCEPT_ZERO.ordinal());
b.add("()?", StemEnum.STEM_SIGN_ACCOUNTING_EXCEPT_ZERO.ordinal());
+ b.add("+-", StemEnum.STEM_SIGN_NEGATIVE.ordinal());
+ b.add("()-", StemEnum.STEM_SIGN_ACCOUNTING_NEGATIVE.ordinal());
// Build the CharsTrie
// TODO: Use SLOW or FAST here?
@@ -354,6 +363,10 @@
return SignDisplay.EXCEPT_ZERO;
case STEM_SIGN_ACCOUNTING_EXCEPT_ZERO:
return SignDisplay.ACCOUNTING_EXCEPT_ZERO;
+ case STEM_SIGN_NEGATIVE:
+ return SignDisplay.NEGATIVE;
+ case STEM_SIGN_ACCOUNTING_NEGATIVE:
+ return SignDisplay.ACCOUNTING_NEGATIVE;
default:
return null; // for objects, throw; for enums, return null
}
@@ -481,6 +494,12 @@
case ACCOUNTING_EXCEPT_ZERO:
sb.append("sign-accounting-except-zero");
break;
+ case NEGATIVE:
+ sb.append("sign-negative");
+ break;
+ case ACCOUNTING_NEGATIVE:
+ sb.append("sign-accounting-negative");
+ break;
default:
throw new AssertionError();
}
@@ -657,7 +676,7 @@
case '@':
checkNull(macros.precision, segment);
BlueprintHelpers.parseDigitsStem(segment, macros);
- return ParseState.STATE_NULL;
+ return ParseState.STATE_PRECISION;
case 'E':
checkNull(macros.notation, segment);
BlueprintHelpers.parseScientificStem(segment, macros);
@@ -720,7 +739,7 @@
case STEM_PRECISION_INTEGER:
return ParseState.STATE_FRACTION_PRECISION; // allows for "precision-integer/@##"
default:
- return ParseState.STATE_NULL;
+ return ParseState.STATE_PRECISION;
}
case STEM_ROUNDING_MODE_CEILING:
@@ -735,6 +754,11 @@
macros.roundingMode = StemToObject.roundingMode(stem);
return ParseState.STATE_NULL;
+ case STEM_INTEGER_WIDTH_TRUNC:
+ checkNull(macros.integerWidth, segment);
+ macros.integerWidth = IntegerWidth.zeroFillTo(0).truncateAt(0);
+ return ParseState.STATE_NULL;
+
case STEM_GROUP_OFF:
case STEM_GROUP_MIN2:
case STEM_GROUP_AUTO:
@@ -767,6 +791,8 @@
case STEM_SIGN_ACCOUNTING_ALWAYS:
case STEM_SIGN_EXCEPT_ZERO:
case STEM_SIGN_ACCOUNTING_EXCEPT_ZERO:
+ case STEM_SIGN_NEGATIVE:
+ case STEM_SIGN_ACCOUNTING_NEGATIVE:
checkNull(macros.sign, segment);
macros.sign = StemToObject.signDisplay(stem);
return ParseState.STATE_NULL;
@@ -855,7 +881,7 @@
return ParseState.STATE_NULL;
case STATE_INCREMENT_PRECISION:
BlueprintHelpers.parseIncrementOption(segment, macros);
- return ParseState.STATE_NULL;
+ return ParseState.STATE_PRECISION;
case STATE_INTEGER_WIDTH:
BlueprintHelpers.parseIntegerWidthOption(segment, macros);
return ParseState.STATE_NULL;
@@ -889,6 +915,19 @@
switch (stem) {
case STATE_FRACTION_PRECISION:
if (BlueprintHelpers.parseFracSigOption(segment, macros)) {
+ return ParseState.STATE_PRECISION;
+ }
+ // If the fracSig option was not found, try normal precision options.
+ stem = ParseState.STATE_PRECISION;
+ break;
+ default:
+ break;
+ }
+
+ // Trailing zeros option
+ switch (stem) {
+ case STATE_PRECISION:
+ if (BlueprintHelpers.parseTrailingZeroOption(segment, macros)) {
return ParseState.STATE_NULL;
}
break;
@@ -950,6 +989,10 @@
throw new UnsupportedOperationException(
"Cannot generate number skeleton with custom padder");
}
+ if (macros.unitDisplayCase != null && !macros.unitDisplayCase.isEmpty()) {
+ throw new UnsupportedOperationException(
+ "Cannot generate number skeleton with custom unit display case");
+ }
if (macros.affixProvider != null) {
throw new UnsupportedOperationException(
"Cannot generate number skeleton with custom affix provider");
@@ -1071,45 +1114,11 @@
* specified via a "unit/" concise skeleton.
*/
private static void parseIdentifierUnitOption(StringSegment segment, MacroProps macros) {
- MeasureUnitImpl fullUnit;
try {
- fullUnit = MeasureUnitImpl.forIdentifier(segment.asString());
+ macros.unit = MeasureUnit.forIdentifier(segment.asString());
} catch (IllegalArgumentException e) {
throw new SkeletonSyntaxException("Invalid unit stem", segment);
}
-
- // Mixed units can only be represented by full MeasureUnit instances, so we
- // don't split the denominator into macros.perUnit.
- if (fullUnit.getComplexity() == MeasureUnit.Complexity.MIXED) {
- macros.unit = fullUnit.build();
- return;
- }
-
- // When we have a built-in unit (e.g. meter-per-second), we don't split it up
- MeasureUnit testBuiltin = fullUnit.build();
- if (testBuiltin.getType() != null) {
- macros.unit = testBuiltin;
- return;
- }
-
- // TODO(ICU-20941): Clean this up.
- for (SingleUnitImpl subUnit : fullUnit.getSingleUnits()) {
- if (subUnit.getDimensionality() > 0) {
- if (macros.unit == null) {
- macros.unit = subUnit.build();
- } else {
- macros.unit = macros.unit.product(subUnit.build());
- }
- } else {
- // It's okay to mutate fullUnit, we're throwing it away after this:
- subUnit.setDimensionality(subUnit.getDimensionality() * -1);
- if (macros.perUnit == null) {
- macros.perUnit = subUnit.build();
- } else {
- macros.perUnit = macros.perUnit.product(subUnit.build());
- }
- }
- }
}
private static void parseUnitUsageOption(StringSegment segment, MacroProps macros) {
@@ -1252,6 +1261,7 @@
} else if (segment.charAt(offset) == '?') {
signDisplay = SignDisplay.EXCEPT_ZERO;
} else {
+ // NOTE: Other sign displays are not included because they aren't useful in this context
break block;
}
offset++;
@@ -1305,20 +1315,14 @@
break;
}
}
- // For the frac-sig option, there must be minSig or maxSig but not both.
- // Valid: @+, @@+, @@@+
- // Valid: @#, @##, @###
- // Invalid: @, @@, @@@
- // Invalid: @@#, @@##, @@@#
if (offset < segment.length()) {
if (isWildcardChar(segment.charAt(offset))) {
+ // @+, @@+, @@@+
maxSig = -1;
offset++;
- } else if (minSig > 1) {
- // @@#, @@##, @@@#
- throw new SkeletonSyntaxException("Invalid digits option for fraction rounder",
- segment);
} else {
+ // @#, @##, @###
+ // @@#, @@##, @@@#
maxSig = minSig;
for (; offset < segment.length(); offset++) {
if (segment.charAt(offset) == '#') {
@@ -1330,21 +1334,55 @@
}
} else {
// @, @@, @@@
- throw new SkeletonSyntaxException("Invalid digits option for fraction rounder", segment);
+ maxSig = minSig;
}
+ RoundingPriority priority;
if (offset < segment.length()) {
- throw new SkeletonSyntaxException("Invalid digits option for fraction rounder", segment);
+ if (maxSig == -1) {
+ throw new SkeletonSyntaxException(
+ "Invalid digits option: Wildcard character not allowed with the priority annotation", segment);
+ }
+ if (segment.codePointAt(offset) == 'r') {
+ priority = RoundingPriority.RELAXED;
+ offset++;
+ } else if (segment.codePointAt(offset) == 's') {
+ priority = RoundingPriority.STRICT;
+ offset++;
+ } else {
+ assert offset < segment.length();
+ priority = RoundingPriority.RELAXED; // make compiler happy (uninitialized variable)
+ }
+ if (offset < segment.length()) {
+ throw new SkeletonSyntaxException(
+ "Invalid digits option for fraction rounder", segment);
+ }
+ } else if (maxSig == -1) {
+ // withMinDigits
+ maxSig = minSig;
+ minSig = 1;
+ priority = RoundingPriority.RELAXED;
+ } else if (minSig == 1) {
+ // withMaxDigits
+ priority = RoundingPriority.STRICT;
+ } else {
+ throw new SkeletonSyntaxException(
+ "Invalid digits option: Priority annotation required", segment);
}
FractionPrecision oldRounder = (FractionPrecision) macros.precision;
- if (maxSig == -1) {
- macros.precision = oldRounder.withMinDigits(minSig);
- } else {
- macros.precision = oldRounder.withMaxDigits(maxSig);
- }
+ macros.precision = oldRounder.withSignificantDigits(minSig, maxSig, priority);
return true;
}
+ /** @return Whether we successfully found and parsed a trailing zero option. */
+ private static boolean parseTrailingZeroOption(StringSegment segment, MacroProps macros) {
+ if (segment.contentEquals("w")) {
+ macros.precision = macros.precision.trailingZeroDisplay(TrailingZeroDisplay.HIDE_IF_WHOLE);
+ return true;
+ }
+ return false;
+ }
+
private static void parseIncrementOption(StringSegment segment, MacroProps macros) {
// Call segment.subSequence() because segment.toString() doesn't create a clean string.
String str = segment.subSequence(0, segment.length()).toString();
@@ -1540,10 +1578,11 @@
Precision.FracSigRounderImpl impl = (Precision.FracSigRounderImpl) macros.precision;
BlueprintHelpers.generateFractionStem(impl.minFrac, impl.maxFrac, sb);
sb.append('/');
- if (impl.minSig == -1) {
- BlueprintHelpers.generateDigitsStem(1, impl.maxSig, sb);
+ BlueprintHelpers.generateDigitsStem(impl.minSig, impl.maxSig, sb);
+ if (impl.priority == RoundingPriority.RELAXED) {
+ sb.append('r');
} else {
- BlueprintHelpers.generateDigitsStem(impl.minSig, -1, sb);
+ sb.append('s');
}
} else if (macros.precision instanceof Precision.IncrementRounderImpl) {
Precision.IncrementRounderImpl impl = (Precision.IncrementRounderImpl) macros.precision;
@@ -1559,6 +1598,10 @@
}
}
+ if (macros.precision.trailingZeroDisplay == TrailingZeroDisplay.HIDE_IF_WHOLE) {
+ sb.append("/w");
+ }
+
// NOTE: Always return true for rounding because the default value depends on other options.
return true;
}
@@ -1588,6 +1631,10 @@
if (macros.integerWidth.equals(IntegerWidth.DEFAULT)) {
return false; // Default
}
+ if (macros.integerWidth.minInt == 0 && macros.integerWidth.maxInt == 0) {
+ sb.append("integer-width-trunc");
+ return true;
+ }
sb.append("integer-width/");
BlueprintHelpers.generateIntegerWidthOption(macros.integerWidth.minInt,
macros.integerWidth.maxInt,
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 3f4e906..c892ad8 100644
--- a/android_icu4j/src/main/java/android/icu/number/Precision.java
+++ b/android_icu4j/src/main/java/android/icu/number/Precision.java
@@ -10,6 +10,9 @@
import android.icu.impl.number.DecimalQuantity;
import android.icu.impl.number.MultiplierProducer;
import android.icu.impl.number.RoundingUtils;
+import android.icu.number.NumberFormatter.RoundingPriority;
+import android.icu.number.NumberFormatter.TrailingZeroDisplay;
+import android.icu.text.PluralRules.Operand;
import android.icu.util.Currency;
import android.icu.util.Currency.CurrencyUsage;
@@ -24,6 +27,7 @@
public abstract class Precision {
/* package-private final */ MathContext mathContext;
+ /* package-private final */ TrailingZeroDisplay trailingZeroDisplay;
/* package-private */ Precision() {
mathContext = RoundingUtils.DEFAULT_MATH_CONTEXT_UNLIMITED;
@@ -324,6 +328,19 @@
}
/**
+ * Configure how trailing zeros are displayed on numbers. For example, to hide trailing zeros
+ * 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();
+ result.trailingZeroDisplay = trailingZeroDisplay;
+ return result;
+ }
+
+ /**
* Sets a MathContext to use on this Precision.
*
* @deprecated This API is ICU internal only.
@@ -370,7 +387,7 @@
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, 2, -1);
+ static final FracSigRounderImpl COMPACT_STRATEGY = new FracSigRounderImpl(0, 0, 1, 2, RoundingPriority.RELAXED);
static final IncrementFiveRounderImpl NICKEL = new IncrementFiveRounderImpl(new BigDecimal("0.05"), 2, 2);
@@ -406,14 +423,16 @@
}
}
- static Precision constructFractionSignificant(FractionPrecision base_, int minSig, int maxSig) {
+ static Precision constructFractionSignificant(
+ FractionPrecision base_, int minSig, int maxSig, RoundingPriority priority) {
assert base_ instanceof FractionRounderImpl;
FractionRounderImpl base = (FractionRounderImpl) base_;
Precision returnValue;
- if (base.minFrac == 0 && base.maxFrac == 0 && minSig == 2 /* && maxSig == -1 */) {
+ if (base.minFrac == 0 && base.maxFrac == 0 && minSig == 1 && maxSig == 2 &&
+ priority == RoundingPriority.RELAXED) {
returnValue = COMPACT_STRATEGY;
} else {
- returnValue = new FracSigRounderImpl(base.minFrac, base.maxFrac, minSig, maxSig);
+ returnValue = new FracSigRounderImpl(base.minFrac, base.maxFrac, minSig, maxSig, priority);
}
return returnValue.withMode(base.mathContext);
}
@@ -597,7 +616,7 @@
@Override
public void apply(DecimalQuantity value) {
value.roundToInfinity();
- value.setMinFraction(0);
+ setResolvedMinFraction(value, 0);
}
@Override
@@ -620,7 +639,7 @@
@Override
public void apply(DecimalQuantity value) {
value.roundToMagnitude(getRoundingMagnitudeFraction(maxFrac), mathContext);
- value.setMinFraction(Math.max(0, -getDisplayMagnitudeFraction(minFrac)));
+ setResolvedMinFraction(value, Math.max(0, -getDisplayMagnitudeFraction(minFrac)));
}
@Override
@@ -643,7 +662,7 @@
@Override
public void apply(DecimalQuantity value) {
value.roundToMagnitude(getRoundingMagnitudeSignificant(value, maxSig), mathContext);
- value.setMinFraction(Math.max(0, -getDisplayMagnitudeSignificant(value, minSig)));
+ setResolvedMinFraction(value, Math.max(0, -getDisplayMagnitudeSignificant(value, minSig)));
// Make sure that digits are displayed on zero.
if (value.isZeroish() && minSig > 0) {
value.setMinInteger(1);
@@ -656,7 +675,7 @@
*/
public void apply(DecimalQuantity quantity, int minInt) {
assert quantity.isZeroish();
- quantity.setMinFraction(minSig - minInt);
+ setResolvedMinFraction(quantity, minSig - minInt);
}
@Override
@@ -672,34 +691,37 @@
final int maxFrac;
final int minSig;
final int maxSig;
+ final RoundingPriority priority;
- public FracSigRounderImpl(int minFrac, int maxFrac, int minSig, int maxSig) {
+ public FracSigRounderImpl(int minFrac, int maxFrac, int minSig, int maxSig, RoundingPriority priority) {
this.minFrac = minFrac;
this.maxFrac = maxFrac;
this.minSig = minSig;
this.maxSig = maxSig;
+ this.priority = priority;
}
@Override
public void apply(DecimalQuantity value) {
- int displayMag = getDisplayMagnitudeFraction(minFrac);
- int roundingMag = getRoundingMagnitudeFraction(maxFrac);
- if (minSig == -1) {
- // Max Sig override
- int candidate = getRoundingMagnitudeSignificant(value, maxSig);
- roundingMag = Math.max(roundingMag, candidate);
+ int roundingMag1 = getRoundingMagnitudeFraction(maxFrac);
+ int roundingMag2 = getRoundingMagnitudeSignificant(value, maxSig);
+ int roundingMag;
+ if (priority == RoundingPriority.RELAXED) {
+ roundingMag = Math.min(roundingMag1, roundingMag2);
} else {
- // Min Sig override
- int candidate = getDisplayMagnitudeSignificant(value, minSig);
- roundingMag = Math.min(roundingMag, candidate);
+ roundingMag = Math.max(roundingMag1, roundingMag2);
}
value.roundToMagnitude(roundingMag, mathContext);
- value.setMinFraction(Math.max(0, -displayMag));
+
+ int displayMag1 = getDisplayMagnitudeFraction(minFrac);
+ int displayMag2 = getDisplayMagnitudeSignificant(value, minSig);
+ int displayMag = Math.min(displayMag1, displayMag2);
+ setResolvedMinFraction(value, Math.max(0, -displayMag));
}
@Override
FracSigRounderImpl createCopy() {
- FracSigRounderImpl copy = new FracSigRounderImpl(minFrac, maxFrac, minSig, maxSig);
+ FracSigRounderImpl copy = new FracSigRounderImpl(minFrac, maxFrac, minSig, maxSig, priority);
copy.mathContext = mathContext;
return copy;
}
@@ -718,7 +740,7 @@
@Override
public void apply(DecimalQuantity value) {
value.roundToIncrement(increment, mathContext);
- value.setMinFraction(increment.scale());
+ setResolvedMinFraction(value, increment.scale());
}
@Override
@@ -747,7 +769,7 @@
@Override
public void apply(DecimalQuantity value) {
value.roundToMagnitude(-maxFrac, mathContext);
- value.setMinFraction(minFrac);
+ setResolvedMinFraction(value, minFrac);
}
@Override
@@ -774,7 +796,7 @@
@Override
public void apply(DecimalQuantity value) {
value.roundToNickel(-maxFrac, mathContext);
- value.setMinFraction(minFrac);
+ setResolvedMinFraction(value, minFrac);
}
@Override
@@ -828,7 +850,17 @@
return -minFrac;
}
+ void setResolvedMinFraction(DecimalQuantity value, int resolvedMinFraction) {
+ if (trailingZeroDisplay == null ||
+ trailingZeroDisplay == TrailingZeroDisplay.AUTO ||
+ // PLURAL_OPERAND_T returns fraction digits as an integer
+ value.getPluralOperand(Operand.t) != 0) {
+ value.setMinFraction(resolvedMinFraction);
+ }
+ }
+
private static int getDisplayMagnitudeSignificant(DecimalQuantity value, int minSig) {
+ // Question: Is it useful to look at trailingZeroDisplay here?
int magnitude = value.isZeroish() ? 0 : value.getMagnitude();
return magnitude - minSig + 1;
}
diff --git a/android_icu4j/src/main/java/android/icu/text/Bidi.java b/android_icu4j/src/main/java/android/icu/text/Bidi.java
index a6319a7..e2adb46 100644
--- a/android_icu4j/src/main/java/android/icu/text/Bidi.java
+++ b/android_icu4j/src/main/java/android/icu/text/Bidi.java
@@ -45,7 +45,7 @@
*
* This is an implementation of the Unicode Bidirectional Algorithm. The
* algorithm is defined in the <a
- * href="http://www.unicode.org/unicode/reports/tr9/">Unicode Standard Annex #9</a>.
+ * href="https://www.unicode.org/reports/tr9/">Unicode Standard Annex #9</a>.
* <p>
*
* Note: Libraries that perform a bidirectional algorithm and reorder strings
@@ -4227,7 +4227,7 @@
/**
* Perform the Unicode Bidi algorithm on a given paragraph, as defined in the
- * <a href="http://www.unicode.org/unicode/reports/tr9/">Unicode Standard Annex #9</a>,
+ * <a href="https://www.unicode.org/reports/tr9/">Unicode Standard Annex #9</a>,
* version 13,
* also described in The Unicode Standard, Version 4.0 .<p>
*
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 e07b223..ef95239 100644
--- a/android_icu4j/src/main/java/android/icu/text/BidiLine.java
+++ b/android_icu4j/src/main/java/android/icu/text/BidiLine.java
@@ -25,7 +25,7 @@
* text in a single paragraph or in a line of a single paragraph
* which has already been processed according to
* the Unicode 3.0 Bidi algorithm as defined in
- * http://www.unicode.org/unicode/reports/tr9/ , version 13,
+ * https://www.unicode.org/reports/tr9/ , version 13,
* also described in The Unicode Standard, Version 4.0.1 .
*
* This means that there is a Bidi object with a levels
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 8971dfc..e8dfb42 100644
--- a/android_icu4j/src/main/java/android/icu/text/BreakIteratorFactory.java
+++ b/android_icu4j/src/main/java/android/icu/text/BreakIteratorFactory.java
@@ -130,17 +130,25 @@
// Get the binary rules.
//
ByteBuffer bytes = null;
- String typeKeyExt = null;
+ String typeKeyExt = "";
if (kind == BreakIterator.KIND_LINE) {
- String lbKeyValue = locale.getKeywordValue("lb");
- if ( lbKeyValue != null && (lbKeyValue.equals("strict") || lbKeyValue.equals("normal") || lbKeyValue.equals("loose")) ) {
- typeKeyExt = "_" + lbKeyValue;
+ String keyValue = locale.getKeywordValue("lb");
+ if ( keyValue != null && (keyValue.equals("strict") || keyValue.equals("normal") || keyValue.equals("loose")) ) {
+ typeKeyExt = "_" + keyValue;
+ }
+ String language = locale.getLanguage();
+ if (language != null && language.equals("ja")) {
+ keyValue = locale.getKeywordValue("lw");
+ if (keyValue != null && keyValue.equals("phrase")) {
+ typeKeyExt += "_" + keyValue;
+ }
}
}
+ String brkfname;
try {
- String typeKey = (typeKeyExt == null)? KIND_NAMES[kind]: KIND_NAMES[kind] + typeKeyExt;
- String brkfname = rb.getStringWithFallback("boundaries/" + typeKey);
+ String typeKey = typeKeyExt.isEmpty() ? KIND_NAMES[kind] : KIND_NAMES[kind] + typeKeyExt;
+ brkfname = rb.getStringWithFallback("boundaries/" + typeKey);
String rulesFileName = ICUData.ICU_BRKITR_NAME+ '/' + brkfname;
bytes = ICUBinary.getData(rulesFileName);
}
@@ -152,7 +160,8 @@
// Create a normal RuleBasedBreakIterator.
//
try {
- iter = RuleBasedBreakIterator.getInstanceFromCompiledRules(bytes);
+ boolean isPhraseBreaking = (brkfname != null) && brkfname.contains("phrase");
+ iter = RuleBasedBreakIterator.getInstanceFromCompiledRules(bytes, isPhraseBreaking);
}
catch (IOException e) {
// Shouldn't be possible to get here.
diff --git a/android_icu4j/src/main/java/android/icu/text/BreakTransliterator.java b/android_icu4j/src/main/java/android/icu/text/BreakTransliterator.java
index be562ad..627e3a4 100644
--- a/android_icu4j/src/main/java/android/icu/text/BreakTransliterator.java
+++ b/android_icu4j/src/main/java/android/icu/text/BreakTransliterator.java
@@ -93,7 +93,7 @@
for(boundary = bi.first(); boundary != BreakIterator.DONE && boundary < pos.limit; boundary = bi.next()) {
if (boundary == 0) continue;
- // HACK: Check to see that preceeding item was a letter
+ // HACK: Check to see that preceding item was a letter
int cp = UTF16.charAt(text, boundary-1);
int type = UCharacter.getType(cp);
diff --git a/android_icu4j/src/main/java/android/icu/text/CanonicalIterator.java b/android_icu4j/src/main/java/android/icu/text/CanonicalIterator.java
index 3f20563..27d8c54 100644
--- a/android_icu4j/src/main/java/android/icu/text/CanonicalIterator.java
+++ b/android_icu4j/src/main/java/android/icu/text/CanonicalIterator.java
@@ -128,7 +128,7 @@
int start = 0;
// i should be the end of the first code point
- // break up the string into segements
+ // break up the string into segments
int i = UTF16.findOffsetFromCodePoint(source, 1);
diff --git a/android_icu4j/src/main/java/android/icu/text/CharsetDetector.java b/android_icu4j/src/main/java/android/icu/text/CharsetDetector.java
index e761d35..837c23b 100644
--- a/android_icu4j/src/main/java/android/icu/text/CharsetDetector.java
+++ b/android_icu4j/src/main/java/android/icu/text/CharsetDetector.java
@@ -381,7 +381,7 @@
}
//
- // Tally up the byte occurence statistics.
+ // Tally up the byte occurrence statistics.
// These are available for use by the various detectors.
//
Arrays.fill(fByteStats, (short)0);
@@ -411,7 +411,7 @@
short fByteStats[] = // byte frequency statistics for the input text.
new short[256]; // Value is percent, not absolute.
- // Value is rounded up, so zero really means zero occurences.
+ // Value is rounded up, so zero really means zero occurrences.
boolean fC1Bytes = // True if any bytes in the range 0x80 - 0x9F are in the input;
false;
diff --git a/android_icu4j/src/main/java/android/icu/text/CharsetMatch.java b/android_icu4j/src/main/java/android/icu/text/CharsetMatch.java
index 6a46072..fe0d83f 100644
--- a/android_icu4j/src/main/java/android/icu/text/CharsetMatch.java
+++ b/android_icu4j/src/main/java/android/icu/text/CharsetMatch.java
@@ -76,7 +76,7 @@
* the string will be trunctated to this length if necessary. A limit value of
* zero or less is ignored, and treated as no limit.
*
- * @param maxLength The maximium length of the String to be created when the
+ * @param maxLength The maximum length of the String to be created when the
* source of the data is an input stream, or -1 for
* unlimited length.
* @return a String created from the converted input data.
diff --git a/android_icu4j/src/main/java/android/icu/text/CharsetRecog_2022.java b/android_icu4j/src/main/java/android/icu/text/CharsetRecog_2022.java
index 98ff2bb..a90464e 100644
--- a/android_icu4j/src/main/java/android/icu/text/CharsetRecog_2022.java
+++ b/android_icu4j/src/main/java/android/icu/text/CharsetRecog_2022.java
@@ -10,7 +10,7 @@
package android.icu.text;
/**
- * class CharsetRecog_2022 part of the ICU charset detection imlementation.
+ * class CharsetRecog_2022 part of the ICU charset detection implementation.
* This is a superclass for the individual detectors for
* each of the detectable members of the ISO 2022 family
* of encodings.
@@ -75,7 +75,7 @@
}
//
- // Initial quality is based on relative proportion of recongized vs.
+ // Initial quality is based on relative proportion of recognized vs.
// unrecognized escape sequences.
// All good: quality = 100;
// half or less good: quality = 0;
diff --git a/android_icu4j/src/main/java/android/icu/text/CharsetRecog_UTF8.java b/android_icu4j/src/main/java/android/icu/text/CharsetRecog_UTF8.java
index bf5e17c..ce9d5a0 100644
--- a/android_icu4j/src/main/java/android/icu/text/CharsetRecog_UTF8.java
+++ b/android_icu4j/src/main/java/android/icu/text/CharsetRecog_UTF8.java
@@ -74,7 +74,7 @@
}
}
- // Cook up some sort of confidence score, based on presense of a BOM
+ // Cook up some sort of confidence score, based on presence of a BOM
// and the existence of valid and/or invalid multi-byte sequences.
confidence = 0;
if (hasBOM && numInvalid==0) {
@@ -91,7 +91,7 @@
// TODO: add plain ASCII as an explicitly detected type.
confidence = 15;
} else if (numValid > numInvalid*10) {
- // Probably corruput utf-8 data. Valid sequences aren't likely by chance.
+ // Probably corrupt utf-8 data. Valid sequences aren't likely by chance.
confidence = 25;
}
return confidence == 0 ? null : new CharsetMatch(det, this, confidence);
diff --git a/android_icu4j/src/main/java/android/icu/text/CharsetRecog_mbcs.java b/android_icu4j/src/main/java/android/icu/text/CharsetRecog_mbcs.java
index b250c2c..bbecc7d 100644
--- a/android_icu4j/src/main/java/android/icu/text/CharsetRecog_mbcs.java
+++ b/android_icu4j/src/main/java/android/icu/text/CharsetRecog_mbcs.java
@@ -13,10 +13,10 @@
import java.util.Arrays;
/**
- * CharsetRecognizer implemenation for Asian - double or multi-byte - charsets.
+ * CharsetRecognizer implementation for Asian - double or multi-byte - charsets.
* Match is determined mostly by the input data adhering to the
* encoding scheme for the charset, and, optionally,
- * frequency-of-occurence of characters.
+ * frequency-of-occurrence of characters.
* <p/>
* Instances of this class are singletons, one per encoding
* being recognized. They are created in the main
@@ -87,7 +87,7 @@
if (doubleByteCharCount == 0 && totalCharCount < 10) {
// There weren't any multibyte sequences, and there was a low density of non-ASCII single bytes.
// We don't have enough data to have any confidence.
- // Statistical analysis of single byte non-ASCII charcters would probably help here.
+ // Statistical analysis of single byte non-ASCII characters would probably help here.
confidence = 0;
}
else {
@@ -109,7 +109,7 @@
}
if (commonChars == null) {
- // We have no statistics on frequently occuring characters.
+ // We have no statistics on frequently occurring characters.
// Assess confidence purely on having a reasonable number of
// multi-byte characters (the more the better
confidence = 30 + doubleByteCharCount - 20*badCharCount;
@@ -118,7 +118,7 @@
}
}else {
//
- // Frequency of occurence statistics exist.
+ // Frequency of occurrence statistics exist.
//
double maxVal = Math.log((float)doubleByteCharCount / 4);
double scaleFactor = 90.0 / maxVal;
@@ -189,7 +189,7 @@
static class CharsetRecog_sjis extends CharsetRecog_mbcs {
static int [] commonChars =
// TODO: This set of data comes from the character frequency-
- // of-occurence analysis tool. The data needs to be moved
+ // of-occurrence analysis tool. The data needs to be moved
// into a resource and loaded from there.
{0x8140, 0x8141, 0x8142, 0x8145, 0x815b, 0x8169, 0x816a, 0x8175, 0x8176, 0x82a0,
0x82a2, 0x82a4, 0x82a9, 0x82aa, 0x82ab, 0x82ad, 0x82af, 0x82b1, 0x82b3, 0x82b5,
@@ -251,7 +251,7 @@
static class CharsetRecog_big5 extends CharsetRecog_mbcs {
static int [] commonChars =
// TODO: This set of data comes from the character frequency-
- // of-occurence analysis tool. The data needs to be moved
+ // of-occurrence analysis tool. The data needs to be moved
// into a resource and loaded from there.
{0xa140, 0xa141, 0xa142, 0xa143, 0xa147, 0xa149, 0xa175, 0xa176, 0xa440, 0xa446,
0xa447, 0xa448, 0xa451, 0xa454, 0xa457, 0xa464, 0xa46a, 0xa46c, 0xa477, 0xa4a3,
@@ -390,7 +390,7 @@
static class CharsetRecog_euc_jp extends CharsetRecog_euc {
static int [] commonChars =
// TODO: This set of data comes from the character frequency-
- // of-occurence analysis tool. The data needs to be moved
+ // of-occurrence analysis tool. The data needs to be moved
// into a resource and loaded from there.
{0xa1a1, 0xa1a2, 0xa1a3, 0xa1a6, 0xa1bc, 0xa1ca, 0xa1cb, 0xa1d6, 0xa1d7, 0xa4a2,
0xa4a4, 0xa4a6, 0xa4a8, 0xa4aa, 0xa4ab, 0xa4ac, 0xa4ad, 0xa4af, 0xa4b1, 0xa4b3,
@@ -427,7 +427,7 @@
static class CharsetRecog_euc_kr extends CharsetRecog_euc {
static int [] commonChars =
// TODO: This set of data comes from the character frequency-
- // of-occurence analysis tool. The data needs to be moved
+ // of-occurrence analysis tool. The data needs to be moved
// into a resource and loaded from there.
{0xb0a1, 0xb0b3, 0xb0c5, 0xb0cd, 0xb0d4, 0xb0e6, 0xb0ed, 0xb0f8, 0xb0fa, 0xb0fc,
0xb1b8, 0xb1b9, 0xb1c7, 0xb1d7, 0xb1e2, 0xb3aa, 0xb3bb, 0xb4c2, 0xb4cf, 0xb4d9,
@@ -527,7 +527,7 @@
static int [] commonChars =
// TODO: This set of data comes from the character frequency-
- // of-occurence analysis tool. The data needs to be moved
+ // of-occurrence analysis tool. The data needs to be moved
// into a resource and loaded from there.
{0xa1a1, 0xa1a2, 0xa1a3, 0xa1a4, 0xa1b0, 0xa1b1, 0xa1f1, 0xa1f3, 0xa3a1, 0xa3ac,
0xa3ba, 0xb1a8, 0xb1b8, 0xb1be, 0xb2bb, 0xb3c9, 0xb3f6, 0xb4f3, 0xb5bd, 0xb5c4,
diff --git a/android_icu4j/src/main/java/android/icu/text/CollationKey.java b/android_icu4j/src/main/java/android/icu/text/CollationKey.java
index dea2abe..fb69e5b 100644
--- a/android_icu4j/src/main/java/android/icu/text/CollationKey.java
+++ b/android_icu4j/src/main/java/android/icu/text/CollationKey.java
@@ -49,7 +49,7 @@
*
* <p>More information about the composition of the bit sequence can
* be found in the
- * <a href="http://www.icu-project.org/userguide/Collate_ServiceArchitecture.html">
+ * <a href="https://unicode-org.github.io/icu/userguide/collation/architecture">
* user guide</a>.</p>
*
* <p>The following example shows how <code>CollationKey</code>s can be used
@@ -373,7 +373,7 @@
* also match "Smithsonian" and similar.
* <p>
* For more on usage, see example in test procedure
- * <a href="http://source.icu-project.org/repos/icu/icu4j/trunk/src/com/ibm/icu/dev/test/collator/CollationAPITest.java">
+ * <a href="https://github.com/unicode-org/icu/blob/main/icu4j/main/tests/collate/src/com/ibm/icu/dev/test/collator/CollationAPITest.java">
* src/com/ibm/icu/dev/test/collator/CollationAPITest/TestBounds.
* </a>
* <p>
diff --git a/android_icu4j/src/main/java/android/icu/text/Collator.java b/android_icu4j/src/main/java/android/icu/text/Collator.java
index dda667b..ccfb38d 100644
--- a/android_icu4j/src/main/java/android/icu/text/Collator.java
+++ b/android_icu4j/src/main/java/android/icu/text/Collator.java
@@ -42,7 +42,7 @@
*
* <p>Following the <a href=http://www.unicode.org>Unicode
* Consortium</a>'s specifications for the
-* <a href="http://www.unicode.org/unicode/reports/tr10/">Unicode Collation
+* <a href="https://www.unicode.org/reports/tr10/">Unicode Collation
* Algorithm (UCA)</a>, there are 5 different levels of strength used
* in comparisons:
*
@@ -77,7 +77,7 @@
* When all other strengths are equal, the IDENTICAL strength is used as a
* tiebreaker. The Unicode code point values of the NFD form of each string
* are compared, just in case there is no difference.
-* For example, Hebrew cantellation marks are only distinguished at this
+* For example, Hebrew cantillation marks are only distinguished at this
* strength. This strength should be used sparingly, as only code point
* value differences between two strings is an extremely rare occurrence.
* Using this strength substantially decreases the performance for both
@@ -216,7 +216,7 @@
* will be decomposed for collation.
*
* <p>CANONICAL_DECOMPOSITION corresponds to Normalization Form D as
- * described in <a href="http://www.unicode.org/unicode/reports/tr15/">
+ * described in <a href="https://www.unicode.org/reports/tr15/">
* Unicode Technical Report #15</a>.
*
* @see #NO_DECOMPOSITION
diff --git a/android_icu4j/src/main/java/android/icu/text/CompactDecimalFormat.java b/android_icu4j/src/main/java/android/icu/text/CompactDecimalFormat.java
index 82be12c..fc253cf 100644
--- a/android_icu4j/src/main/java/android/icu/text/CompactDecimalFormat.java
+++ b/android_icu4j/src/main/java/android/icu/text/CompactDecimalFormat.java
@@ -21,6 +21,12 @@
/**
* Formats numbers in compact (abbreviated) notation, like "1.2K" instead of "1200".
*
+ * <p>
+ * <strong>IMPORTANT:</strong> New users are strongly encouraged to see if
+ * {@link NumberFormatter} fits their use case. Although not deprecated, this
+ * class, CompactDecimalFormat, is provided for backwards compatibility only.
+ * <hr>
+ *
* The CompactDecimalFormat produces abbreviated numbers, suitable for display in environments will
* limited real estate. For example, 'Hits: 1.2B' instead of 'Hits: 1,200,000,000'. The format will
* be appropriate for the given language, such as "1,2 Mrd." for German.
@@ -67,6 +73,9 @@
}
/**
+ * <strong>NOTE:</strong> New users are strongly encouraged to use
+ * {@link NumberFormatter} instead of NumberFormat.
+ * <hr>
* Creates a CompactDecimalFormat appropriate for a locale. The result may be affected by the
* number system in the locale, such as ar-u-nu-latn.
*
@@ -78,6 +87,9 @@
}
/**
+ * <strong>NOTE:</strong> New users are strongly encouraged to use
+ * {@link NumberFormatter} instead of NumberFormat.
+ * <hr>
* Creates a CompactDecimalFormat appropriate for a locale. The result may be affected by the
* number system in the locale, such as ar-u-nu-latn.
*
diff --git a/android_icu4j/src/main/java/android/icu/text/CurrencyDisplayNames.java b/android_icu4j/src/main/java/android/icu/text/CurrencyDisplayNames.java
index 0f1dff0..0af43d0 100644
--- a/android_icu4j/src/main/java/android/icu/text/CurrencyDisplayNames.java
+++ b/android_icu4j/src/main/java/android/icu/text/CurrencyDisplayNames.java
@@ -143,7 +143,6 @@
*
* @param isoCode the three-letter ISO code.
* @return the formal symbol.
- * @hide draft / provisional / internal are hidden on Android
*/
public abstract String getFormalSymbol(String isoCode);
@@ -158,7 +157,6 @@
*
* @param isoCode the three-letter ISO code.
* @return the variant symbol.
- * @hide draft / provisional / internal are hidden on Android
*/
public abstract String getVariantSymbol(String isoCode);
diff --git a/android_icu4j/src/main/java/android/icu/text/DateFormat.java b/android_icu4j/src/main/java/android/icu/text/DateFormat.java
index f814af0..ef189e6 100644
--- a/android_icu4j/src/main/java/android/icu/text/DateFormat.java
+++ b/android_icu4j/src/main/java/android/icu/text/DateFormat.java
@@ -493,31 +493,25 @@
/**
* Hour Cycle
- * @hide Only a subset of ICU is exposed in Android
- * @hide draft / provisional / internal are hidden on Android
*/
public enum HourCycle {
/**
* hour in am/pm (0~11)
- * @hide draft / provisional / internal are hidden on Android
*/
HOUR_CYCLE_11,
/**
* hour in am/pm (1~12)
- * @hide draft / provisional / internal are hidden on Android
*/
HOUR_CYCLE_12,
/**
* hour in day (0~23)
- * @hide draft / provisional / internal are hidden on Android
*/
HOUR_CYCLE_23,
/**
* hour in day (1~24)
- * @hide draft / provisional / internal are hidden on Android
*/
HOUR_CYCLE_24;
};
@@ -1798,7 +1792,7 @@
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//-------------------------------------------------------------------------
- // Public static interface for creating custon DateFormats for different
+ // Public static interface for creating custom DateFormats for different
// types of Calendars.
//-------------------------------------------------------------------------
@@ -1938,7 +1932,6 @@
*
* @param cal The calendar system for which a date/time format is desired.
* @param locale The locale for which the date/time format is desired.
- * @hide draft / provisional / internal are hidden on Android
*/
static final public DateFormat getInstance(Calendar cal, ULocale locale) {
return getDateTimeInstance(cal, SHORT, SHORT, locale);
diff --git a/android_icu4j/src/main/java/android/icu/text/DateFormatSymbols.java b/android_icu4j/src/main/java/android/icu/text/DateFormatSymbols.java
index 9f8b781..3f25b39 100644
--- a/android_icu4j/src/main/java/android/icu/text/DateFormatSymbols.java
+++ b/android_icu4j/src/main/java/android/icu/text/DateFormatSymbols.java
@@ -505,6 +505,13 @@
String shortQuarters[] = null;
/**
+ * Narrow quarter names. For example: "1", "2", "3", "4". An array
+ * of 4 strings indexed by the month divided by 3.
+ * @serial
+ */
+ String narrowQuarters[] = null;
+
+ /**
* Full quarter names. For example: "1st Quarter", "2nd Quarter", "3rd Quarter",
* "4th Quarter". An array of 4 strings, indexed by the month divided by 3.
* @serial
@@ -519,6 +526,13 @@
String standaloneShortQuarters[] = null;
/**
+ * Standalone narrow quarter names. For example: "1", "2", "3", "4". An array
+ * of 4 strings indexed by the month divided by 3.
+ * @serial
+ */
+ String standaloneNarrowQuarters[] = null;
+
+ /**
* Standalone full quarter names. For example: "1st Quarter", "2nd Quarter", "3rd Quarter",
* "4th Quarter". An array of 4 strings, indexed by the month divided by 3.
* @serial
@@ -1013,7 +1027,7 @@
* <strong>[icu]</strong> Returns quarter strings. For example: "1st Quarter", "2nd Quarter", etc.
* @param context The quarter context, FORMAT or STANDALONE.
* @param width The width or the returned quarter string,
- * either WIDE or ABBREVIATED. There are no NARROW quarters.
+ * WIDE, NARROW, or ABBREVIATED.
* @return the quarter strings.
*/
public String[] getQuarters(int context, int width) {
@@ -1029,7 +1043,7 @@
returnValue = shortQuarters;
break;
case NARROW :
- returnValue = null;
+ returnValue = narrowQuarters;
break;
}
break;
@@ -1044,7 +1058,7 @@
returnValue = standaloneShortQuarters;
break;
case NARROW:
- returnValue = null;
+ returnValue = standaloneNarrowQuarters;
break;
}
break;
@@ -1060,7 +1074,7 @@
* @param newQuarters the new quarter strings.
* @param context The formatting context, FORMAT or STANDALONE.
* @param width The width of the quarter string,
- * either WIDE or ABBREVIATED. There are no NARROW quarters.
+ * WIDE, NARROW, or ABBREVIATED.
*/
public void setQuarters(String[] newQuarters, int context, int width) {
switch (context) {
@@ -1073,7 +1087,7 @@
shortQuarters = duplicate(newQuarters);
break;
case NARROW :
- //narrowQuarters = duplicate(newQuarters);
+ narrowQuarters = duplicate(newQuarters);
break;
default : // HANDLE SHORT, etc.
break;
@@ -1088,7 +1102,7 @@
standaloneShortQuarters = duplicate(newQuarters);
break;
case NARROW :
- //standaloneNarrowQuarters = duplicate(newQuarters);
+ standaloneNarrowQuarters = duplicate(newQuarters);
break;
default : // HANDLE SHORT, etc.
break;
@@ -1472,7 +1486,7 @@
&& arrayOfArrayEquals(zoneStrings, that.zoneStrings)
// getDiplayName maps deprecated country and language codes to the current ones
// too bad there is no way to get the current codes!
- // I thought canolicalize() would map the codes but .. alas! it doesn't.
+ // I thought canonicalize() would map the codes but .. alas! it doesn't.
&& requestedLocale.getDisplayName().equals(that.requestedLocale.getDisplayName())
&& Utility.arrayEquals(localPatternChars,
that.localPatternChars));
@@ -1636,8 +1650,10 @@
this.ampmsNarrow = dfs.ampmsNarrow;
this.timeSeparator = dfs.timeSeparator;
this.shortQuarters = dfs.shortQuarters;
+ this.narrowQuarters = dfs.narrowQuarters;
this.quarters = dfs.quarters;
this.standaloneShortQuarters = dfs.standaloneShortQuarters;
+ this.standaloneNarrowQuarters = dfs.standaloneNarrowQuarters;
this.standaloneQuarters = dfs.standaloneQuarters;
this.leapMonthPatterns = dfs.leapMonthPatterns;
this.shortYearNames = dfs.shortYearNames;
@@ -2052,9 +2068,11 @@
quarters = arrays.get("quarters/format/wide");
shortQuarters = arrays.get("quarters/format/abbreviated");
+ narrowQuarters = arrays.get("quarters/format/narrow");
standaloneQuarters = arrays.get("quarters/stand-alone/wide");
standaloneShortQuarters = arrays.get("quarters/stand-alone/abbreviated");
+ standaloneNarrowQuarters = arrays.get("quarters/stand-alone/narrow");
// BEGIN Android-changed: Load narrow quarters needed for the Q/q symbols in DateTimeFormatter.
if (aospExtend