summaryrefslogtreecommitdiff
path: root/android_icu4j
diff options
context:
space:
mode:
authorVictor Chang <vichang@google.com>2021-01-05 16:24:59 +0000
committerVictor Chang <vichang@google.com>2021-01-12 18:24:25 +0000
commit2c09218f13106837aa39cd597fa4f5b6d1c3597c (patch)
treef9c6cf03a5dd2c17e9815e2797f83253ad2e7c2d /android_icu4j
parent0e3f9a9e8214629bf74a6815efc0a02505493137 (diff)
downloadicu-2c09218f13106837aa39cd597fa4f5b6d1c3597c.tar.gz
Integrate ICU4J 67.1 with Android patches into android_icu4j/
android_icu4j/ files updated using: tools/srcgen/generate_android_icu4j.sh Change-Id: Ia1038271820ee3c5da364d6c5ff688261ba5a378
Diffstat (limited to 'android_icu4j')
-rw-r--r--android_icu4j/src/main/java/android/icu/impl/CurrencyData.java10
-rw-r--r--android_icu4j/src/main/java/android/icu/impl/FormattedStringBuilder.java109
-rw-r--r--android_icu4j/src/main/java/android/icu/impl/FormattedValueStringBuilderImpl.java96
-rw-r--r--android_icu4j/src/main/java/android/icu/impl/ICUCurrencyDisplayInfoProvider.java76
-rw-r--r--android_icu4j/src/main/java/android/icu/impl/ICUService.java6
-rw-r--r--android_icu4j/src/main/java/android/icu/impl/PluralRulesLoader.java10
-rw-r--r--android_icu4j/src/main/java/android/icu/impl/SimpleFormatterImpl.java122
-rw-r--r--android_icu4j/src/main/java/android/icu/impl/StringSegment.java6
-rw-r--r--android_icu4j/src/main/java/android/icu/impl/locale/InternalLocaleBuilder.java4
-rw-r--r--android_icu4j/src/main/java/android/icu/impl/locale/LSR.java24
-rw-r--r--android_icu4j/src/main/java/android/icu/impl/locale/LanguageTag.java16
-rw-r--r--android_icu4j/src/main/java/android/icu/impl/locale/LocaleDistance.java166
-rw-r--r--android_icu4j/src/main/java/android/icu/impl/locale/XLikelySubtags.java153
-rw-r--r--android_icu4j/src/main/java/android/icu/impl/number/AdoptingModifierStore.java41
-rw-r--r--android_icu4j/src/main/java/android/icu/impl/number/ConstantMultiFieldModifier.java4
-rw-r--r--android_icu4j/src/main/java/android/icu/impl/number/CurrencySpacingEnabledModifier.java6
-rw-r--r--android_icu4j/src/main/java/android/icu/impl/number/DecimalFormatProperties.java2
-rw-r--r--android_icu4j/src/main/java/android/icu/impl/number/DecimalQuantity.java25
-rw-r--r--android_icu4j/src/main/java/android/icu/impl/number/DecimalQuantity_AbstractBCD.java108
-rw-r--r--android_icu4j/src/main/java/android/icu/impl/number/DecimalQuantity_DualStorageBCD.java5
-rw-r--r--android_icu4j/src/main/java/android/icu/impl/number/LocalizedNumberFormatterAsFormat.java12
-rw-r--r--android_icu4j/src/main/java/android/icu/impl/number/LongNameHandler.java12
-rw-r--r--android_icu4j/src/main/java/android/icu/impl/number/Modifier.java11
-rw-r--r--android_icu4j/src/main/java/android/icu/impl/number/ModifierStore.java3
-rw-r--r--android_icu4j/src/main/java/android/icu/impl/number/MutablePatternModifier.java91
-rw-r--r--android_icu4j/src/main/java/android/icu/impl/number/PatternStringUtils.java95
-rw-r--r--android_icu4j/src/main/java/android/icu/impl/number/PropertiesAffixPatternProvider.java10
-rw-r--r--android_icu4j/src/main/java/android/icu/impl/number/RoundingUtils.java3
-rw-r--r--android_icu4j/src/main/java/android/icu/impl/number/SimpleModifier.java84
-rw-r--r--android_icu4j/src/main/java/android/icu/impl/number/parse/AffixMatcher.java32
-rw-r--r--android_icu4j/src/main/java/android/icu/impl/number/parse/NumberParserImpl.java8
-rw-r--r--android_icu4j/src/main/java/android/icu/number/CompactNotation.java24
-rw-r--r--android_icu4j/src/main/java/android/icu/number/FormattedNumber.java82
-rw-r--r--android_icu4j/src/main/java/android/icu/number/FormattedNumberRange.java45
-rw-r--r--android_icu4j/src/main/java/android/icu/number/LocalizedNumberFormatter.java40
-rw-r--r--android_icu4j/src/main/java/android/icu/number/NumberFormatter.java37
-rw-r--r--android_icu4j/src/main/java/android/icu/number/NumberFormatterImpl.java33
-rw-r--r--android_icu4j/src/main/java/android/icu/number/NumberPropertyMapper.java12
-rw-r--r--android_icu4j/src/main/java/android/icu/number/NumberSkeletonImpl.java171
-rw-r--r--android_icu4j/src/main/java/android/icu/number/Precision.java24
-rw-r--r--android_icu4j/src/main/java/android/icu/number/ScientificNotation.java7
-rw-r--r--android_icu4j/src/main/java/android/icu/text/ChineseDateFormat.java3
-rw-r--r--android_icu4j/src/main/java/android/icu/text/ConstrainedFieldPosition.java16
-rw-r--r--android_icu4j/src/main/java/android/icu/text/CurrencyDisplayNames.java45
-rw-r--r--android_icu4j/src/main/java/android/icu/text/DateFormat.java31
-rw-r--r--android_icu4j/src/main/java/android/icu/text/DateFormatSymbols.java32
-rw-r--r--android_icu4j/src/main/java/android/icu/text/DateIntervalFormat.java43
-rw-r--r--android_icu4j/src/main/java/android/icu/text/DateIntervalInfo.java11
-rw-r--r--android_icu4j/src/main/java/android/icu/text/DateTimePatternGenerator.java103
-rw-r--r--android_icu4j/src/main/java/android/icu/text/DecimalFormat.java79
-rw-r--r--android_icu4j/src/main/java/android/icu/text/FormattedValue.java5
-rw-r--r--android_icu4j/src/main/java/android/icu/text/ListFormatter.java558
-rw-r--r--android_icu4j/src/main/java/android/icu/text/MeasureFormat.java68
-rw-r--r--android_icu4j/src/main/java/android/icu/text/NumberFormat.java4
-rw-r--r--android_icu4j/src/main/java/android/icu/text/PluralRules.java36
-rw-r--r--android_icu4j/src/main/java/android/icu/text/RBBIRuleScanner.java17
-rw-r--r--android_icu4j/src/main/java/android/icu/text/RBBITableBuilder.java179
-rw-r--r--android_icu4j/src/main/java/android/icu/text/RelativeDateTimeFormatter.java45
-rw-r--r--android_icu4j/src/main/java/android/icu/text/RuleBasedBreakIterator.java7
-rw-r--r--android_icu4j/src/main/java/android/icu/text/SimpleDateFormat.java43
-rw-r--r--android_icu4j/src/main/java/android/icu/text/UFormat.java3
-rw-r--r--android_icu4j/src/main/java/android/icu/util/BytesTrie.java4
-rw-r--r--android_icu4j/src/main/java/android/icu/util/Calendar.java8
-rw-r--r--android_icu4j/src/main/java/android/icu/util/CharsTrie.java4
-rw-r--r--android_icu4j/src/main/java/android/icu/util/Currency.java32
-rw-r--r--android_icu4j/src/main/java/android/icu/util/GlobalizationPreferences.java21
-rw-r--r--android_icu4j/src/main/java/android/icu/util/LocaleMatcher.java234
-rw-r--r--android_icu4j/src/main/java/android/icu/util/MeasureUnit.java113
-rw-r--r--android_icu4j/src/main/java/android/icu/util/ULocale.java564
-rw-r--r--android_icu4j/src/main/java/android/icu/util/VersionInfo.java4
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/data/collationtest.txt47
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/data/numberpermutationtest.txt92
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/data/testdata/ibm9027.cnvbin52544 -> 52544 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/data/testdata/root.resbin1792 -> 1792 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/data/testdata/structLocale.resbin170592 -> 170736 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/data/testdata/test1.cnvbin4320 -> 4320 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/data/testdata/test1bmp.cnvbin2448 -> 2448 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/data/testdata/test2.cnvbin9344 -> 9344 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/data/testdata/test3.cnvbin10608 -> 10608 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/data/testdata/test4.cnvbin10496 -> 10496 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/data/testdata/test4x.cnvbin800 -> 800 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/data/testdata/test5.cnvbin7760 -> 7760 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/impl/number/DecimalQuantity_64BitBCD.java1
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/impl/number/DecimalQuantity_ByteArrayBCD.java1
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/impl/number/DecimalQuantity_SimpleStorage.java45
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/calendar/CalendarRegressionTest.java2
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/format/DateFormatTest.java79
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/format/DateIntervalFormatTest.java104
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/format/DateTimeGeneratorTest.java10
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/format/FormattedStringBuilderTest.java5
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/format/GlobalizationPreferencesTest.java46
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/format/IntlTestNumberFormat.java26
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/format/ListFormatterTest.java145
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/format/MeasureUnitTest.java54
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/format/NumberFormatTest.java62
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/format/PluralRulesTest.java61
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/number/DecimalQuantityTest.java309
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/number/ExhaustiveNumberTest.java8
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/number/MutablePatternModifierTest.java25
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/number/NumberFormatterApiTest.java441
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/number/NumberPermutationTest.java2
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/number/NumberSkeletonTest.java135
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/rbbi/RBBITestMonkey.java542
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/rbbi/rbbitst.txt20
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/FormatHandler.java31
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/SerializableTestUtility.java2
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.impl.IllegalIcuArgumentException.datbin858 -> 0 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.impl.InvalidFormatException.datbin2821 -> 0 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.impl.locale.LocaleSyntaxException.datbin873 -> 0 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.number.SkeletonSyntaxException.datbin893 -> 0 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.ArabicShapingException.datbin2821 -> 0 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.ChineseDateFormat.datbin91840 -> 0 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.ChineseDateFormatSymbols.datbin39168 -> 0 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.DateFormat.datbin46875 -> 0 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.DateFormatSymbols.datbin31524 -> 0 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.DecimalFormat.datbin11058 -> 0 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.SimpleDateFormat.datbin90751 -> 0 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.StringPrepParseException.datbin7356 -> 0 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.BuddhistCalendar.datbin2672 -> 0 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.ChineseCalendar.datbin3152 -> 0 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.DangiCalendar.datbin3538 -> 0 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.EthiopicCalendar.datbin2621 -> 0 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.HebrewCalendar.datbin3647 -> 0 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.ICUCloneNotSupportedException.datbin1393 -> 0 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.ICUException.datbin1298 -> 0 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.ICUUncheckedIOException.datbin1331 -> 0 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.IllformedLocaleException.datbin923 -> 0 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.IndianCalendar.datbin2511 -> 0 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.JapaneseCalendar.datbin2750 -> 0 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.PersianCalendar.datbin3664 -> 0 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.TaiwanCalendar.datbin2821 -> 0 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.UResourceTypeMismatchException.datbin2886 -> 0 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.impl.DateNumberFormat.dat (renamed from android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.impl.DateNumberFormat.dat)bin2602 -> 2602 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.impl.IllegalIcuArgumentException.datbin0 -> 1114 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.impl.InvalidFormatException.datbin0 -> 3177 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.impl.OlsonTimeZone.dat (renamed from android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.impl.OlsonTimeZone.dat)bin20899 -> 20943 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.impl.RelativeDateFormat.dat (renamed from android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.impl.RelativeDateFormat.dat)bin11741 -> 11741 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.impl.TZDBTimeZoneNames.dat (renamed from android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.impl.TZDBTimeZoneNames.dat)bin277 -> 277 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.impl.TimeZoneAdapter.dat (renamed from android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.impl.TimeZoneAdapter.dat)bin21332 -> 21376 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.impl.TimeZoneGenericNames.dat (renamed from android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.impl.TimeZoneGenericNames.dat)bin407 -> 407 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.impl.TimeZoneNamesImpl.dat (renamed from android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.impl.TimeZoneNamesImpl.dat)bin237 -> 237 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.impl.duration.BasicDurationFormat.dat (renamed from android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.impl.duration.BasicDurationFormat.dat)bin383 -> 383 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.impl.locale.LocaleSyntaxException.datbin0 -> 1134 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.impl.number.CustomSymbolCurrency.dat (renamed from android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.impl.number.CustomSymbolCurrency.dat)bin345 -> 345 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.impl.number.DecimalFormatProperties.dat (renamed from android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.impl.number.DecimalFormatProperties.dat)bin586 -> 586 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.impl.number.LocalizedNumberFormatterAsFormat.dat (renamed from android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.impl.number.LocalizedNumberFormatterAsFormat.dat)bin187 -> 187 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.impl.number.Properties.dat (renamed from android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.impl.number.Properties.dat)bin106 -> 106 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.math.BigDecimal.dat (renamed from android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.math.BigDecimal.dat)bin520 -> 520 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.math.MathContext.dat (renamed from android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.math.MathContext.dat)bin595 -> 595 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.number.SkeletonSyntaxException.datbin0 -> 1149 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.ArabicShapingException.datbin0 -> 3177 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.ChineseDateFormat$Field.dat (renamed from android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.ChineseDateFormat$Field.dat)bin312 -> 312 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.ChineseDateFormat.datbin0 -> 92019 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.ChineseDateFormatSymbols.datbin0 -> 39347 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.CompactDecimalFormat.dat (renamed from android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.CompactDecimalFormat.dat)bin65 -> 65 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.CurrencyPluralInfo.dat (renamed from android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.CurrencyPluralInfo.dat)bin1170 -> 1170 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.DateFormat$Field.dat (renamed from android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.DateFormat$Field.dat)bin792 -> 792 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.DateFormat.datbin0 -> 47141 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.DateFormatSymbols.datbin0 -> 31790 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.DateIntervalFormat$SpanField.datbin0 -> 303 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.DateIntervalFormat.dat (renamed from android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.DateIntervalFormat.dat)bin10974 -> 12289 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.DateIntervalInfo$PatternInfo.dat (renamed from android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.DateIntervalInfo$PatternInfo.dat)bin270 -> 270 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.DateIntervalInfo.dat (renamed from android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.DateIntervalInfo.dat)bin568 -> 568 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.DecimalFormat.datbin0 -> 11289 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.DecimalFormatSymbols.dat (renamed from android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.DecimalFormatSymbols.dat)bin7029 -> 7260 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.ListFormatter$Field.datbin0 -> 250 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.ListFormatter$SpanField.datbin0 -> 289 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.MeasureFormat.dat (renamed from android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.MeasureFormat.dat)bin3192 -> 3247 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.MessageFormat$Field.dat (renamed from android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.MessageFormat$Field.dat)bin249 -> 249 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.MessageFormat.dat (renamed from android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.MessageFormat.dat)bin382 -> 382 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.NumberFormat$Field.dat (renamed from android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.NumberFormat$Field.dat)bin449 -> 449 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.NumberFormat.dat (renamed from android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.NumberFormat.dat)bin3714 -> 3757 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.PluralFormat.dat (renamed from android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.PluralFormat.dat)bin2960 -> 3003 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.PluralRules.dat (renamed from android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.PluralRules.dat)bin872 -> 872 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.RelativeDateTimeFormatter$Field.datbin0 -> 246 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.RuleBasedNumberFormat.dat (renamed from android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.RuleBasedNumberFormat.dat)bin44502 -> 44502 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.SelectFormat.dat (renamed from android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.SelectFormat.dat)bin202 -> 202 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.SimpleDateFormat.datbin0 -> 91017 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.StringPrepParseException.datbin0 -> 7712 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.TimeUnitFormat.dat (renamed from android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.TimeUnitFormat.dat)bin2606 -> 2649 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.TimeZoneFormat.dat (renamed from android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.TimeZoneFormat.dat)bin774 -> 774 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.AnnualTimeZoneRule.dat (renamed from android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.AnnualTimeZoneRule.dat)bin898 -> 898 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.BuddhistCalendar.datbin0 -> 2672 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.Calendar.dat (renamed from android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.Calendar.dat)bin4090 -> 4090 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.ChineseCalendar.datbin0 -> 3242 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.CopticCalendar.dat (renamed from android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.CopticCalendar.dat)bin3328 -> 3328 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.Currency.dat (renamed from android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.Currency.dat)bin345 -> 345 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.DangiCalendar.datbin0 -> 3610 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.DateInterval.dat (renamed from android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.DateInterval.dat)bin139 -> 139 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.DateTimeRule.dat (renamed from android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.DateTimeRule.dat)bin324 -> 324 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.EthiopicCalendar.datbin0 -> 2621 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.GregorianCalendar.dat (renamed from android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.GregorianCalendar.dat)bin4099 -> 4099 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.HebrewCalendar.datbin0 -> 3683 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.ICUCloneNotSupportedException.datbin0 -> 1674 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.ICUException.datbin0 -> 1579 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.ICUUncheckedIOException.datbin0 -> 1612 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.IllformedLocaleException.datbin0 -> 1184 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.IndianCalendar.datbin0 -> 2511 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.InitialTimeZoneRule.dat (renamed from android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.InitialTimeZoneRule.dat)bin241 -> 241 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.IslamicCalendar.dat (renamed from android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.IslamicCalendar.dat)bin3876 -> 3876 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.JapaneseCalendar.datbin0 -> 2750 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.MeasureUnit.dat (renamed from android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.MeasureUnit.dat)bin173 -> 173 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.NoUnit.dat (renamed from android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.NoUnit.dat)bin173 -> 173 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.PersianCalendar.datbin0 -> 4600 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.RuleBasedTimeZone.dat (renamed from android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.RuleBasedTimeZone.dat)bin1401 -> 1401 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.SimpleTimeZone.dat (renamed from android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.SimpleTimeZone.dat)bin1152 -> 1152 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.TaiwanCalendar.datbin0 -> 2911 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.TimeArrayTimeZoneRule.dat (renamed from android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.TimeArrayTimeZoneRule.dat)bin312 -> 312 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.TimeUnit.dat (renamed from android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.TimeUnit.dat)bin173 -> 173 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.TimeZone.dat (renamed from android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.TimeZone.dat)bin1606 -> 1606 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.ULocale.dat (renamed from android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.ULocale.dat)bin365 -> 365 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.UResourceTypeMismatchException.datbin0 -> 3242 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.VTimeZone.dat (renamed from android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.VTimeZone.dat)bin2676 -> 2676 bytes
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/timezone/TimeZoneTest.java1
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/util/CurrencyTest.java59
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/util/DebugUtilitiesData.java2
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/util/ICUResourceBundleTest.java5
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/util/LocaleBuilderTest.java8
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/util/LocaleMatcherTest.java17
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/util/ULocaleTest.java286
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/util/data/localeDistanceTest.txt6
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/test/util/data/localeMatcherTest.txt89
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/tool/locale/LikelySubtagsBuilder.java11
-rw-r--r--android_icu4j/src/main/tests/android/icu/dev/tool/locale/LocaleDistanceBuilder.java9
-rw-r--r--android_icu4j/src/main/tests/android/icu/extratest/android_icu_version.properties2
-rw-r--r--android_icu4j/src/main/tests/android/icu/extratest/expected_transliteration_id_list.txt20
226 files changed, 4973 insertions, 1974 deletions
diff --git a/android_icu4j/src/main/java/android/icu/impl/CurrencyData.java b/android_icu4j/src/main/java/android/icu/impl/CurrencyData.java
index 608e178aa..ffb0a8921 100644
--- a/android_icu4j/src/main/java/android/icu/impl/CurrencyData.java
+++ b/android_icu4j/src/main/java/android/icu/impl/CurrencyData.java
@@ -181,6 +181,16 @@ public class CurrencyData {
}
@Override
+ public String getFormalSymbol(String isoCode) {
+ return fallback ? isoCode : null;
+ }
+
+ @Override
+ public String getVariantSymbol(String isoCode) {
+ return fallback ? isoCode : null;
+ }
+
+ @Override
public Map<String, String> symbolMap() {
return Collections.emptyMap();
}
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 a6b48eb4b..0b793f3a9 100644
--- a/android_icu4j/src/main/java/android/icu/impl/FormattedStringBuilder.java
+++ b/android_icu4j/src/main/java/android/icu/impl/FormattedStringBuilder.java
@@ -3,7 +3,6 @@
// License & terms of use: http://www.unicode.org/copyright.html#License
package android.icu.impl;
-import java.text.Format.Field;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
@@ -26,23 +25,29 @@ import android.icu.text.NumberFormat;
* @author sffc (Shane Carr)
* @hide Only a subset of ICU is exposed in Android
*/
-public class FormattedStringBuilder implements CharSequence {
+public class FormattedStringBuilder implements CharSequence, Appendable {
/** A constant, empty FormattedStringBuilder. Do NOT call mutative operations on this. */
public static final FormattedStringBuilder EMPTY = new FormattedStringBuilder();
char[] chars;
- Field[] fields;
+ Object[] fields;
int zero;
int length;
+ /** Number of characters from the end where .append() operations insert. */
+ int appendOffset = 0;
+
+ /** Field applied when Appendable methods are used. */
+ Object appendableField = null;
+
public FormattedStringBuilder() {
this(40);
}
public FormattedStringBuilder(int capacity) {
chars = new char[capacity];
- fields = new Field[capacity];
+ fields = new Object[capacity];
zero = capacity / 2;
length = 0;
}
@@ -74,7 +79,7 @@ public class FormattedStringBuilder implements CharSequence {
return chars[zero + index];
}
- public Field fieldAt(int index) {
+ public Object fieldAt(int index) {
assert index >= 0;
assert index < length;
return fields[zero + index];
@@ -108,11 +113,20 @@ public class FormattedStringBuilder implements CharSequence {
return this;
}
- public int appendChar16(char codeUnit, Field field) {
- return insertChar16(length, codeUnit, field);
+ /**
+ * Sets the index at which append operations insert. Defaults to the end.
+ *
+ * @param index The index at which append operations should insert.
+ */
+ public void setAppendIndex(int index) {
+ appendOffset = length - index;
+ }
+
+ public int appendChar16(char codeUnit, Object field) {
+ return insertChar16(length - appendOffset, codeUnit, field);
}
- public int insertChar16(int index, char codeUnit, Field field) {
+ public int insertChar16(int index, char codeUnit, Object field) {
int count = 1;
int position = prepareForInsert(index, count);
chars[position] = codeUnit;
@@ -125,8 +139,8 @@ public class FormattedStringBuilder implements CharSequence {
*
* @return The number of chars added: 1 if the code point is in the BMP, or 2 otherwise.
*/
- public int appendCodePoint(int codePoint, Field field) {
- return insertCodePoint(length, codePoint, field);
+ public int appendCodePoint(int codePoint, Object field) {
+ return insertCodePoint(length - appendOffset, codePoint, field);
}
/**
@@ -134,7 +148,7 @@ public class FormattedStringBuilder implements CharSequence {
*
* @return The number of chars added: 1 if the code point is in the BMP, or 2 otherwise.
*/
- public int insertCodePoint(int index, int codePoint, Field field) {
+ public int insertCodePoint(int index, int codePoint, Object field) {
int count = Character.charCount(codePoint);
int position = prepareForInsert(index, count);
Character.toChars(codePoint, chars, position);
@@ -149,8 +163,8 @@ public class FormattedStringBuilder implements CharSequence {
*
* @return The number of chars added, which is the length of CharSequence.
*/
- public int append(CharSequence sequence, Field field) {
- return insert(length, sequence, field);
+ public int append(CharSequence sequence, Object field) {
+ return insert(length - appendOffset, sequence, field);
}
/**
@@ -158,7 +172,7 @@ public class FormattedStringBuilder implements CharSequence {
*
* @return The number of chars added, which is the length of CharSequence.
*/
- public int insert(int index, CharSequence sequence, Field field) {
+ public int insert(int index, CharSequence sequence, Object field) {
if (sequence.length() == 0) {
// Nothing to insert.
return 0;
@@ -177,7 +191,7 @@ public class FormattedStringBuilder implements CharSequence {
*
* @return The number of chars added, which is the length of CharSequence.
*/
- public int insert(int index, CharSequence sequence, int start, int end, Field field) {
+ public int insert(int index, CharSequence sequence, int start, int end, Object field) {
int count = end - start;
int position = prepareForInsert(index, count);
for (int i = 0; i < count; i++) {
@@ -201,7 +215,7 @@ public class FormattedStringBuilder implements CharSequence {
CharSequence sequence,
int startOther,
int endOther,
- Field field) {
+ Object field) {
int thisLength = endThis - startThis;
int otherLength = endOther - startOther;
int count = otherLength - thisLength;
@@ -226,8 +240,8 @@ public class FormattedStringBuilder implements CharSequence {
*
* @return The number of chars added, which is the length of the char array.
*/
- public int append(char[] chars, Field[] fields) {
- return insert(length, chars, fields);
+ public int append(char[] chars, Object[] fields) {
+ return insert(length - appendOffset, chars, fields);
}
/**
@@ -236,7 +250,7 @@ public class FormattedStringBuilder implements CharSequence {
*
* @return The number of chars added, which is the length of the char array.
*/
- public int insert(int index, char[] chars, Field[] fields) {
+ public int insert(int index, char[] chars, Object[] fields) {
assert fields == null || chars.length == fields.length;
int count = chars.length;
if (count == 0)
@@ -255,7 +269,7 @@ public class FormattedStringBuilder implements CharSequence {
* @return The number of chars added, which is the length of the other {@link FormattedStringBuilder}.
*/
public int append(FormattedStringBuilder other) {
- return insert(length, other);
+ return insert(length - appendOffset, other);
}
/**
@@ -290,6 +304,9 @@ public class FormattedStringBuilder implements CharSequence {
* @return The position in the char array to insert the chars.
*/
private int prepareForInsert(int index, int count) {
+ if (index == -1) {
+ index = length;
+ }
if (index == 0 && zero - count >= 0) {
// Append to start
zero -= count;
@@ -311,13 +328,13 @@ public class FormattedStringBuilder implements CharSequence {
int oldCapacity = getCapacity();
int oldZero = zero;
char[] oldChars = chars;
- Field[] oldFields = fields;
+ Object[] oldFields = fields;
if (length + count > oldCapacity) {
int newCapacity = (length + count) * 2;
int newZero = newCapacity / 2 - (length + count) / 2;
char[] newChars = new char[newCapacity];
- Field[] newFields = new Field[newCapacity];
+ Object[] newFields = new Object[newCapacity];
// First copy the prefix and then the suffix, leaving room for the new chars that the
// caller wants to insert.
@@ -410,7 +427,7 @@ public class FormattedStringBuilder implements CharSequence {
return new String(chars, zero, length);
}
- private static final Map<Field, Character> fieldToDebugChar = new HashMap<>();
+ private static final Map<Object, Character> fieldToDebugChar = new HashMap<>();
static {
fieldToDebugChar.put(NumberFormat.Field.SIGN, '-');
@@ -461,17 +478,59 @@ public class FormattedStringBuilder implements CharSequence {
}
/** @return A new array containing the field values of this string builder. */
- public Field[] toFieldArray() {
+ public Object[] toFieldArray() {
return Arrays.copyOfRange(fields, zero, zero + length);
}
/**
+ * Call this method before using any of the Appendable overrides.
+ *
+ * @param field The field used when inserting strings.
+ */
+ public void setAppendableField(Object field) {
+ appendableField = field;
+ }
+
+ /**
+ * This method is provided for Java Appendable compatibility. In most cases, please use the append methods that take
+ * a Field parameter. If you do use this method, you must call {@link #setAppendableField} first.
+ */
+ @Override
+ public Appendable append(CharSequence csq) {
+ assert appendableField != null;
+ insert(length - appendOffset, csq, appendableField);
+ return this;
+ }
+
+ /**
+ * This method is provided for Java Appendable compatibility. In most cases, please use the append methods that take
+ * a Field parameter. If you do use this method, you must call {@link #setAppendableField} first.
+ */
+ @Override
+ public Appendable append(CharSequence csq, int start, int end) {
+ assert appendableField != null;
+ insert(length - appendOffset, csq, start, end, appendableField);
+ return this;
+ }
+
+ /**
+ * This method is provided for Java Appendable compatibility. In most cases, please use the append methods that take
+ * a Field parameter. If you do use this method, you must call {@link #setAppendableField} first.
+ */
+ @Override
+ public Appendable append(char c) {
+ assert appendableField != null;
+ insertChar16(length - appendOffset, c, appendableField);
+ return this;
+ }
+
+ /**
* @return Whether the contents and field values of this string builder are equal to the given chars
* and fields.
* @see #toCharArray
* @see #toFieldArray
*/
- public boolean contentEquals(char[] chars, Field[] fields) {
+ public boolean contentEquals(char[] chars, Object[] fields) {
if (chars.length != length)
return false;
if (fields.length != length)
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 5b316cda8..4cce71c60 100644
--- a/android_icu4j/src/main/java/android/icu/impl/FormattedValueStringBuilderImpl.java
+++ b/android_icu4j/src/main/java/android/icu/impl/FormattedValueStringBuilderImpl.java
@@ -9,7 +9,9 @@ import java.text.FieldPosition;
import java.text.Format.Field;
import android.icu.text.ConstrainedFieldPosition;
+import android.icu.text.ListFormatter;
import android.icu.text.NumberFormat;
+import android.icu.text.UFormat;
import android.icu.text.UnicodeSet;
/**
@@ -26,6 +28,34 @@ import android.icu.text.UnicodeSet;
*/
public class FormattedValueStringBuilderImpl {
+ /**
+ * Placeholder field used for calculating spans.
+ * Does not currently support nested fields beyond one level.
+ * @hide Only a subset of ICU is exposed in Android
+ */
+ public static class SpanFieldPlaceholder {
+ public UFormat.SpanField spanField;
+ public Field normalField;
+ public Object value;
+ }
+
+ /**
+ * Finds the index at which a span field begins.
+ *
+ * @param value The value of the span field to search for.
+ * @return The index, or -1 if not found.
+ */
+ public static int findSpan(FormattedStringBuilder self, Object value) {
+ for (int i = self.zero; i < self.zero + self.length; i++) {
+ if (!(self.fields[i] instanceof SpanFieldPlaceholder)) {
+ continue;
+ }
+ if (((SpanFieldPlaceholder) self.fields[i]).value.equals(value)) {
+ return i - self.zero;
+ }
+ }
+ return -1;
+ }
public static boolean nextFieldPosition(FormattedStringBuilder self, FieldPosition fp) {
java.text.Format.Field rawField = fp.getFieldAttribute();
@@ -80,7 +110,11 @@ public class FormattedValueStringBuilderImpl {
AttributedString as = new AttributedString(self.toString());
while (nextPosition(self, cfpos, numericField)) {
// Backwards compatibility: field value = field
- as.addAttribute(cfpos.getField(), cfpos.getField(), cfpos.getStart(), cfpos.getLimit());
+ Object value = cfpos.getFieldValue();
+ if (value == null) {
+ value = cfpos.getField();
+ }
+ as.addAttribute(cfpos.getField(), value, cfpos.getStart(), cfpos.getLimit());
}
return as.getIterator();
}
@@ -104,15 +138,21 @@ public class FormattedValueStringBuilderImpl {
*/
public static boolean nextPosition(FormattedStringBuilder self, ConstrainedFieldPosition cfpos, Field numericField) {
int fieldStart = -1;
- Field currField = null;
+ Object currField = null;
for (int i = self.zero + cfpos.getLimit(); i <= self.zero + self.length; i++) {
- Field _field = (i < self.zero + self.length) ? self.fields[i] : NullField.END;
+ 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 (currField != NumberFormat.Field.GROUPING_SEPARATOR) {
+ if (isTrimmable(currField)) {
end = trimBack(self, end);
}
if (end <= fieldStart) {
@@ -123,10 +163,10 @@ public class FormattedValueStringBuilderImpl {
continue;
}
int start = fieldStart;
- if (currField != NumberFormat.Field.GROUPING_SEPARATOR) {
+ if (isTrimmable(currField)) {
start = trimFront(self, start);
}
- cfpos.setState(currField, null, start, end);
+ cfpos.setState((Field) currField, null, start, end);
return true;
}
continue;
@@ -156,6 +196,15 @@ public class FormattedValueStringBuilderImpl {
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)) {
+ return true;
+ }
+ }
// Special case: skip over INTEGER; will be coalesced later.
if (_field == NumberFormat.Field.INTEGER) {
_field = null;
@@ -165,7 +214,16 @@ public class FormattedValueStringBuilderImpl {
continue;
}
// Case 3: check for field starting at this position
- if (cfpos.matchesField(_field, null)) {
+ // 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
+ else if (cfpos.matchesField((Field) _field, null)) {
fieldStart = i - self.zero;
currField = _field;
}
@@ -175,14 +233,19 @@ public class FormattedValueStringBuilderImpl {
return false;
}
- private static boolean isIntOrGroup(Field field) {
+ private static boolean isIntOrGroup(Object field) {
return field == NumberFormat.Field.INTEGER || field == NumberFormat.Field.GROUPING_SEPARATOR;
}
- private static boolean isNumericField(Field field) {
+ private static boolean isNumericField(Object field) {
return field == null || NumberFormat.Field.class.isAssignableFrom(field.getClass());
}
+ private static boolean isTrimmable(Object field) {
+ return field != NumberFormat.Field.GROUPING_SEPARATOR
+ && !(field instanceof ListFormatter.Field);
+ }
+
private static int trimBack(FormattedStringBuilder self, int limit) {
return StaticUnicodeSets.get(StaticUnicodeSets.Key.DEFAULT_IGNORABLES)
.spanBack(self, limit, UnicodeSet.SpanCondition.CONTAINED);
@@ -192,4 +255,19 @@ public class FormattedValueStringBuilderImpl {
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/ICUCurrencyDisplayInfoProvider.java b/android_icu4j/src/main/java/android/icu/impl/ICUCurrencyDisplayInfoProvider.java
index b0f8dfc86..78fddf879 100644
--- a/android_icu4j/src/main/java/android/icu/impl/ICUCurrencyDisplayInfoProvider.java
+++ b/android_icu4j/src/main/java/android/icu/impl/ICUCurrencyDisplayInfoProvider.java
@@ -79,10 +79,10 @@ public class ICUCurrencyDisplayInfoProvider implements CurrencyDisplayInfoProvid
private volatile FormattingData formattingDataCache = null;
/**
- * Single-item cache for getNarrowSymbol().
+ * Single-item cache for variant symbols.
* Holds data for only one currency. If another currency is requested, the old cache item is overwritten.
*/
- private volatile NarrowSymbol narrowSymbolCache = null;
+ private volatile VariantSymbol variantSymbolCache = null;
/**
* Single-item cache for getPluralName().
@@ -120,11 +120,15 @@ public class ICUCurrencyDisplayInfoProvider implements CurrencyDisplayInfoProvid
FormattingData(String isoCode) { this.isoCode = isoCode; }
}
- static class NarrowSymbol {
+ static class VariantSymbol {
final String isoCode;
- String narrowSymbol = null;
+ final String variant;
+ String symbol = null;
- NarrowSymbol(String isoCode) { this.isoCode = isoCode; }
+ VariantSymbol(String isoCode, String variant) {
+ this.isoCode = isoCode;
+ this.variant = variant;
+ }
}
static class ParsingData {
@@ -171,13 +175,35 @@ public class ICUCurrencyDisplayInfoProvider implements CurrencyDisplayInfoProvid
@Override
public String getNarrowSymbol(String isoCode) {
- NarrowSymbol narrowSymbol = fetchNarrowSymbol(isoCode);
+ VariantSymbol variantSymbol = fetchVariantSymbol(isoCode, "narrow");
- // Fall back to ISO Code
- if (narrowSymbol.narrowSymbol == null && fallback) {
+ // Fall back to regular symbol
+ if (variantSymbol.symbol == null && fallback) {
+ return getSymbol(isoCode);
+ }
+ return variantSymbol.symbol;
+ }
+
+ @Override
+ public String getFormalSymbol(String isoCode) {
+ VariantSymbol variantSymbol = fetchVariantSymbol(isoCode, "formal");
+
+ // Fall back to regular symbol
+ if (variantSymbol.symbol == null && fallback) {
+ return getSymbol(isoCode);
+ }
+ return variantSymbol.symbol;
+ }
+
+ @Override
+ public String getVariantSymbol(String isoCode) {
+ VariantSymbol variantSymbol = fetchVariantSymbol(isoCode, "variant");
+
+ // Fall back to regular symbol
+ if (variantSymbol.symbol == null && fallback) {
return getSymbol(isoCode);
}
- return narrowSymbol.narrowSymbol;
+ return variantSymbol.symbol;
}
@Override
@@ -260,14 +286,14 @@ public class ICUCurrencyDisplayInfoProvider implements CurrencyDisplayInfoProvid
return result;
}
- NarrowSymbol fetchNarrowSymbol(String isoCode) {
- NarrowSymbol result = narrowSymbolCache;
- if (result == null || !result.isoCode.equals(isoCode)) {
- result = new NarrowSymbol(isoCode);
- CurrencySink sink = new CurrencySink(!fallback, CurrencySink.EntrypointTable.CURRENCY_NARROW);
- sink.narrowSymbol = result;
- rb.getAllItemsWithFallbackNoFail("Currencies%narrow/" + isoCode, sink);
- narrowSymbolCache = result;
+ VariantSymbol fetchVariantSymbol(String isoCode, String variant) {
+ VariantSymbol result = variantSymbolCache;
+ if (result == null || !result.isoCode.equals(isoCode) || !result.variant.equals(variant)) {
+ result = new VariantSymbol(isoCode, variant);
+ CurrencySink sink = new CurrencySink(!fallback, CurrencySink.EntrypointTable.CURRENCY_VARIANT);
+ sink.variantSymbol = result;
+ rb.getAllItemsWithFallbackNoFail("Currencies%" + variant + "/" + isoCode, sink);
+ variantSymbolCache = result;
}
return result;
}
@@ -335,7 +361,7 @@ public class ICUCurrencyDisplayInfoProvider implements CurrencyDisplayInfoProvid
ParsingData parsingData = null;
Map<String, String> unitPatterns = null;
CurrencySpacingInfo spacingInfo = null;
- NarrowSymbol narrowSymbol = null;
+ VariantSymbol variantSymbol = null;
enum EntrypointTable {
// For Parsing:
@@ -344,7 +370,7 @@ public class ICUCurrencyDisplayInfoProvider implements CurrencyDisplayInfoProvid
// For Formatting:
CURRENCIES,
CURRENCY_PLURALS,
- CURRENCY_NARROW,
+ CURRENCY_VARIANT,
CURRENCY_SPACING,
CURRENCY_UNIT_PATTERNS
}
@@ -375,8 +401,8 @@ public class ICUCurrencyDisplayInfoProvider implements CurrencyDisplayInfoProvid
case CURRENCY_PLURALS:
consumeCurrencyPluralsEntry(key, value);
break;
- case CURRENCY_NARROW:
- consumeCurrenciesNarrowEntry(key, value);
+ case CURRENCY_VARIANT:
+ consumeCurrenciesVariantEntry(key, value);
break;
case CURRENCY_SPACING:
consumeCurrencySpacingTable(key, value);
@@ -479,11 +505,11 @@ public class ICUCurrencyDisplayInfoProvider implements CurrencyDisplayInfoProvid
* ...
* }
*/
- void consumeCurrenciesNarrowEntry(UResource.Key key, UResource.Value value) {
- assert narrowSymbol != null;
+ void consumeCurrenciesVariantEntry(UResource.Key key, UResource.Value value) {
+ assert variantSymbol != null;
// No extra structure to traverse.
- if (narrowSymbol.narrowSymbol == null) {
- narrowSymbol.narrowSymbol = value.getString();
+ if (variantSymbol.symbol == null) {
+ variantSymbol.symbol = value.getString();
}
}
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 aa79df43e..850da1afd 100644
--- a/android_icu4j/src/main/java/android/icu/impl/ICUService.java
+++ b/android_icu4j/src/main/java/android/icu/impl/ICUService.java
@@ -597,15 +597,13 @@ public class ICUService extends ICUNotifier {
Factory f = lIter.previous();
f.updateVisibleIDs(mutableMap);
}
- Map<String, Factory> result = Collections.unmodifiableMap(mutableMap);
- this.idcache = result;
- return result;
+ this.idcache = Collections.unmodifiableMap(mutableMap);
} finally {
factoryLock.releaseRead();
}
}
- return idcache;
}
+ return idcache;
}
private Map<String, Factory> idcache;
diff --git a/android_icu4j/src/main/java/android/icu/impl/PluralRulesLoader.java b/android_icu4j/src/main/java/android/icu/impl/PluralRulesLoader.java
index 8e270fdf6..41401d4cb 100644
--- a/android_icu4j/src/main/java/android/icu/impl/PluralRulesLoader.java
+++ b/android_icu4j/src/main/java/android/icu/impl/PluralRulesLoader.java
@@ -13,6 +13,7 @@ import java.text.ParseException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
+import java.util.LinkedHashSet;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.Set;
@@ -49,12 +50,11 @@ public class PluralRulesLoader extends PluralRules.Factory {
*/
public ULocale[] getAvailableULocales() {
Set<String> keys = getLocaleIdToRulesIdMap(PluralType.CARDINAL).keySet();
- ULocale[] locales = new ULocale[keys.size()];
- int n = 0;
+ Set<ULocale> locales = new LinkedHashSet<ULocale>(keys.size());
for (Iterator<String> iter = keys.iterator(); iter.hasNext();) {
- locales[n++] = ULocale.createCanonical(iter.next());
+ locales.add(ULocale.createCanonical(iter.next()));
}
- return locales;
+ return locales.toArray(new ULocale[0]);
}
/**
@@ -501,4 +501,4 @@ public class PluralRulesLoader extends PluralRules.Factory {
// now make whole thing immutable
localeIdToPluralRanges = Collections.unmodifiableMap(tempLocaleIdToPluralRanges);
}
-} \ No newline at end of file
+}
diff --git a/android_icu4j/src/main/java/android/icu/impl/SimpleFormatterImpl.java b/android_icu4j/src/main/java/android/icu/impl/SimpleFormatterImpl.java
index ce8fe0fea..f25a0d980 100644
--- a/android_icu4j/src/main/java/android/icu/impl/SimpleFormatterImpl.java
+++ b/android_icu4j/src/main/java/android/icu/impl/SimpleFormatterImpl.java
@@ -9,6 +9,11 @@
*/
package android.icu.impl;
+import java.io.IOException;
+import java.text.Format;
+
+import android.icu.util.ICUUncheckedIOException;
+
/**
* Formats simple patterns like "{1} was born in {0}".
* Internal version of {@link android.icu.text.SimpleFormatter}
@@ -306,18 +311,125 @@ public final class SimpleFormatterImpl {
return sb.toString();
}
- /** Poor-man's iterator interface. See ICU-20406.
- * @hide Only a subset of ICU is exposed in Android*/
- public static class Int64Iterator {
+ /**
+ * Returns the length of the pattern text with none of the arguments.
+ * @param compiledPattern Compiled form of a pattern string.
+ * @param codePoints true to count code points; false to count code units.
+ * @return The number of code points or code units.
+ */
+ public static int getLength(String compiledPattern, boolean codePoints) {
+ int result = 0;
+ for (int i = 1; i < compiledPattern.length();) {
+ int segmentLength = compiledPattern.charAt(i++) - ARG_NUM_LIMIT;
+ if (segmentLength > 0) {
+ int limit = i + segmentLength;
+ if (codePoints) {
+ result += Character.codePointCount(compiledPattern, i, limit);
+ } else {
+ result += (limit - i);
+ }
+ i = limit;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Returns the length in code units of the pattern text up until the first argument.
+ * @param compiledPattern Compiled form of a pattern string.
+ * @return The number of code units.
+ */
+ public static int getPrefixLength(String compiledPattern) {
+ if (compiledPattern.length() == 1) {
+ return 0;
+ } else if (compiledPattern.charAt(0) == 0) {
+ return compiledPattern.length() - 2;
+ } else if (compiledPattern.charAt(1) <= ARG_NUM_LIMIT) {
+ return 0;
+ } else {
+ return compiledPattern.charAt(1) - ARG_NUM_LIMIT;
+ }
+ }
+
+ /**
+ * Special case for using FormattedStringBuilder with patterns with 0 or 1 argument.
+ *
+ * With 1 argument, treat the current contents of the FormattedStringBuilder between
+ * start and end as the argument {0}. Insert the extra strings from compiledPattern
+ * to surround the argument in the output.
+ *
+ * With 0 arguments, overwrite the entire contents of the FormattedStringBuilder
+ * between start and end.
+ *
+ * @param compiledPattern Compiled form of a pattern string.
+ * @param field Field to use when adding chars to the output.
+ * @param start The start index of the argument already in the output string.
+ * @param end The end index of the argument already in the output string.
+ * @param output Destination for formatted output.
+ * @return Net number of characters added to the formatted string.
+ */
+ public static int formatPrefixSuffix(
+ String compiledPattern,
+ Format.Field field,
+ int start,
+ int end,
+ FormattedStringBuilder output) {
+ int argLimit = getArgumentLimit(compiledPattern);
+ if (argLimit == 0) {
+ // No arguments in compiled pattern; overwrite the entire segment with our string.
+ return output.splice(start, end, compiledPattern, 2, compiledPattern.length(), field);
+ } else {
+ assert argLimit == 1;
+ int suffixOffset;
+ int length = 0;
+ if (compiledPattern.charAt(1) != '\u0000') {
+ int prefixLength = compiledPattern.charAt(1) - ARG_NUM_LIMIT;
+ length = output.insert(start, compiledPattern, 2, 2 + prefixLength, field);
+ suffixOffset = 3 + prefixLength;
+ } else {
+ suffixOffset = 2;
+ }
+ if (suffixOffset < compiledPattern.length()) {
+ int suffixLength = compiledPattern.charAt(suffixOffset) - ARG_NUM_LIMIT;
+ length += output.insert(end + length, compiledPattern, 1 + suffixOffset,
+ 1 + suffixOffset + suffixLength, field);
+ }
+ return length;
+ }
+ }
+
+ /** Internal iterator interface for maximum efficiency.
+ *
+ * Usage boilerplate:
+ *
+ * <pre>
+ * long state = 0;
+ * while (true) {
+ * state = IterInternal.step(state, compiledPattern, output);
+ * if (state == IterInternal.DONE) {
+ * break;
+ * }
+ * int argIndex = IterInternal.getArgIndex(state);
+ * // Append the string corresponding to argIndex to output
+ * }
+ * </pre>
+ * @hide Only a subset of ICU is exposed in Android
+ *
+ */
+ public static class IterInternal {
public static final long DONE = -1;
- public static long step(CharSequence compiledPattern, long state, StringBuffer output) {
+ public static long step(long state, CharSequence compiledPattern, Appendable output) {
int i = (int) (state >>> 32);
assert i < compiledPattern.length();
i++;
while (i < compiledPattern.length() && compiledPattern.charAt(i) > ARG_NUM_LIMIT) {
int limit = i + compiledPattern.charAt(i) + 1 - ARG_NUM_LIMIT;
- output.append(compiledPattern, i + 1, limit);
+ try {
+ output.append(compiledPattern, i + 1, limit);
+ } catch (IOException e) {
+ throw new ICUUncheckedIOException(e);
+ }
i = limit;
}
if (i == compiledPattern.length()) {
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 41aa1349e..28cf0556a 100644
--- a/android_icu4j/src/main/java/android/icu/impl/StringSegment.java
+++ b/android_icu4j/src/main/java/android/icu/impl/StringSegment.java
@@ -223,8 +223,14 @@ public class StringSegment implements CharSequence {
return Utility.charSequenceHashCode(this);
}
+ /** Returns a string representation useful for debugging. */
@Override
public String toString() {
return str.substring(0, start) + "[" + str.substring(start, end) + "]" + str.substring(end);
}
+
+ /** Returns a String that is equivalent to the CharSequence representation. */
+ public String asString() {
+ return str.substring(start, end);
+ }
}
diff --git a/android_icu4j/src/main/java/android/icu/impl/locale/InternalLocaleBuilder.java b/android_icu4j/src/main/java/android/icu/impl/locale/InternalLocaleBuilder.java
index d6f1b1a49..9eb165751 100644
--- a/android_icu4j/src/main/java/android/icu/impl/locale/InternalLocaleBuilder.java
+++ b/android_icu4j/src/main/java/android/icu/impl/locale/InternalLocaleBuilder.java
@@ -10,6 +10,7 @@
package android.icu.impl.locale;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -330,7 +331,8 @@ public final class InternalLocaleBuilder {
_script = langtag.getScript();
_region = langtag.getRegion();
- List<String> bcpVariants = langtag.getVariants();
+ ArrayList<String> bcpVariants = new ArrayList<String>(langtag.getVariants());
+ Collections.sort(bcpVariants);
if (bcpVariants.size() > 0) {
StringBuilder var = new StringBuilder(bcpVariants.get(0));
for (int i = 1; i < bcpVariants.size(); i++) {
diff --git a/android_icu4j/src/main/java/android/icu/impl/locale/LSR.java b/android_icu4j/src/main/java/android/icu/impl/locale/LSR.java
index d7db9a0f6..d49440934 100644
--- a/android_icu4j/src/main/java/android/icu/impl/locale/LSR.java
+++ b/android_icu4j/src/main/java/android/icu/impl/locale/LSR.java
@@ -11,6 +11,13 @@ import java.util.Objects;
public final class LSR {
public static final int REGION_INDEX_LIMIT = 1001 + 26 * 26;
+ public static final int EXPLICIT_LSR = 7;
+ public static final int EXPLICIT_LANGUAGE = 4;
+ public static final int EXPLICIT_SCRIPT = 2;
+ public static final int EXPLICIT_REGION = 1;
+ public static final int IMPLICIT_LSR = 0;
+ public static final int DONT_CARE_FLAGS = 0;
+
public static final boolean DEBUG_OUTPUT = false;
public final String language;
@@ -18,12 +25,14 @@ public final class LSR {
public final String region;
/** Index for region, negative if ill-formed. @see indexForRegion */
final int regionIndex;
+ public final int flags;
- public LSR(String language, String script, String region) {
+ public LSR(String language, String script, String region, int flags) {
this.language = language;
this.script = script;
this.region = region;
regionIndex = indexForRegion(region);
+ this.flags = flags;
}
/**
@@ -61,6 +70,13 @@ public final class LSR {
}
return result.toString();
}
+
+ public boolean isEquivalentTo(LSR other) {
+ return language.equals(other.language)
+ && script.equals(other.script)
+ && region.equals(other.region);
+ }
+
@Override
public boolean equals(Object obj) {
LSR other;
@@ -69,10 +85,12 @@ public final class LSR {
&& obj.getClass() == this.getClass()
&& language.equals((other = (LSR) obj).language)
&& script.equals(other.script)
- && region.equals(other.region));
+ && region.equals(other.region)
+ && flags == other.flags);
}
+
@Override
public int hashCode() {
- return Objects.hash(language, script, region);
+ return Objects.hash(language, script, region, flags);
}
}
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 469eceee8..f1b1a9cf1 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
@@ -701,7 +701,21 @@ public class LanguageTag {
}
public static String canonicalizeExtension(String s) {
- return AsciiUtil.toLowerString(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
+ }
+ return s;
}
public static String canonicalizeExtensionSingleton(String s) {
diff --git a/android_icu4j/src/main/java/android/icu/impl/locale/LocaleDistance.java b/android_icu4j/src/main/java/android/icu/impl/locale/LocaleDistance.java
index 2cd8eb9b6..25da1d8c3 100644
--- a/android_icu4j/src/main/java/android/icu/impl/locale/LocaleDistance.java
+++ b/android_icu4j/src/main/java/android/icu/impl/locale/LocaleDistance.java
@@ -6,7 +6,7 @@ package android.icu.impl.locale;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Collections;
-import java.util.HashSet;
+import java.util.LinkedHashSet;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.Set;
@@ -16,6 +16,7 @@ import android.icu.impl.ICUData;
import android.icu.impl.ICUResourceBundle;
import android.icu.impl.UResource;
import android.icu.util.BytesTrie;
+import android.icu.util.LocaleMatcher;
import android.icu.util.LocaleMatcher.FavorSubtag;
import android.icu.util.ULocale;
@@ -36,6 +37,17 @@ public class LocaleDistance {
private static final int DISTANCE_IS_FINAL = 0x100;
private static final int DISTANCE_IS_FINAL_OR_SKIP_SCRIPT =
DISTANCE_IS_FINAL | DISTANCE_SKIP_SCRIPT;
+
+ // The distance is shifted left to gain some fraction bits.
+ private static final int DISTANCE_SHIFT = 3;
+ private static final int DISTANCE_FRACTION_MASK = 7;
+ // 7 bits for 0..100
+ private static final int DISTANCE_INT_SHIFT = 7;
+ private static final int INDEX_SHIFT = DISTANCE_INT_SHIFT + DISTANCE_SHIFT;
+ private static final int DISTANCE_MASK = 0x3ff;
+ // vate static final int MAX_INDEX = 0x1fffff; // avoids sign bit
+ private static final int INDEX_NEG_1 = 0xfffffc00;
+
// Indexes into array of distances.
public static final int IX_DEF_LANG_DISTANCE = 0;
public static final int IX_DEF_SCRIPT_DISTANCE = 1;
@@ -69,6 +81,28 @@ public class LocaleDistance {
private final int minRegionDistance;
private final int defaultDemotionPerDesiredLocale;
+ public static final int shiftDistance(int distance) {
+ return distance << DISTANCE_SHIFT;
+ }
+
+ public static final int getShiftedDistance(int indexAndDistance) {
+ return indexAndDistance & DISTANCE_MASK;
+ }
+
+ public static final double getDistanceDouble(int indexAndDistance) {
+ double shiftedDistance = getShiftedDistance(indexAndDistance);
+ return shiftedDistance / (1 << DISTANCE_SHIFT);
+ }
+
+ private static final int getDistanceFloor(int indexAndDistance) {
+ return (indexAndDistance & DISTANCE_MASK) >> DISTANCE_SHIFT;
+ }
+
+ public static final int getIndex(int indexAndDistance) {
+ assert indexAndDistance >= 0;
+ return indexAndDistance >> INDEX_SHIFT;
+ }
+
// VisibleForTesting
/**
* @hide Only a subset of ICU is exposed in Android
@@ -124,9 +158,11 @@ public class LocaleDistance {
Set<LSR> paradigmLSRs;
if (matchTable.findValue("paradigms", value)) {
String[] paradigms = value.getStringArray();
- paradigmLSRs = new HashSet<>(paradigms.length / 3);
+ // LinkedHashSet for stable order; otherwise a unit test is flaky.
+ paradigmLSRs = new LinkedHashSet<>(paradigms.length / 3);
for (int i = 0; i < paradigms.length; i += 3) {
- paradigmLSRs.add(new LSR(paradigms[i], paradigms[i + 1], paradigms[i + 2]));
+ paradigmLSRs.add(new LSR(paradigms[i], paradigms[i + 1], paradigms[i + 2],
+ LSR.DONT_CARE_FLAGS));
}
} else {
paradigmLSRs = Collections.emptySet();
@@ -144,7 +180,7 @@ public class LocaleDistance {
@Override
public boolean equals(Object other) {
if (this == other) { return true; }
- if (!getClass().equals(other.getClass())) { return false; }
+ if (other == null || !getClass().equals(other.getClass())) { return false; }
Data od = (Data)other;
return Arrays.equals(trie, od.trie) &&
Arrays.equals(regionToPartitionsIndex, od.regionToPartitionsIndex) &&
@@ -152,6 +188,11 @@ public class LocaleDistance {
paradigmLSRs.equals(od.paradigmLSRs) &&
Arrays.equals(distances, od.distances);
}
+
+ @Override
+ public int hashCode() { // unused; silence ErrorProne
+ return 1;
+ }
}
// VisibleForTesting
@@ -173,10 +214,11 @@ public class LocaleDistance {
// a mere region difference for one desired locale
// is as good as a perfect match for the next following desired locale.
// As of CLDR 36, we have <languageMatch desired="en_*_*" supported="en_*_*" distance="5"/>.
- LSR en = new LSR("en", "Latn", "US");
- LSR enGB = new LSR("en", "Latn", "GB");
- defaultDemotionPerDesiredLocale = getBestIndexAndDistance(en, new LSR[] { enGB },
- 50, FavorSubtag.LANGUAGE) & 0xff;
+ LSR en = new LSR("en", "Latn", "US", LSR.EXPLICIT_LSR);
+ LSR enGB = new LSR("en", "Latn", "GB", LSR.EXPLICIT_LSR);
+ int indexAndDistance = getBestIndexAndDistance(en, new LSR[] { enGB }, 1,
+ shiftDistance(50), FavorSubtag.LANGUAGE, LocaleMatcher.Direction.WITH_ONE_WAY);
+ defaultDemotionPerDesiredLocale = getDistanceFloor(indexAndDistance);
if (DEBUG_OUTPUT) {
System.out.println("*** locale distance");
@@ -192,29 +234,32 @@ public class LocaleDistance {
int threshold, FavorSubtag favorSubtag) {
LSR supportedLSR = XLikelySubtags.INSTANCE.makeMaximizedLsrFrom(supported);
LSR desiredLSR = XLikelySubtags.INSTANCE.makeMaximizedLsrFrom(desired);
- return getBestIndexAndDistance(desiredLSR, new LSR[] { supportedLSR },
- threshold, favorSubtag) & 0xff;
+ int indexAndDistance = getBestIndexAndDistance(desiredLSR, new LSR[] { supportedLSR }, 1,
+ shiftDistance(threshold), favorSubtag, LocaleMatcher.Direction.WITH_ONE_WAY);
+ return getDistanceFloor(indexAndDistance);
}
/**
* Finds the supported LSR with the smallest distance from the desired one.
* Equivalent LSR subtags must be normalized into a canonical form.
*
- * <p>Returns the index of the lowest-distance supported LSR in bits 31..8
+ * <p>Returns the index of the lowest-distance supported LSR in the high bits
* (negative if none has a distance below the threshold),
- * and its distance (0..ABOVE_THRESHOLD) in bits 7..0.
+ * and its distance (0..ABOVE_THRESHOLD) in the low bits.
*/
- public int getBestIndexAndDistance(LSR desired, LSR[] supportedLSRs,
- int threshold, FavorSubtag favorSubtag) {
+ public int getBestIndexAndDistance(LSR desired, LSR[] supportedLSRs, int supportedLSRsLength,
+ int shiftedThreshold, FavorSubtag favorSubtag, LocaleMatcher.Direction direction) {
BytesTrie iter = new BytesTrie(trie);
// Look up the desired language only once for all supported LSRs.
// Its "distance" is either a match point value of 0, or a non-match negative value.
// Note: The data builder verifies that there are no <*, supported> or <desired, *> rules.
int desLangDistance = trieNext(iter, desired.language, false);
- long desLangState = desLangDistance >= 0 && supportedLSRs.length > 1 ? iter.getState64() : 0;
+ long desLangState = desLangDistance >= 0 && supportedLSRsLength > 1 ? iter.getState64() : 0;
// Index of the supported LSR with the lowest distance.
int bestIndex = -1;
- for (int slIndex = 0; slIndex < supportedLSRs.length; ++slIndex) {
+ // Cached lookup info from XLikelySubtags.compareLikely().
+ int bestLikelyInfo = -1;
+ for (int slIndex = 0; slIndex < supportedLSRsLength; ++slIndex) {
LSR supported = supportedLSRs[slIndex];
boolean star = false;
int distance = desLangDistance;
@@ -243,6 +288,11 @@ public class LocaleDistance {
star = true;
}
assert 0 <= distance && distance <= 100;
+ // Round up the shifted threshold (if fraction bits are not 0)
+ // for comparison with un-shifted distances until we need fraction bits.
+ // (If we simply shifted non-zero fraction bits away, then we might ignore a language
+ // when it's really still a micro distance below the threshold.)
+ int roundedThreshold = (shiftedThreshold + DISTANCE_FRACTION_MASK) >> DISTANCE_SHIFT;
// We implement "favor subtag" by reducing the language subtag distance
// (unscientifically reducing it to a quarter of the normal value),
// so that the script distance is relatively more important.
@@ -251,7 +301,9 @@ public class LocaleDistance {
if (favorSubtag == FavorSubtag.SCRIPT) {
distance >>= 2;
}
- if (distance >= threshold) {
+ // Let distance == roundedThreshold pass until the tie-breaker logic
+ // at the end of the loop.
+ if (distance > roundedThreshold) {
continue;
}
@@ -269,7 +321,7 @@ public class LocaleDistance {
scriptDistance &= ~DISTANCE_IS_FINAL;
}
distance += scriptDistance;
- if (distance >= threshold) {
+ if (distance > roundedThreshold) {
continue;
}
@@ -278,8 +330,8 @@ public class LocaleDistance {
} else if (star || (flags & DISTANCE_IS_FINAL) != 0) {
distance += defaultRegionDistance;
} else {
- int remainingThreshold = threshold - distance;
- if (minRegionDistance >= remainingThreshold) {
+ int remainingThreshold = roundedThreshold - distance;
+ if (minRegionDistance > remainingThreshold) {
continue;
}
@@ -294,15 +346,58 @@ public class LocaleDistance {
partitionsForRegion(supported),
remainingThreshold);
}
- if (distance < threshold) {
- if (distance == 0) {
- return slIndex << 8;
+ int shiftedDistance = shiftDistance(distance);
+ if (shiftedDistance == 0) {
+ // Distinguish between equivalent but originally unequal locales via an
+ // additional micro distance.
+ shiftedDistance |= (desired.flags ^ supported.flags);
+ if (shiftedDistance < shiftedThreshold) {
+ if (direction != LocaleMatcher.Direction.ONLY_TWO_WAY ||
+ // Is there also a match when we swap desired/supported?
+ isMatch(supported, desired, shiftedThreshold, favorSubtag)) {
+ if (shiftedDistance == 0) {
+ return slIndex << INDEX_SHIFT;
+ }
+ bestIndex = slIndex;
+ shiftedThreshold = shiftedDistance;
+ bestLikelyInfo = -1;
+ }
+ }
+ } else {
+ if (shiftedDistance < shiftedThreshold) {
+ if (direction != LocaleMatcher.Direction.ONLY_TWO_WAY ||
+ // Is there also a match when we swap desired/supported?
+ isMatch(supported, desired, shiftedThreshold, favorSubtag)) {
+ bestIndex = slIndex;
+ shiftedThreshold = shiftedDistance;
+ bestLikelyInfo = -1;
+ }
+ } else if (shiftedDistance == shiftedThreshold && bestIndex >= 0) {
+ if (direction != LocaleMatcher.Direction.ONLY_TWO_WAY ||
+ // Is there also a match when we swap desired/supported?
+ isMatch(supported, desired, shiftedThreshold, favorSubtag)) {
+ bestLikelyInfo = XLikelySubtags.INSTANCE.compareLikely(
+ supported, supportedLSRs[bestIndex], bestLikelyInfo);
+ if ((bestLikelyInfo & 1) != 0) {
+ // This supported locale matches as well as the previous best match,
+ // and neither matches perfectly,
+ // but this one is "more likely" (has more-default subtags).
+ bestIndex = slIndex;
+ }
+ }
}
- bestIndex = slIndex;
- threshold = distance;
}
}
- return bestIndex >= 0 ? (bestIndex << 8) | threshold : 0xffffff00 | ABOVE_THRESHOLD;
+ return bestIndex >= 0 ?
+ (bestIndex << INDEX_SHIFT) | shiftedThreshold :
+ INDEX_NEG_1 | shiftDistance(ABOVE_THRESHOLD);
+ }
+
+ private boolean isMatch(LSR desired, LSR supported,
+ int shiftedThreshold, FavorSubtag favorSubtag) {
+ return getBestIndexAndDistance(
+ desired, new LSR[] { supported }, 1,
+ shiftedThreshold, favorSubtag, null) >= 0;
}
private static final int getDesSuppScriptDistance(BytesTrie iter, long startState,
@@ -364,7 +459,7 @@ public class LocaleDistance {
d = getFallbackRegionDistance(iter, startState);
star = true;
}
- if (d >= threshold) {
+ if (d > threshold) {
return d;
} else if (regionDistance < d) {
regionDistance = d;
@@ -377,7 +472,7 @@ public class LocaleDistance {
}
} else if (!star) {
int d = getFallbackRegionDistance(iter, startState);
- if (d >= threshold) {
+ if (d > threshold) {
return d;
} else if (regionDistance < d) {
regionDistance = d;
@@ -444,7 +539,17 @@ public class LocaleDistance {
}
public boolean isParadigmLSR(LSR lsr) {
- return paradigmLSRs.contains(lsr);
+ // Linear search for a very short list (length 6 as of 2019),
+ // because we look for equivalence not equality, and
+ // HashSet does not support customizing equality.
+ // If there are many paradigm LSRs we should revisit this.
+ assert paradigmLSRs.size() <= 15;
+ for (LSR plsr : paradigmLSRs) {
+ if (lsr.isEquivalentTo(plsr)) {
+ return true;
+ }
+ }
+ return false;
}
// VisibleForTesting
@@ -460,9 +565,6 @@ public class LocaleDistance {
return defaultDemotionPerDesiredLocale;
}
- // TODO: When we build data offline,
- // write test code to compare the loaded table with the builder output.
- // Fail if different, with instructions for how to update the data file.
// VisibleForTesting
public Map<String, Integer> testOnlyGetDistanceTable() {
Map<String, Integer> map = new TreeMap<>();
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 201958d0a..0294a36ca 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
@@ -94,7 +94,8 @@ public final class XLikelySubtags {
String[] lsrSubtags = getValue(likelyTable, "lsrs", value).getStringArray();
LSR[] lsrs = new LSR[lsrSubtags.length / 3];
for (int i = 0, j = 0; i < lsrSubtags.length; i += 3, ++j) {
- lsrs[j] = new LSR(lsrSubtags[i], lsrSubtags[i + 1], lsrSubtags[i + 2]);
+ lsrs[j] = new LSR(lsrSubtags[i], lsrSubtags[i + 1], lsrSubtags[i + 2],
+ LSR.IMPLICIT_LSR);
}
return new Data(languageAliases, regionAliases, trie, lsrs);
@@ -103,7 +104,7 @@ public final class XLikelySubtags {
@Override
public boolean equals(Object other) {
if (this == other) { return true; }
- if (!getClass().equals(other.getClass())) { return false; }
+ if (other == null || !getClass().equals(other.getClass())) { return false; }
Data od = (Data)other;
return
languageAliases.equals(od.languageAliases) &&
@@ -111,6 +112,11 @@ public final class XLikelySubtags {
Arrays.equals(trie, od.trie) &&
Arrays.equals(lsrs, od.lsrs);
}
+
+ @Override
+ public int hashCode() { // unused; silence ErrorProne
+ return 1;
+ }
}
// VisibleForTesting
@@ -192,7 +198,7 @@ public final class XLikelySubtags {
String tag = locale.toLanguageTag();
assert tag.startsWith("x-");
// Private use language tag x-subtag-subtag...
- return new LSR(tag, "", "");
+ return new LSR(tag, "", "", LSR.EXPLICIT_LSR);
}
return makeMaximizedLsr(locale.getLanguage(), locale.getScript(), locale.getCountry(),
locale.getVariant());
@@ -202,7 +208,7 @@ public final class XLikelySubtags {
String tag = locale.toLanguageTag();
if (tag.startsWith("x-")) {
// Private use language tag x-subtag-subtag...
- return new LSR(tag, "", "");
+ return new LSR(tag, "", "", LSR.EXPLICIT_LSR);
}
return makeMaximizedLsr(locale.getLanguage(), locale.getScript(), locale.getCountry(),
locale.getVariant());
@@ -216,29 +222,34 @@ public final class XLikelySubtags {
switch (region.charAt(1)) {
case 'A':
return new LSR(PSEUDO_ACCENTS_PREFIX + language,
- PSEUDO_ACCENTS_PREFIX + script, region);
+ PSEUDO_ACCENTS_PREFIX + script, region, LSR.EXPLICIT_LSR);
case 'B':
return new LSR(PSEUDO_BIDI_PREFIX + language,
- PSEUDO_BIDI_PREFIX + script, region);
+ PSEUDO_BIDI_PREFIX + script, region, LSR.EXPLICIT_LSR);
case 'C':
return new LSR(PSEUDO_CRACKED_PREFIX + language,
- PSEUDO_CRACKED_PREFIX + script, region);
+ PSEUDO_CRACKED_PREFIX + script, region, LSR.EXPLICIT_LSR);
default: // normal locale
break;
}
}
if (variant.startsWith("PS")) {
+ int lsrFlags = region.isEmpty() ?
+ LSR.EXPLICIT_LANGUAGE | LSR.EXPLICIT_SCRIPT : LSR.EXPLICIT_LSR;
switch (variant) {
case "PSACCENT":
return new LSR(PSEUDO_ACCENTS_PREFIX + language,
- PSEUDO_ACCENTS_PREFIX + script, region.isEmpty() ? "XA" : region);
+ PSEUDO_ACCENTS_PREFIX + script,
+ region.isEmpty() ? "XA" : region, lsrFlags);
case "PSBIDI":
return new LSR(PSEUDO_BIDI_PREFIX + language,
- PSEUDO_BIDI_PREFIX + script, region.isEmpty() ? "XB" : region);
+ PSEUDO_BIDI_PREFIX + script,
+ region.isEmpty() ? "XB" : region, lsrFlags);
case "PSCRACK":
return new LSR(PSEUDO_CRACKED_PREFIX + language,
- PSEUDO_CRACKED_PREFIX + script, region.isEmpty() ? "XC" : region);
+ PSEUDO_CRACKED_PREFIX + script,
+ region.isEmpty() ? "XC" : region, lsrFlags);
default: // normal locale
break;
}
@@ -264,7 +275,7 @@ public final class XLikelySubtags {
region = "";
}
if (!script.isEmpty() && !region.isEmpty() && !language.isEmpty()) {
- return new LSR(language, script, region); // already maximized
+ return new LSR(language, script, region, LSR.EXPLICIT_LSR); // already maximized
}
int retainOldMask = 0;
@@ -347,6 +358,7 @@ public final class XLikelySubtags {
}
if (retainOldMask == 0) {
+ assert result.flags == LSR.IMPLICIT_LSR;
return result;
}
if ((retainOldMask & 4) == 0) {
@@ -358,7 +370,116 @@ public final class XLikelySubtags {
if ((retainOldMask & 1) == 0) {
region = result.region;
}
- return new LSR(language, script, region);
+ // retainOldMask flags = LSR explicit-subtag flags
+ return new LSR(language, script, region, retainOldMask);
+ }
+
+ /**
+ * Tests whether lsr is "more likely" than other.
+ * For example, fr-Latn-FR is more likely than fr-Latn-CH because
+ * FR is the default region for fr-Latn.
+ *
+ * <p>The likelyInfo caches lookup information between calls.
+ * The return value is an updated likelyInfo value,
+ * with bit 0 set if lsr is "more likely".
+ * The initial value of likelyInfo must be negative.
+ */
+ int compareLikely(LSR lsr, LSR other, int likelyInfo) {
+ // If likelyInfo >= 0:
+ // likelyInfo bit 1 is set if the previous comparison with lsr
+ // was for equal language and script.
+ // Otherwise the scripts differed.
+ if (!lsr.language.equals(other.language)) {
+ return 0xfffffffc; // negative, lsr not better than other
+ }
+ if (!lsr.script.equals(other.script)) {
+ int index;
+ if (likelyInfo >= 0 && (likelyInfo & 2) == 0) {
+ index = likelyInfo >> 2;
+ } else {
+ index = getLikelyIndex(lsr.language, "");
+ likelyInfo = index << 2;
+ }
+ LSR likely = lsrs[index];
+ if (lsr.script.equals(likely.script)) {
+ return likelyInfo | 1;
+ } else {
+ return likelyInfo & ~1;
+ }
+ }
+ if (!lsr.region.equals(other.region)) {
+ int index;
+ if (likelyInfo >= 0 && (likelyInfo & 2) != 0) {
+ index = likelyInfo >> 2;
+ } else {
+ index = getLikelyIndex(lsr.language, lsr.region);
+ likelyInfo = (index << 2) | 2;
+ }
+ LSR likely = lsrs[index];
+ if (lsr.region.equals(likely.region)) {
+ return likelyInfo | 1;
+ } else {
+ return likelyInfo & ~1;
+ }
+ }
+ return likelyInfo & ~1; // lsr not better than other
+ }
+
+ // Subset of maximize().
+ private int getLikelyIndex(String language, String script) {
+ if (language.equals("und")) {
+ language = "";
+ }
+ if (script.equals("Zzzz")) {
+ script = "";
+ }
+
+ BytesTrie iter = new BytesTrie(trie);
+ long state;
+ int value;
+ // Small optimization: Array lookup for first language letter.
+ int c0;
+ if (language.length() >= 2 && 0 <= (c0 = language.charAt(0) - 'a') && c0 <= 25 &&
+ (state = trieFirstLetterStates[c0]) != 0) {
+ value = trieNext(iter.resetToState64(state), language, 1);
+ } else {
+ value = trieNext(iter, language, 0);
+ }
+ if (value >= 0) {
+ state = iter.getState64();
+ } else {
+ iter.resetToState64(trieUndState); // "und" ("*")
+ state = 0;
+ }
+
+ if (value > 0) {
+ // Intermediate or final value from just language.
+ if (value == SKIP_SCRIPT) {
+ value = 0;
+ }
+ } else {
+ value = trieNext(iter, script, 0);
+ if (value >= 0) {
+ state = iter.getState64();
+ } else {
+ if (state == 0) {
+ iter.resetToState64(trieUndZzzzState); // "und-Zzzz" ("**")
+ } else {
+ iter.resetToState64(state);
+ value = trieNext(iter, "", 0);
+ assert value >= 0;
+ state = iter.getState64();
+ }
+ }
+ }
+
+ if (value > 0) {
+ // Final value from just language or language+script.
+ } else {
+ value = trieNext(iter, "", 0);
+ assert value > 0;
+ }
+ return value;
}
private static final int trieNext(BytesTrie iter, String s, int i) {
@@ -418,9 +539,9 @@ public final class XLikelySubtags {
boolean favorRegionOk = false;
if (result.script.equals(value00.script)) { //script is default
if (result.region.equals(value00.region)) {
- return new LSR(result.language, "", "");
+ return new LSR(result.language, "", "", LSR.DONT_CARE_FLAGS);
} else if (fieldToFavor == ULocale.Minimize.FAVOR_REGION) {
- return new LSR(result.language, "", result.region);
+ return new LSR(result.language, "", result.region, LSR.DONT_CARE_FLAGS);
} else {
favorRegionOk = true;
}
@@ -430,9 +551,9 @@ public final class XLikelySubtags {
// Maybe do later, but for now use the straightforward code.
LSR result2 = maximize(languageIn, scriptIn, "");
if (result2.equals(result)) {
- return new LSR(result.language, result.script, "");
+ return new LSR(result.language, result.script, "", LSR.DONT_CARE_FLAGS);
} else if (favorRegionOk) {
- return new LSR(result.language, "", result.region);
+ return new LSR(result.language, "", result.region, LSR.DONT_CARE_FLAGS);
}
return result;
}
diff --git a/android_icu4j/src/main/java/android/icu/impl/number/AdoptingModifierStore.java b/android_icu4j/src/main/java/android/icu/impl/number/AdoptingModifierStore.java
index 91941aad1..76c3d6423 100644
--- a/android_icu4j/src/main/java/android/icu/impl/number/AdoptingModifierStore.java
+++ b/android_icu4j/src/main/java/android/icu/impl/number/AdoptingModifierStore.java
@@ -4,6 +4,7 @@
package android.icu.impl.number;
import android.icu.impl.StandardPlural;
+import android.icu.impl.number.Modifier.Signum;
/**
* This implementation of ModifierStore adopts references to Modifiers.
@@ -13,7 +14,8 @@ import android.icu.impl.StandardPlural;
*/
public class AdoptingModifierStore implements ModifierStore {
private final Modifier positive;
- private final Modifier zero;
+ private final Modifier posZero;
+ private final Modifier negZero;
private final Modifier negative;
final Modifier[] mods;
boolean frozen;
@@ -24,9 +26,10 @@ public class AdoptingModifierStore implements ModifierStore {
* <p>
* If this constructor is used, a plural form CANNOT be passed to {@link #getModifier}.
*/
- public AdoptingModifierStore(Modifier positive, Modifier zero, Modifier negative) {
+ public AdoptingModifierStore(Modifier positive, Modifier posZero, Modifier negZero, Modifier negative) {
this.positive = positive;
- this.zero = zero;
+ this.posZero = posZero;
+ this.negZero = negZero;
this.negative = negative;
this.mods = null;
this.frozen = true;
@@ -41,13 +44,14 @@ public class AdoptingModifierStore implements ModifierStore {
*/
public AdoptingModifierStore() {
this.positive = null;
- this.zero = null;
+ this.posZero = null;
+ this.negZero = null;
this.negative = null;
- this.mods = new Modifier[3 * StandardPlural.COUNT];
+ this.mods = new Modifier[4 * StandardPlural.COUNT];
this.frozen = false;
}
- public void setModifier(int signum, StandardPlural plural, Modifier mod) {
+ public void setModifier(Signum signum, StandardPlural plural, Modifier mod) {
assert !frozen;
mods[getModIndex(signum, plural)] = mod;
}
@@ -56,21 +60,34 @@ public class AdoptingModifierStore implements ModifierStore {
frozen = true;
}
- public Modifier getModifierWithoutPlural(int signum) {
+ public Modifier getModifierWithoutPlural(Signum signum) {
assert frozen;
assert mods == null;
- return signum == 0 ? zero : signum < 0 ? negative : positive;
+ assert signum != null;
+ switch (signum) {
+ case POS:
+ return positive;
+ case POS_ZERO:
+ return posZero;
+ case NEG_ZERO:
+ return negZero;
+ case NEG:
+ return negative;
+ default:
+ throw new AssertionError("Unreachable");
+ }
}
- public Modifier getModifier(int signum, StandardPlural plural) {
+ @Override
+ public Modifier getModifier(Signum signum, StandardPlural plural) {
assert frozen;
assert positive == null;
return mods[getModIndex(signum, plural)];
}
- private static int getModIndex(int signum, StandardPlural plural) {
- assert signum >= -1 && signum <= 1;
+ private static int getModIndex(Signum signum, StandardPlural plural) {
+ assert signum != null;
assert plural != null;
- return plural.ordinal() * 3 + (signum + 1);
+ return plural.ordinal() * Signum.COUNT + signum.ordinal();
}
}
diff --git a/android_icu4j/src/main/java/android/icu/impl/number/ConstantMultiFieldModifier.java b/android_icu4j/src/main/java/android/icu/impl/number/ConstantMultiFieldModifier.java
index 5dab11193..e8e787e26 100644
--- a/android_icu4j/src/main/java/android/icu/impl/number/ConstantMultiFieldModifier.java
+++ b/android_icu4j/src/main/java/android/icu/impl/number/ConstantMultiFieldModifier.java
@@ -20,8 +20,8 @@ public class ConstantMultiFieldModifier implements Modifier {
// value and is treated internally as immutable.
protected final char[] prefixChars;
protected final char[] suffixChars;
- protected final Field[] prefixFields;
- protected final Field[] suffixFields;
+ protected final Object[] prefixFields;
+ protected final Object[] suffixFields;
private final boolean overwrite;
private final boolean strong;
diff --git a/android_icu4j/src/main/java/android/icu/impl/number/CurrencySpacingEnabledModifier.java b/android_icu4j/src/main/java/android/icu/impl/number/CurrencySpacingEnabledModifier.java
index 41e4fd174..5c93ba968 100644
--- a/android_icu4j/src/main/java/android/icu/impl/number/CurrencySpacingEnabledModifier.java
+++ b/android_icu4j/src/main/java/android/icu/impl/number/CurrencySpacingEnabledModifier.java
@@ -3,8 +3,6 @@
// License & terms of use: http://www.unicode.org/copyright.html#License
package android.icu.impl.number;
-import java.text.Format.Field;
-
import android.icu.impl.FormattedStringBuilder;
import android.icu.text.DecimalFormatSymbols;
import android.icu.text.NumberFormat;
@@ -58,7 +56,7 @@ public class CurrencySpacingEnabledModifier extends ConstantMultiFieldModifier {
afterPrefixInsert = null;
}
if (suffix.length() > 0 && suffix.fieldAt(0) == NumberFormat.Field.CURRENCY) {
- int suffixCp = suffix.getLastCodePoint();
+ int suffixCp = suffix.getFirstCodePoint();
UnicodeSet suffixUnicodeSet = getUnicodeSet(symbols, IN_CURRENCY, SUFFIX);
if (suffixUnicodeSet.contains(suffixCp)) {
beforeSuffixUnicodeSet = getUnicodeSet(symbols, IN_NUMBER, SUFFIX);
@@ -127,7 +125,7 @@ public class CurrencySpacingEnabledModifier extends ConstantMultiFieldModifier {
// NOTE: For prefix, output.fieldAt(index-1) gets the last field type in the prefix.
// This works even if the last code point in the prefix is 2 code units because the
// field value gets populated to both indices in the field array.
- Field affixField = (affix == PREFIX) ? output.fieldAt(index - 1)
+ Object affixField = (affix == PREFIX) ? output.fieldAt(index - 1)
: output.fieldAt(index);
if (affixField != NumberFormat.Field.CURRENCY) {
return 0;
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 7367a5395..4a73640fd 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
@@ -70,7 +70,7 @@ public class DecimalFormatProperties implements Cloneable, Serializable {
/**
* Internal parse mode for increased compatibility with java.text.DecimalFormat.
* Used by Android libcore. To enable this feature, java.text.DecimalFormat holds an instance of
- * ICU4J's DecimalFormat and enable it by calling setParseStrictMode(ParseMode.COMPATIBILITY).
+ * ICU4J's DecimalFormat and enable it by calling setParseStrictMode(ParseMode.JAVA_COMPATIBILITY).
*/
JAVA_COMPATIBILITY,
}
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 bb3cc71e1..b6f548dac 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
@@ -8,6 +8,7 @@ import java.math.MathContext;
import java.text.FieldPosition;
import android.icu.impl.StandardPlural;
+import android.icu.impl.number.Modifier.Signum;
import android.icu.text.PluralRules;
import android.icu.text.UFieldPosition;
@@ -124,6 +125,26 @@ public interface DecimalQuantity extends PluralRules.IFixedDecimal {
public int getMagnitude() throws ArithmeticException;
/**
+ * @return The value of the (suppressed) exponent after the number has been
+ * put into a notation with exponents (ex: compact, scientific). Ex: given
+ * the number 1000 as "1K" / "1E3", the return value will be 3 (positive).
+ */
+ public int getExponent();
+
+ /**
+ * Adjusts the value for the (suppressed) exponent stored when using
+ * notation with exponents (ex: compact, scientific).
+ *
+ * <p>Adjusting the exponent is decoupled from {@link #adjustMagnitude} in
+ * order to allow flexibility for {@link StandardPlural} to be selected in
+ * formatting (ex: for compact notation) either with or without the exponent
+ * applied in the value of the number.
+ * @param delta
+ * The value to adjust the exponent by.
+ */
+ public void adjustExponent(int delta);
+
+ /**
* @return Whether the value represented by this {@link DecimalQuantity} is
* zero, infinity, or NaN.
*/
@@ -132,8 +153,8 @@ public interface DecimalQuantity extends PluralRules.IFixedDecimal {
/** @return Whether the value represented by this {@link DecimalQuantity} is less than zero. */
public boolean isNegative();
- /** @return -1 if the value is negative; 1 if positive; or 0 if zero. */
- public int signum();
+ /** @return The appropriate value from the Signum enum. */
+ public Signum signum();
/** @return Whether the value represented by this {@link DecimalQuantity} is infinite. */
@Override
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 47d5bfdc3..f00ae97cb 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
@@ -10,6 +10,7 @@ import java.text.FieldPosition;
import android.icu.impl.StandardPlural;
import android.icu.impl.Utility;
+import android.icu.impl.number.Modifier.Signum;
import android.icu.text.PluralRules;
import android.icu.text.PluralRules.Operand;
import android.icu.text.UFieldPosition;
@@ -87,6 +88,12 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity {
protected int lReqPos = 0;
protected int rReqPos = 0;
+ /**
+ * The value of the (suppressed) exponent after the number has been put into
+ * a notation with exponents (ex: compact, scientific).
+ */
+ protected int exponent = 0;
+
@Override
public void copyFrom(DecimalQuantity _other) {
copyBcdFrom(_other);
@@ -99,13 +106,14 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity {
origDouble = other.origDouble;
origDelta = other.origDelta;
isApproximate = other.isApproximate;
+ exponent = other.exponent;
}
public DecimalQuantity_AbstractBCD clear() {
lReqPos = 0;
rReqPos = 0;
flags = 0;
- setBcdToZero(); // sets scale, precision, hasDouble, origDouble, origDelta, and BCD data
+ setBcdToZero(); // sets scale, precision, hasDouble, origDouble, origDelta, exponent, and BCD data
return this;
}
@@ -218,6 +226,16 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity {
}
@Override
+ public int getExponent() {
+ return exponent;
+ }
+
+ @Override
+ public void adjustExponent(int delta) {
+ exponent = exponent + delta;
+ }
+
+ @Override
public StandardPlural getStandardPlural(PluralRules rules) {
if (rules == null) {
// Fail gracefully if the user didn't provide a PluralRules
@@ -247,6 +265,8 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity {
return fractionCount();
case w:
return fractionCountWithoutTrailingZeros();
+ case e:
+ return getExponent();
default:
return Math.abs(toDouble());
}
@@ -292,11 +312,11 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity {
}
private int fractionCount() {
- return -getLowerDisplayMagnitude();
+ return Math.max(0, -getLowerDisplayMagnitude() - exponent);
}
private int fractionCountWithoutTrailingZeros() {
- return Math.max(-scale, 0);
+ return Math.max(-scale - exponent, 0);
}
@Override
@@ -305,8 +325,18 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity {
}
@Override
- public int signum() {
- return isNegative() ? -1 : (isZeroish() && !isInfinite()) ? 0 : 1;
+ public Signum signum() {
+ boolean isZero = (isZeroish() && !isInfinite());
+ boolean isNeg = isNegative();
+ if (isZero && isNeg) {
+ return Signum.NEG_ZERO;
+ } else if (isZero) {
+ return Signum.POS_ZERO;
+ } else if (isNeg) {
+ return Signum.NEG;
+ } else {
+ return Signum.POS;
+ }
}
@Override
@@ -460,8 +490,14 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity {
return;
}
+ if (exponent == -1023 || exponent == 1024) {
+ // The extreme values of exponent are special; use slow path.
+ convertToAccurateDouble();
+ return;
+ }
+
// 3.3219... is log2(10)
- int fracLength = (int) ((52 - exponent) / 3.32192809489);
+ int fracLength = (int) ((52 - exponent) / 3.32192809488736234787031942948939017586);
if (fracLength >= 0) {
int i = fracLength;
// 1e22 is the largest exact double.
@@ -568,7 +604,9 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity {
/**
* Returns a long approximating the internal BCD. A long can only represent the integral part of the
- * number.
+ * 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.
@@ -579,12 +617,12 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity {
// Fallback behavior upon truncateIfOverflow is to truncate at 17 digits.
assert(truncateIfOverflow || fitsInLong());
long result = 0L;
- int upperMagnitude = scale + precision - 1;
+ int upperMagnitude = exponent + scale + precision - 1;
if (truncateIfOverflow) {
upperMagnitude = Math.min(upperMagnitude, 17);
}
for (int magnitude = upperMagnitude; magnitude >= 0; magnitude--) {
- result = result * 10 + getDigitPos(magnitude - scale);
+ result = result * 10 + getDigitPos(magnitude - scale - exponent);
}
if (isNegative()) {
result = -result;
@@ -596,10 +634,13 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity {
* This returns a long representing the fraction digits of the number, as required by PluralRules.
* For example, if we represent the number "1.20" (including optional and required digits), then this
* function returns "20" if includeTrailingZeros is true or "2" if false.
+ * 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.
*/
public long toFractionLong(boolean includeTrailingZeros) {
long result = 0L;
- int magnitude = -1;
+ int magnitude = -1 - exponent;
int lowerMagnitude = scale;
if (includeTrailingZeros) {
lowerMagnitude = Math.min(lowerMagnitude, rReqPos);
@@ -629,7 +670,7 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity {
if (isZeroish()) {
return true;
}
- if (scale < 0) {
+ if (exponent + scale < 0) {
return false;
}
int magnitude = getMagnitude();
@@ -982,20 +1023,43 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity {
@Override
public String toPlainString() {
- // NOTE: This logic is duplicated between here and DecimalQuantity_SimpleStorage.
StringBuilder sb = new StringBuilder();
+ toPlainString(sb);
+ return sb.toString();
+ }
+
+ public void toPlainString(StringBuilder result) {
+ assert(!isApproximate);
if (isNegative()) {
- sb.append('-');
+ result.append('-');
}
- if (precision == 0 || getMagnitude() < 0) {
- sb.append('0');
+ if (precision == 0) {
+ result.append('0');
+ return;
}
- for (int m = getUpperDisplayMagnitude(); m >= getLowerDisplayMagnitude(); m--) {
- sb.append((char) ('0' + getDigit(m)));
- if (m == 0)
- sb.append('.');
+
+ int upper = scale + precision + exponent - 1;
+ int lower = scale + exponent;
+ if (upper < lReqPos - 1) {
+ upper = lReqPos - 1;
+ }
+ if (lower > rReqPos) {
+ lower = rReqPos;
+ }
+
+ int p = upper;
+ if (p < 0) {
+ result.append('0');
+ }
+ for (; p >= 0; p--) {
+ result.append((char) ('0' + getDigitPos(p - scale - exponent)));
+ }
+ if (lower < 0) {
+ result.append('.');
+ }
+ for(; p >= lower; p--) {
+ result.append((char) ('0' + getDigitPos(p - scale - exponent)));
}
- return sb.toString();
}
public String toScientificString() {
@@ -1026,7 +1090,7 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity {
}
}
result.append('E');
- int _scale = upperPos + scale;
+ int _scale = upperPos + scale + exponent;
if (_scale == Integer.MIN_VALUE) {
result.append("-2147483648");
return;
@@ -1137,7 +1201,7 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity {
/**
* Sets the internal representation to zero. Clears any values stored in scale, precision, hasDouble,
- * origDouble, origDelta, and BCD data.
+ * origDouble, origDelta, exponent, and BCD data.
*/
protected abstract void setBcdToZero();
diff --git a/android_icu4j/src/main/java/android/icu/impl/number/DecimalQuantity_DualStorageBCD.java b/android_icu4j/src/main/java/android/icu/impl/number/DecimalQuantity_DualStorageBCD.java
index a83fcb0e3..bfca13c09 100644
--- a/android_icu4j/src/main/java/android/icu/impl/number/DecimalQuantity_DualStorageBCD.java
+++ b/android_icu4j/src/main/java/android/icu/impl/number/DecimalQuantity_DualStorageBCD.java
@@ -182,6 +182,7 @@ public final class DecimalQuantity_DualStorageBCD extends DecimalQuantity_Abstra
isApproximate = false;
origDouble = 0;
origDelta = 0;
+ exponent = 0;
}
@Override
@@ -256,11 +257,11 @@ public final class DecimalQuantity_DualStorageBCD extends DecimalQuantity_Abstra
}
BigDecimal result = BigDecimal.valueOf(tempLong);
// Test that the new scale fits inside the BigDecimal
- long newScale = result.scale() + scale;
+ long newScale = result.scale() + scale + exponent;
if (newScale <= Integer.MIN_VALUE) {
result = BigDecimal.ZERO;
} else {
- result = result.scaleByPowerOfTen(scale);
+ result = result.scaleByPowerOfTen(scale + exponent);
}
if (isNegative()) {
result = result.negate();
diff --git a/android_icu4j/src/main/java/android/icu/impl/number/LocalizedNumberFormatterAsFormat.java b/android_icu4j/src/main/java/android/icu/impl/number/LocalizedNumberFormatterAsFormat.java
index a23cefa0e..79d62ed11 100644
--- a/android_icu4j/src/main/java/android/icu/impl/number/LocalizedNumberFormatterAsFormat.java
+++ b/android_icu4j/src/main/java/android/icu/impl/number/LocalizedNumberFormatterAsFormat.java
@@ -13,7 +13,9 @@ import java.text.FieldPosition;
import java.text.Format;
import java.text.ParsePosition;
-import android.icu.number.FormattedNumber;
+import android.icu.impl.FormattedStringBuilder;
+import android.icu.impl.FormattedValueStringBuilderImpl;
+import android.icu.impl.Utility;
import android.icu.number.LocalizedNumberFormatter;
import android.icu.number.NumberFormatter;
import android.icu.util.ULocale;
@@ -48,16 +50,18 @@ public class LocalizedNumberFormatterAsFormat extends Format {
if (!(obj instanceof Number)) {
throw new IllegalArgumentException();
}
- FormattedNumber result = formatter.format((Number) obj);
+ DecimalQuantity dq = new DecimalQuantity_DualStorageBCD((Number) obj);
+ FormattedStringBuilder string = new FormattedStringBuilder();
+ formatter.formatImpl(dq, string);
// always return first occurrence:
pos.setBeginIndex(0);
pos.setEndIndex(0);
- boolean found = result.nextFieldPosition(pos);
+ boolean found = FormattedValueStringBuilderImpl.nextFieldPosition(string, pos);
if (found && toAppendTo.length() != 0) {
pos.setBeginIndex(pos.getBeginIndex() + toAppendTo.length());
pos.setEndIndex(pos.getEndIndex() + toAppendTo.length());
}
- result.appendTo(toAppendTo);
+ Utility.appendTo(string, toAppendTo);
return toAppendTo;
}
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 6b4d10aea..d67f0212f 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
@@ -13,6 +13,7 @@ import android.icu.impl.ICUResourceBundle;
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.number.NumberFormatter.UnitWidth;
import android.icu.text.NumberFormat;
import android.icu.text.PluralRules;
@@ -244,8 +245,10 @@ public class LongNameHandler implements MicroPropsGenerator, ModifierStore {
String compiled = SimpleFormatterImpl
.compileToStringMinMaxArguments(rawPerUnitFormat, sb, 2, 2);
String secondaryFormat = getWithPlural(secondaryData, StandardPlural.ONE);
+
+ // Some "one" pattern may not contain "{0}". For example in "ar" or "ne" locale.
String secondaryCompiled = SimpleFormatterImpl
- .compileToStringMinMaxArguments(secondaryFormat, sb, 1, 1);
+ .compileToStringMinMaxArguments(secondaryFormat, sb, 0, 1);
String secondaryString = SimpleFormatterImpl.getTextWithNoArguments(secondaryCompiled)
.trim();
perUnitFormat = SimpleFormatterImpl.formatCompiledPattern(compiled, "{0}", secondaryString);
@@ -266,7 +269,7 @@ public class LongNameHandler implements MicroPropsGenerator, ModifierStore {
String compiled = SimpleFormatterImpl.compileToStringMinMaxArguments(simpleFormat, sb, 0, 1);
Modifier.Parameters parameters = new Modifier.Parameters();
parameters.obj = this;
- parameters.signum = 0;
+ parameters.signum = null;// Signum ignored
parameters.plural = plural;
modifiers.put(plural, new SimpleModifier(compiled, field, false, parameters));
}
@@ -285,7 +288,7 @@ public class LongNameHandler implements MicroPropsGenerator, ModifierStore {
.compileToStringMinMaxArguments(compoundFormat, sb, 0, 1);
Modifier.Parameters parameters = new Modifier.Parameters();
parameters.obj = this;
- parameters.signum = 0;
+ parameters.signum = null; // Signum ignored
parameters.plural = plural;
modifiers.put(plural, new SimpleModifier(compoundCompiled, field, false, parameters));
}
@@ -300,7 +303,8 @@ public class LongNameHandler implements MicroPropsGenerator, ModifierStore {
}
@Override
- public Modifier getModifier(int signum, StandardPlural plural) {
+ public Modifier getModifier(Signum signum, StandardPlural plural) {
+ // Signum ignored
return modifiers.get(plural);
}
}
diff --git a/android_icu4j/src/main/java/android/icu/impl/number/Modifier.java b/android_icu4j/src/main/java/android/icu/impl/number/Modifier.java
index 3507a025c..5fa5fd166 100644
--- a/android_icu4j/src/main/java/android/icu/impl/number/Modifier.java
+++ b/android_icu4j/src/main/java/android/icu/impl/number/Modifier.java
@@ -19,6 +19,15 @@ import android.icu.impl.StandardPlural;
*/
public interface Modifier {
+ static enum Signum {
+ NEG,
+ NEG_ZERO,
+ POS_ZERO,
+ POS;
+
+ static final int COUNT = Signum.values().length;
+ };
+
/**
* Apply this Modifier to the string builder.
*
@@ -68,7 +77,7 @@ public interface Modifier {
*/
public static class Parameters {
public ModifierStore obj;
- public int signum;
+ public Signum signum;
public StandardPlural plural;
}
diff --git a/android_icu4j/src/main/java/android/icu/impl/number/ModifierStore.java b/android_icu4j/src/main/java/android/icu/impl/number/ModifierStore.java
index 92479c7ad..134d94d90 100644
--- a/android_icu4j/src/main/java/android/icu/impl/number/ModifierStore.java
+++ b/android_icu4j/src/main/java/android/icu/impl/number/ModifierStore.java
@@ -4,6 +4,7 @@
package android.icu.impl.number;
import android.icu.impl.StandardPlural;
+import android.icu.impl.number.Modifier.Signum;
/**
* This is *not* a modifier; rather, it is an object that can return modifiers
@@ -16,5 +17,5 @@ public interface ModifierStore {
/**
* Returns a Modifier with the given parameters (best-effort).
*/
- Modifier getModifier(int signum, StandardPlural plural);
+ Modifier getModifier(Signum signum, StandardPlural plural);
}
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 7f69ce71c..89f9274ba 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
@@ -52,7 +52,7 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, MicroPr
PluralRules rules;
// Number details
- int signum;
+ Signum signum;
StandardPlural plural;
// QuantityChain details
@@ -131,7 +131,7 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, MicroPr
* The plural form of the number, required only if the pattern contains the triple
* currency sign, "¤¤¤" (and as indicated by {@link #needsPlurals()}).
*/
- public void setNumberProperties(int signum, StandardPlural plural) {
+ public void setNumberProperties(Signum signum, StandardPlural plural) {
assert (plural != null) == needsPlurals();
this.signum = signum;
this.plural = plural;
@@ -157,44 +157,35 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, MicroPr
* @return An immutable that supports both positive and negative numbers.
*/
public ImmutablePatternModifier createImmutable() {
- return createImmutableAndChain(null);
- }
-
- /**
- * Creates a new quantity-dependent Modifier that behaves the same as the current instance, but which
- * is immutable and can be saved for future use. The number properties in the current instance are
- * mutated; all other properties are left untouched.
- *
- * @param parent
- * The QuantityChain to which to chain this immutable.
- * @return An immutable that supports both positive and negative numbers.
- */
- public ImmutablePatternModifier createImmutableAndChain(MicroPropsGenerator parent) {
FormattedStringBuilder a = new FormattedStringBuilder();
FormattedStringBuilder b = new FormattedStringBuilder();
if (needsPlurals()) {
// Slower path when we require the plural keyword.
AdoptingModifierStore pm = new AdoptingModifierStore();
for (StandardPlural plural : StandardPlural.VALUES) {
- setNumberProperties(1, plural);
- pm.setModifier(1, plural, createConstantModifier(a, b));
- setNumberProperties(0, plural);
- pm.setModifier(0, plural, createConstantModifier(a, b));
- setNumberProperties(-1, plural);
- pm.setModifier(-1, plural, createConstantModifier(a, b));
+ setNumberProperties(Signum.POS, plural);
+ pm.setModifier(Signum.POS, plural, createConstantModifier(a, b));
+ setNumberProperties(Signum.POS_ZERO, plural);
+ pm.setModifier(Signum.POS_ZERO, plural, createConstantModifier(a, b));
+ setNumberProperties(Signum.NEG_ZERO, plural);
+ pm.setModifier(Signum.NEG_ZERO, plural, createConstantModifier(a, b));
+ setNumberProperties(Signum.NEG, plural);
+ pm.setModifier(Signum.NEG, plural, createConstantModifier(a, b));
}
pm.freeze();
- return new ImmutablePatternModifier(pm, rules, parent);
+ return new ImmutablePatternModifier(pm, rules);
} else {
// Faster path when plural keyword is not needed.
- setNumberProperties(1, null);
+ setNumberProperties(Signum.POS, null);
Modifier positive = createConstantModifier(a, b);
- setNumberProperties(0, null);
- Modifier zero = createConstantModifier(a, b);
- setNumberProperties(-1, null);
+ setNumberProperties(Signum.POS_ZERO, null);
+ Modifier posZero = createConstantModifier(a, b);
+ setNumberProperties(Signum.NEG_ZERO, null);
+ Modifier negZero = createConstantModifier(a, b);
+ setNumberProperties(Signum.NEG, null);
Modifier negative = createConstantModifier(a, b);
- AdoptingModifierStore pm = new AdoptingModifierStore(positive, zero, negative);
- return new ImmutablePatternModifier(pm, null, parent);
+ AdoptingModifierStore pm = new AdoptingModifierStore(positive, posZero, negZero, negative);
+ return new ImmutablePatternModifier(pm, null);
}
}
@@ -227,20 +218,30 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, MicroPr
public static class ImmutablePatternModifier implements MicroPropsGenerator {
final AdoptingModifierStore pm;
final PluralRules rules;
- final MicroPropsGenerator parent;
+ /* final */ MicroPropsGenerator parent;
ImmutablePatternModifier(
AdoptingModifierStore pm,
- PluralRules rules,
- MicroPropsGenerator parent) {
+ PluralRules rules) {
this.pm = pm;
this.rules = rules;
+ this.parent = null;
+ }
+
+ public ImmutablePatternModifier addToChain(MicroPropsGenerator parent) {
this.parent = parent;
+ return this;
}
@Override
public MicroProps processQuantity(DecimalQuantity quantity) {
MicroProps micros = parent.processQuantity(quantity);
+ if (micros.rounder != null) {
+ micros.rounder.apply(quantity);
+ }
+ if (micros.modMiddle != null) {
+ return micros;
+ }
applyToMicros(micros, quantity);
return micros;
}
@@ -275,6 +276,12 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, MicroPr
@Override
public MicroProps processQuantity(DecimalQuantity fq) {
MicroProps micros = parent.processQuantity(fq);
+ if (micros.rounder != null) {
+ micros.rounder.apply(fq);
+ }
+ if (micros.modMiddle != null) {
+ return micros;
+ }
if (needsPlurals()) {
StandardPlural pluralForm = RoundingUtils.getPluralSafe(micros.rounder, rules, fq);
setNumberProperties(fq.signum(), pluralForm);
@@ -372,8 +379,7 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, MicroPr
}
PatternStringUtils.patternInfoToStringBuilder(patternInfo,
isPrefix,
- signum,
- signDisplay,
+ PatternStringUtils.resolveSignDisplay(signDisplay, signum),
plural,
perMilleReplacesPercent,
currentAffix);
@@ -400,8 +406,23 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, MicroPr
} else if (unitWidth == UnitWidth.HIDDEN) {
return "";
} else {
- int selector = unitWidth == UnitWidth.NARROW ? Currency.NARROW_SYMBOL_NAME
- : Currency.SYMBOL_NAME;
+ 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);
}
case AffixUtils.TYPE_CURRENCY_DOUBLE:
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 ac7bf0a13..2aeb45acd 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
@@ -6,6 +6,7 @@ package android.icu.impl.number;
import java.math.BigDecimal;
import android.icu.impl.StandardPlural;
+import android.icu.impl.number.Modifier.Signum;
import android.icu.impl.number.Padder.PadPosition;
import android.icu.number.NumberFormatter.SignDisplay;
import android.icu.text.DecimalFormatSymbols;
@@ -16,6 +17,21 @@ import android.icu.text.DecimalFormatSymbols;
*/
public class PatternStringUtils {
+ // Note: the order of fields in this enum matters for parsing.
+ /**
+ * @hide Only a subset of ICU is exposed in Android
+ */
+ public static enum PatternSignType {
+ // Render using normal positive subpattern rules
+ POS,
+ // Render using rules to force the display of a plus sign
+ POS_SIGN,
+ // Render using negative subpattern rules
+ NEG;
+
+ public static final PatternSignType[] VALUES = PatternSignType.values();
+ };
+
/**
* Determine whether a given roundingIncrement should be ignored for formatting
* based on the current maxFrac value (maximum fraction digits). For example a
@@ -25,7 +41,7 @@ public class PatternStringUtils {
* it should not be ignored if maxFrac is 2 or more (but a roundingIncrement of
* 0.005 is treated like 0.001 for significance).
*
- * This test is needed for both NumberPropertyMapper.oldToNew and
+ * This test is needed for both NumberPropertyMapper.oldToNew and
* PatternStringUtils.propertiesToPatternString, but NumberPropertyMapper
* is package-private so we have it here.
*
@@ -82,7 +98,7 @@ public class PatternStringUtils {
boolean alwaysShowDecimal = properties.getDecimalSeparatorAlwaysShown();
int exponentDigits = Math.min(properties.getMinimumExponentDigits(), dosMax);
boolean exponentShowPlusSign = properties.getExponentSignAlwaysShown();
- PropertiesAffixPatternProvider affixes = new PropertiesAffixPatternProvider(properties);
+ AffixPatternProvider affixes = PropertiesAffixPatternProvider.forProperties(properties);
// Prefixes
sb.append(affixes.getString(AffixPatternProvider.FLAG_POS_PREFIX));
@@ -418,25 +434,19 @@ public class PatternStringUtils {
public static void patternInfoToStringBuilder(
AffixPatternProvider patternInfo,
boolean isPrefix,
- int signum,
- SignDisplay signDisplay,
+ PatternSignType patternSignType,
StandardPlural plural,
boolean perMilleReplacesPercent,
StringBuilder output) {
- // Should the output render '+' where '-' would normally appear in the pattern?
- boolean plusReplacesMinusSign = signum != -1
- && (signDisplay == SignDisplay.ALWAYS
- || signDisplay == SignDisplay.ACCOUNTING_ALWAYS
- || (signum == 1
- && (signDisplay == SignDisplay.EXCEPT_ZERO
- || signDisplay == SignDisplay.ACCOUNTING_EXCEPT_ZERO)))
- && patternInfo.positiveHasPlusSign() == false;
-
- // Should we use the affix from the negative subpattern? (If not, we will use the positive
- // subpattern.)
+ boolean plusReplacesMinusSign = (patternSignType == PatternSignType.POS_SIGN)
+ && !patternInfo.positiveHasPlusSign();
+
+ // Should we use the affix from the negative subpattern?
+ // (If not, we will use the positive subpattern.)
boolean useNegativeAffixPattern = patternInfo.hasNegativeSubpattern()
- && (signum == -1 || (patternInfo.negativeHasMinusSign() && plusReplacesMinusSign));
+ && (patternSignType == PatternSignType.NEG
+ || (patternInfo.negativeHasMinusSign() && plusReplacesMinusSign));
// Resolve the flags for the affix pattern.
int flags = 0;
@@ -455,8 +465,8 @@ public class PatternStringUtils {
boolean prependSign;
if (!isPrefix || useNegativeAffixPattern) {
prependSign = false;
- } else if (signum == -1) {
- prependSign = signDisplay != SignDisplay.NEVER;
+ } else if (patternSignType == PatternSignType.NEG) {
+ prependSign = true;
} else {
prependSign = plusReplacesMinusSign;
}
@@ -485,4 +495,53 @@ public class PatternStringUtils {
}
}
+ public static PatternSignType resolveSignDisplay(SignDisplay signDisplay, Signum signum) {
+ switch (signDisplay) {
+ case AUTO:
+ case ACCOUNTING:
+ switch (signum) {
+ case NEG:
+ case NEG_ZERO:
+ return PatternSignType.NEG;
+ case POS_ZERO:
+ case POS:
+ return PatternSignType.POS;
+ }
+ break;
+
+ case ALWAYS:
+ case ACCOUNTING_ALWAYS:
+ switch (signum) {
+ case NEG:
+ case NEG_ZERO:
+ return PatternSignType.NEG;
+ case POS_ZERO:
+ case POS:
+ return PatternSignType.POS_SIGN;
+ }
+ break;
+
+ case EXCEPT_ZERO:
+ case ACCOUNTING_EXCEPT_ZERO:
+ switch (signum) {
+ case NEG:
+ return PatternSignType.NEG;
+ case NEG_ZERO:
+ case POS_ZERO:
+ return PatternSignType.POS;
+ case POS:
+ return PatternSignType.POS_SIGN;
+ }
+ break;
+
+ case NEVER:
+ return PatternSignType.POS;
+
+ default:
+ break;
+ }
+
+ throw new AssertionError("Unreachable");
+ }
+
}
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 837f35ef6..f9deb3774 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
@@ -13,7 +13,15 @@ public class PropertiesAffixPatternProvider implements AffixPatternProvider {
private final String negSuffix;
private final boolean isCurrencyPattern;
- public PropertiesAffixPatternProvider(DecimalFormatProperties properties) {
+ public static AffixPatternProvider forProperties(DecimalFormatProperties properties) {
+ if (properties.getCurrencyPluralInfo() == null) {
+ return new PropertiesAffixPatternProvider(properties);
+ } else {
+ return new CurrencyPluralInfoAffixProvider(properties.getCurrencyPluralInfo(), properties);
+ }
+ }
+
+ PropertiesAffixPatternProvider(DecimalFormatProperties properties) {
// There are two ways to set affixes in DecimalFormat: via the pattern string (applyPattern), and via the
// explicit setters (setPositivePrefix and friends). The way to resolve the settings is as follows:
//
diff --git a/android_icu4j/src/main/java/android/icu/impl/number/RoundingUtils.java b/android_icu4j/src/main/java/android/icu/impl/number/RoundingUtils.java
index b233475dd..8dfeea255 100644
--- a/android_icu4j/src/main/java/android/icu/impl/number/RoundingUtils.java
+++ b/android_icu4j/src/main/java/android/icu/impl/number/RoundingUtils.java
@@ -230,6 +230,9 @@ public class RoundingUtils {
*/
public static StandardPlural getPluralSafe(
Precision rounder, PluralRules rules, DecimalQuantity dq) {
+ if (rounder == null) {
+ return dq.getStandardPlural(rules);
+ }
// TODO(ICU-20500): Avoid the copy?
DecimalQuantity copy = dq.createCopy();
rounder.apply(copy);
diff --git a/android_icu4j/src/main/java/android/icu/impl/number/SimpleModifier.java b/android_icu4j/src/main/java/android/icu/impl/number/SimpleModifier.java
index 8f84a7d9f..923e90577 100644
--- a/android_icu4j/src/main/java/android/icu/impl/number/SimpleModifier.java
+++ b/android_icu4j/src/main/java/android/icu/impl/number/SimpleModifier.java
@@ -19,9 +19,6 @@ public class SimpleModifier implements Modifier {
private final String compiledPattern;
private final Field field;
private final boolean strong;
- private final int prefixLength;
- private final int suffixOffset;
- private final int suffixLength;
// Parameters: used for number range formatting
private final Parameters parameters;
@@ -41,53 +38,21 @@ public class SimpleModifier implements Modifier {
this.field = field;
this.strong = strong;
this.parameters = parameters;
-
- int argLimit = SimpleFormatterImpl.getArgumentLimit(compiledPattern);
- if (argLimit == 0) {
- // No arguments in compiled pattern
- prefixLength = compiledPattern.charAt(1) - ARG_NUM_LIMIT;
- assert 2 + prefixLength == compiledPattern.length();
- // Set suffixOffset = -1 to indicate no arguments in compiled pattern.
- suffixOffset = -1;
- suffixLength = 0;
- } else {
- assert argLimit == 1;
- if (compiledPattern.charAt(1) != '\u0000') {
- prefixLength = compiledPattern.charAt(1) - ARG_NUM_LIMIT;
- suffixOffset = 3 + prefixLength;
- } else {
- prefixLength = 0;
- suffixOffset = 2;
- }
- if (3 + prefixLength < compiledPattern.length()) {
- suffixLength = compiledPattern.charAt(suffixOffset) - ARG_NUM_LIMIT;
- } else {
- suffixLength = 0;
- }
- }
}
@Override
public int apply(FormattedStringBuilder output, int leftIndex, int rightIndex) {
- return formatAsPrefixSuffix(output, leftIndex, rightIndex);
+ return SimpleFormatterImpl.formatPrefixSuffix(compiledPattern, field, leftIndex, rightIndex, output);
}
@Override
public int getPrefixLength() {
- return prefixLength;
+ return SimpleFormatterImpl.getPrefixLength(compiledPattern);
}
@Override
public int getCodePointCount() {
- int count = 0;
- if (prefixLength > 0) {
- count += Character.codePointCount(compiledPattern, 2, 2 + prefixLength);
- }
- if (suffixLength > 0) {
- count += Character
- .codePointCount(compiledPattern, 1 + suffixOffset, 1 + suffixOffset + suffixLength);
- }
- return count;
+ return SimpleFormatterImpl.getLength(compiledPattern, true);
}
@Override
@@ -120,49 +85,6 @@ public class SimpleModifier implements Modifier {
}
/**
- * TODO: This belongs in SimpleFormatterImpl. The only reason I haven't moved it there yet is because
- * DoubleSidedStringBuilder is an internal class and SimpleFormatterImpl feels like it should not
- * depend on it.
- *
- * <p>
- * Formats a value that is already stored inside the StringBuilder <code>result</code> between the
- * indices <code>startIndex</code> and <code>endIndex</code> by inserting characters before the start
- * index and after the end index.
- *
- * <p>
- * This is well-defined only for patterns with exactly one argument.
- *
- * @param result
- * The StringBuilder containing the value argument.
- * @param startIndex
- * The left index of the value within the string builder.
- * @param endIndex
- * The right index of the value within the string builder.
- * @return The number of characters (UTF-16 code points) that were added to the StringBuilder.
- */
- public int formatAsPrefixSuffix(
- FormattedStringBuilder result,
- int startIndex,
- int endIndex) {
- if (suffixOffset == -1) {
- // There is no argument for the inner number; overwrite the entire segment with our string.
- return result.splice(startIndex, endIndex, compiledPattern, 2, 2 + prefixLength, field);
- } else {
- if (prefixLength > 0) {
- result.insert(startIndex, compiledPattern, 2, 2 + prefixLength, field);
- }
- if (suffixLength > 0) {
- result.insert(endIndex + prefixLength,
- compiledPattern,
- 1 + suffixOffset,
- 1 + suffixOffset + suffixLength,
- field);
- }
- return prefixLength + suffixLength;
- }
- }
-
- /**
* TODO: Like above, this belongs with the rest of the SimpleFormatterImpl code.
* I put it here so that the SimpleFormatter uses in FormattedStringBuilder are near each other.
*
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 a961741b8..b9e70750f 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
@@ -13,7 +13,7 @@ import android.icu.impl.StringSegment;
import android.icu.impl.number.AffixPatternProvider;
import android.icu.impl.number.AffixUtils;
import android.icu.impl.number.PatternStringUtils;
-import android.icu.number.NumberFormatter.SignDisplay;
+import android.icu.impl.number.PatternStringUtils.PatternSignType;
/**
* @author sffc
@@ -92,20 +92,27 @@ public class AffixMatcher implements NumberParseMatcher {
StringBuilder sb = new StringBuilder();
ArrayList<AffixMatcher> matchers = new ArrayList<>(6);
boolean includeUnpaired = 0 != (parseFlags & ParsingUtils.PARSE_FLAG_INCLUDE_UNPAIRED_AFFIXES);
- SignDisplay signDisplay = (0 != (parseFlags & ParsingUtils.PARSE_FLAG_PLUS_SIGN_ALLOWED))
- ? SignDisplay.ALWAYS
- : SignDisplay.AUTO;
AffixPatternMatcher posPrefix = null;
AffixPatternMatcher posSuffix = null;
// Pre-process the affix strings to resolve LDML rules like sign display.
- for (int signum = 1; signum >= -1; signum--) {
+ for (PatternSignType type : PatternSignType.VALUES) {
+
+ // Skip affixes in some cases
+ if (type == PatternSignType.POS
+ && 0 != (parseFlags & ParsingUtils.PARSE_FLAG_PLUS_SIGN_ALLOWED)) {
+ continue;
+ }
+ if (type == PatternSignType.POS_SIGN
+ && 0 == (parseFlags & ParsingUtils.PARSE_FLAG_PLUS_SIGN_ALLOWED)) {
+ continue;
+ }
+
// Generate Prefix
PatternStringUtils.patternInfoToStringBuilder(patternInfo,
true,
- signum,
- signDisplay,
+ type,
StandardPlural.OTHER,
false,
sb);
@@ -115,15 +122,14 @@ public class AffixMatcher implements NumberParseMatcher {
// Generate Suffix
PatternStringUtils.patternInfoToStringBuilder(patternInfo,
false,
- signum,
- signDisplay,
+ type,
StandardPlural.OTHER,
false,
sb);
AffixPatternMatcher suffix = AffixPatternMatcher
.fromAffixPattern(sb.toString(), factory, parseFlags);
- if (signum == 1) {
+ if (type == PatternSignType.POS) {
posPrefix = prefix;
posSuffix = suffix;
} else if (Objects.equals(prefix, posPrefix) && Objects.equals(suffix, posSuffix)) {
@@ -132,17 +138,17 @@ public class AffixMatcher implements NumberParseMatcher {
}
// Flags for setting in the ParsedNumber; the token matchers may add more.
- int flags = (signum == -1) ? ParsedNumber.FLAG_NEGATIVE : 0;
+ int flags = (type == PatternSignType.NEG) ? ParsedNumber.FLAG_NEGATIVE : 0;
// Note: it is indeed possible for posPrefix and posSuffix to both be null.
// We still need to add that matcher for strict mode to work.
matchers.add(getInstance(prefix, suffix, flags));
if (includeUnpaired && prefix != null && suffix != null) {
// The following if statements are designed to prevent adding two identical matchers.
- if (signum == 1 || !Objects.equals(prefix, posPrefix)) {
+ if (type == PatternSignType.POS || !Objects.equals(prefix, posPrefix)) {
matchers.add(getInstance(prefix, null, flags));
}
- if (signum == 1 || !Objects.equals(suffix, posSuffix)) {
+ if (type == PatternSignType.POS || !Objects.equals(suffix, posSuffix)) {
matchers.add(getInstance(null, suffix, flags));
}
}
diff --git a/android_icu4j/src/main/java/android/icu/impl/number/parse/NumberParserImpl.java b/android_icu4j/src/main/java/android/icu/impl/number/parse/NumberParserImpl.java
index 48a71acce..5b847dd65 100644
--- a/android_icu4j/src/main/java/android/icu/impl/number/parse/NumberParserImpl.java
+++ b/android_icu4j/src/main/java/android/icu/impl/number/parse/NumberParserImpl.java
@@ -11,7 +11,6 @@ import java.util.List;
import android.icu.impl.StringSegment;
import android.icu.impl.number.AffixPatternProvider;
import android.icu.impl.number.AffixUtils;
-import android.icu.impl.number.CurrencyPluralInfoAffixProvider;
import android.icu.impl.number.CustomSymbolCurrency;
import android.icu.impl.number.DecimalFormatProperties;
import android.icu.impl.number.DecimalFormatProperties.ParseMode;
@@ -140,12 +139,7 @@ public class NumberParserImpl {
boolean parseCurrency) {
ULocale locale = symbols.getULocale();
- AffixPatternProvider affixProvider;
- if (properties.getCurrencyPluralInfo() == null) {
- affixProvider = new PropertiesAffixPatternProvider(properties);
- } else {
- affixProvider = new CurrencyPluralInfoAffixProvider(properties.getCurrencyPluralInfo(), properties);
- }
+ AffixPatternProvider affixProvider = PropertiesAffixPatternProvider.forProperties(properties);
Currency currency = CustomSymbolCurrency.resolve(properties.getCurrency(), locale, symbols);
ParseMode parseMode = properties.getParseMode();
if (parseMode == null) {
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 d0c701291..02aa21e47 100644
--- a/android_icu4j/src/main/java/android/icu/number/CompactNotation.java
+++ b/android_icu4j/src/main/java/android/icu/number/CompactNotation.java
@@ -66,9 +66,10 @@ public class CompactNotation extends Notation {
CompactType compactType,
PluralRules rules,
MutablePatternModifier buildReference,
+ boolean safe,
MicroPropsGenerator parent) {
// TODO: Add a data cache? It would be keyed by locale, nsName, compact type, and compact style.
- return new CompactHandler(this, locale, nsName, compactType, rules, buildReference, parent);
+ return new CompactHandler(this, locale, nsName, compactType, rules, buildReference, safe, parent);
}
private static class CompactHandler implements MicroPropsGenerator {
@@ -76,6 +77,7 @@ public class CompactNotation extends Notation {
final PluralRules rules;
final MicroPropsGenerator parent;
final Map<String, ImmutablePatternModifier> precomputedMods;
+ final MutablePatternModifier unsafePatternModifier;
final CompactData data;
private CompactHandler(
@@ -85,6 +87,7 @@ public class CompactNotation extends Notation {
CompactType compactType,
PluralRules rules,
MutablePatternModifier buildReference,
+ boolean safe,
MicroPropsGenerator parent) {
this.rules = rules;
this.parent = parent;
@@ -94,13 +97,15 @@ public class CompactNotation extends Notation {
} else {
data.populate(notation.compactCustomData);
}
- if (buildReference != null) {
+ if (safe) {
// Safe code path
precomputedMods = new HashMap<>();
precomputeAllModifiers(buildReference);
+ unsafePatternModifier = null;
} else {
// Unsafe code path
precomputedMods = null;
+ unsafePatternModifier = buildReference;
}
}
@@ -123,11 +128,12 @@ public class CompactNotation extends Notation {
// Treat zero, NaN, and infinity as if they had magnitude 0
int magnitude;
+ int multiplier = 0;
if (quantity.isZeroish()) {
magnitude = 0;
micros.rounder.apply(quantity);
} else {
- int multiplier = micros.rounder.chooseMultiplierAndApply(quantity, data);
+ multiplier = micros.rounder.chooseMultiplierAndApply(quantity, data);
magnitude = quantity.isZeroish() ? 0 : quantity.getMagnitude();
magnitude -= multiplier;
}
@@ -145,13 +151,19 @@ public class CompactNotation extends Notation {
} else {
// Unsafe code path.
// Overwrite the PatternInfo in the existing modMiddle.
- assert micros.modMiddle instanceof MutablePatternModifier;
ParsedPatternInfo patternInfo = PatternStringParser.parseToPatternInfo(patternString);
- ((MutablePatternModifier) micros.modMiddle).setPatternInfo(patternInfo, NumberFormat.Field.COMPACT);
+ unsafePatternModifier.setPatternInfo(patternInfo, NumberFormat.Field.COMPACT);
+ unsafePatternModifier.setNumberProperties(quantity.signum(), null);
+ micros.modMiddle = unsafePatternModifier;
}
+ // Change the exponent only after we select appropriate plural form
+ // for formatting purposes so that we preserve expected formatted
+ // string behavior.
+ quantity.adjustExponent(-1 * multiplier);
+
// We already performed rounding. Do not perform it again.
- micros.rounder = Precision.constructPassThrough();
+ micros.rounder = null;
return micros;
}
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 04790ff06..3ea745bef 100644
--- a/android_icu4j/src/main/java/android/icu/number/FormattedNumber.java
+++ b/android_icu4j/src/main/java/android/icu/number/FormattedNumber.java
@@ -5,8 +5,6 @@ package android.icu.number;
import java.math.BigDecimal;
import java.text.AttributedCharacterIterator;
-import java.text.FieldPosition;
-import java.util.Arrays;
import android.icu.impl.FormattedStringBuilder;
import android.icu.impl.FormattedValueStringBuilderImpl;
@@ -44,7 +42,7 @@ public class FormattedNumber implements FormattedValue {
/**
* {@inheritDoc}
*
- * @hide draft / provisional / internal are hidden on Android
+ * @hide Hide new API in Android temporarily
*/
@Override
public int length() {
@@ -54,7 +52,7 @@ public class FormattedNumber implements FormattedValue {
/**
* {@inheritDoc}
*
- * @hide draft / provisional / internal are hidden on Android
+ * @hide Hide new API in Android temporarily
*/
@Override
public char charAt(int index) {
@@ -64,7 +62,7 @@ public class FormattedNumber implements FormattedValue {
/**
* {@inheritDoc}
*
- * @hide draft / provisional / internal are hidden on Android
+ * @hide Hide new API in Android temporarily
*/
@Override
public CharSequence subSequence(int start, int end) {
@@ -84,7 +82,7 @@ public class FormattedNumber implements FormattedValue {
/**
* {@inheritDoc}
*
- * @hide draft / provisional / internal are hidden on Android
+ * @hide Hide new API in Android temporarily
*/
@Override
public boolean nextPosition(ConstrainedFieldPosition cfpos) {
@@ -100,43 +98,6 @@ public class FormattedNumber implements FormattedValue {
}
/**
- * Determines the start (inclusive) and end (exclusive) indices of the next occurrence of the
- * given <em>field</em> in the output string. This allows you to determine the locations of,
- * for example, the integer part, fraction part, or symbols.
- * <p>
- * This is a simpler but less powerful alternative to {@link #nextPosition}.
- * <p>
- * If a field occurs just once, calling this method will find that occurrence and return it. If a
- * field occurs multiple times, this method may be called repeatedly with the following pattern:
- *
- * <pre>
- * FieldPosition fpos = new FieldPosition(NumberFormat.Field.GROUPING_SEPARATOR);
- * while (formattedNumber.nextFieldPosition(fpos, status)) {
- * // do something with fpos.
- * }
- * </pre>
- * <p>
- * This method is useful if you know which field to query. If you want all available field position
- * information, use {@link #nextPosition} or {@link #toCharacterIterator()}.
- *
- * @param fieldPosition
- * Input+output variable. On input, the "field" property determines which field to look
- * up, and the "beginIndex" and "endIndex" properties determine where to begin the search.
- * On output, the "beginIndex" is set to the beginning of the first occurrence of the
- * field with either begin or end indices after the input indices, "endIndex" is set to
- * the end of that occurrence of the field (exclusive index). If a field position is not
- * found, the method returns FALSE and the FieldPosition may or may not be changed.
- * @return true if a new occurrence of the field was found; false otherwise.
- * @see android.icu.text.NumberFormat.Field
- * @see NumberFormatter
- * @hide draft / provisional / internal are hidden on Android
- */
- public boolean nextFieldPosition(FieldPosition fieldPosition) {
- fq.populateUFieldPosition(fieldPosition);
- return FormattedValueStringBuilderImpl.nextFieldPosition(string, fieldPosition);
- }
-
- /**
* Export the formatted number as a BigDecimal. This endpoint is useful for obtaining the exact
* number being printed after scaling and rounding have been applied by the number formatting
* pipeline.
@@ -156,39 +117,4 @@ public class FormattedNumber implements FormattedValue {
public IFixedDecimal getFixedDecimal() {
return fq;
}
-
- /**
- * {@inheritDoc}
- *
- * @hide draft / provisional / internal are hidden on Android
- */
- @Override
- public int hashCode() {
- // FormattedStringBuilder and BigDecimal are mutable, so we can't call
- // #equals() or #hashCode() on them directly.
- return Arrays.hashCode(string.toCharArray())
- ^ Arrays.hashCode(string.toFieldArray())
- ^ fq.toBigDecimal().hashCode();
- }
-
- /**
- * {@inheritDoc}
- *
- * @hide draft / provisional / internal are hidden on Android
- */
- @Override
- public boolean equals(Object other) {
- if (this == other)
- return true;
- if (other == null)
- return false;
- if (!(other instanceof FormattedNumber))
- return false;
- // FormattedStringBuilder and BigDecimal are mutable, so we can't call
- // #equals() or #hashCode() on them directly.
- FormattedNumber _other = (FormattedNumber) other;
- return Arrays.equals(string.toCharArray(), _other.string.toCharArray())
- && Arrays.equals(string.toFieldArray(), _other.string.toFieldArray())
- && fq.toBigDecimal().equals(_other.fq.toBigDecimal());
- }
} \ No newline at end of file
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 c9a6183ad..a17e56af6 100644
--- a/android_icu4j/src/main/java/android/icu/number/FormattedNumberRange.java
+++ b/android_icu4j/src/main/java/android/icu/number/FormattedNumberRange.java
@@ -6,7 +6,6 @@ package android.icu.number;
import java.io.IOException;
import java.math.BigDecimal;
import java.text.AttributedCharacterIterator;
-import java.text.FieldPosition;
import java.util.Arrays;
import android.icu.impl.FormattedStringBuilder;
@@ -67,7 +66,7 @@ public class FormattedNumberRange implements FormattedValue {
/**
* {@inheritDoc}
*
- * @hide draft / provisional / internal are hidden on Android
+ * @hide Hide new API in Android temporarily
*/
@Override
public char charAt(int index) {
@@ -77,7 +76,7 @@ public class FormattedNumberRange implements FormattedValue {
/**
* {@inheritDoc}
*
- * @hide draft / provisional / internal are hidden on Android
+ * @hide Hide new API in Android temporarily
*/
@Override
public int length() {
@@ -87,7 +86,7 @@ public class FormattedNumberRange implements FormattedValue {
/**
* {@inheritDoc}
*
- * @hide draft / provisional / internal are hidden on Android
+ * @hide Hide new API in Android temporarily
*/
@Override
public CharSequence subSequence(int start, int end) {
@@ -97,7 +96,7 @@ public class FormattedNumberRange implements FormattedValue {
/**
* {@inheritDoc}
*
- * @hide draft / provisional / internal are hidden on Android
+ * @hide Hide new API in Android temporarily
*/
@Override
public boolean nextPosition(ConstrainedFieldPosition cfpos) {
@@ -105,38 +104,6 @@ public class FormattedNumberRange implements FormattedValue {
}
/**
- * Determines the start (inclusive) and end (exclusive) indices of the next occurrence of the given
- * <em>field</em> in the output string. This allows you to determine the locations of, for example,
- * the integer part, fraction part, or symbols.
- * <p>
- * If both sides of the range have the same field, the field will occur twice, once before the range separator and
- * once after the range separator, if applicable.
- * <p>
- * If a field occurs just once, calling this method will find that occurrence and return it. If a field occurs
- * multiple times, this method may be called repeatedly with the following pattern:
- *
- * <pre>
- * FieldPosition fpos = new FieldPosition(NumberFormat.Field.INTEGER);
- * while (formattedNumberRange.nextFieldPosition(fpos, status)) {
- * // do something with fpos.
- * }
- * </pre>
- * <p>
- * This method is useful if you know which field to query. If you want all available field position information, use
- * {@link #toCharacterIterator()}.
- *
- * @param fieldPosition
- * Input+output variable. See {@link FormattedNumber#nextFieldPosition(FieldPosition)}.
- * @return true if a new occurrence of the field was found; false otherwise.
- * @see android.icu.text.NumberFormat.Field
- * @see NumberRangeFormatter
- * @hide draft / provisional / internal are hidden on Android
- */
- public boolean nextFieldPosition(FieldPosition fieldPosition) {
- return FormattedValueStringBuilderImpl.nextFieldPosition(string, fieldPosition);
- }
-
- /**
* {@inheritDoc}
*/
@Override
@@ -184,7 +151,7 @@ public class FormattedNumberRange implements FormattedValue {
/**
* {@inheritDoc}
*
- * @hide draft / provisional / internal are hidden on Android
+ * @hide Hide new API in Android temporarily
*/
@Override
public int hashCode() {
@@ -197,7 +164,7 @@ public class FormattedNumberRange implements FormattedValue {
/**
* {@inheritDoc}
*
- * @hide draft / provisional / internal are hidden on Android
+ * @hide Hide new API in Android temporarily
*/
@Override
public boolean equals(Object other) {
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 99acd6a32..181831fbb 100644
--- a/android_icu4j/src/main/java/android/icu/number/LocalizedNumberFormatter.java
+++ b/android_icu4j/src/main/java/android/icu/number/LocalizedNumberFormatter.java
@@ -93,20 +93,11 @@ public class LocalizedNumberFormatter extends NumberFormatterSettings<LocalizedN
* @see NumberFormatter
*/
public FormattedNumber format(Measure input) {
+ DecimalQuantity fq = new DecimalQuantity_DualStorageBCD(input.getNumber());
MeasureUnit unit = input.getUnit();
- Number number = input.getNumber();
- // Use this formatter if possible
- if (Objects.equals(resolve().unit, unit)) {
- return format(number);
- }
- // This mechanism saves the previously used unit, so if the user calls this method with the
- // same unit multiple times in a row, they get a more efficient code path.
- LocalizedNumberFormatter withUnit = savedWithUnit;
- if (withUnit == null || !Objects.equals(withUnit.resolve().unit, unit)) {
- withUnit = new LocalizedNumberFormatter(this, KEY_UNIT, unit);
- savedWithUnit = withUnit;
- }
- return withUnit.format(number);
+ FormattedStringBuilder string = new FormattedStringBuilder();
+ formatImpl(fq, unit, string);
+ return new FormattedNumber(string, fq);
}
/**
@@ -157,6 +148,29 @@ public class LocalizedNumberFormatter extends NumberFormatterSettings<LocalizedN
}
/**
+ * Version of above for unit override.
+ *
+ * @deprecated ICU 67 This API is ICU internal only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public void formatImpl(DecimalQuantity fq, MeasureUnit unit, FormattedStringBuilder string) {
+ // Use this formatter if possible
+ if (Objects.equals(resolve().unit, unit)) {
+ formatImpl(fq, string);
+ return;
+ }
+ // This mechanism saves the previously used unit, so if the user calls this method with the
+ // same unit multiple times in a row, they get a more efficient code path.
+ LocalizedNumberFormatter withUnit = savedWithUnit;
+ if (withUnit == null || !Objects.equals(withUnit.resolve().unit, unit)) {
+ withUnit = new LocalizedNumberFormatter(this, KEY_UNIT, unit);
+ savedWithUnit = withUnit;
+ }
+ withUnit.formatImpl(fq, string);
+ }
+
+ /**
* @deprecated This API is ICU internal only. Use {@link FormattedNumber#nextPosition}
* for related functionality.
* @hide draft / provisional / internal are hidden on Android
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 11782924e..1b20071a9 100644
--- a/android_icu4j/src/main/java/android/icu/number/NumberFormatter.java
+++ b/android_icu4j/src/main/java/android/icu/number/NumberFormatter.java
@@ -127,8 +127,10 @@ public final class NumberFormatter {
FULL_NAME,
/**
- * Use the three-digit ISO XXX code in place of the symbol for displaying currencies. The
- * behavior of this option is currently undefined for use with measure units.
+ * Use the three-digit ISO XXX code in place of the symbol for displaying currencies.
+ *
+ * <p>
+ * Behavior of this option with non-currency units is not defined at this time.
*
* <p>
* In CLDR, this option corresponds to the "¤¤" placeholder for currencies.
@@ -138,6 +140,30 @@ public final class NumberFormatter {
ISO_CODE,
/**
+ * Use the formal variant of the currency symbol; for example, "NT$" for the New Taiwan
+ * dollar in zh-TW.
+ *
+ * <p>
+ * 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,
+
+ /**
+ * Use the alternate variant of the currency symbol; for example, "TL" for the Turkish
+ * lira (TRY).
+ *
+ * <p>
+ * 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,
+
+ /**
* Format the number according to the specified unit, but do not display the unit. For
* currencies, apply monetary symbols and formats as with SHORT, but omit the currency symbol.
* For measure units, the behavior is equivalent to not specifying the unit at all.
@@ -309,7 +335,7 @@ public final class NumberFormatter {
/**
* Show the minus sign on negative numbers and the plus sign on positive numbers. Do not show a
- * sign on zero or NaN, unless the sign bit is set (-0.0 gets a sign).
+ * sign on zero, numbers that round to zero, or NaN.
*
* @see NumberFormatter
*/
@@ -317,9 +343,8 @@ public final class NumberFormatter {
/**
* Use the locale-dependent accounting format on negative numbers, and show the plus sign on
- * positive numbers. Do not show a sign on zero or NaN, unless the sign bit is set (-0.0 gets a
- * sign). For more information on the accounting format, see the ACCOUNTING sign display
- * strategy.
+ * positive numbers. Do not show a sign on zero, numbers that round to zero, or NaN. For more
+ * information on the accounting format, see the ACCOUNTING sign display strategy.
*
* @see NumberFormatter
*/
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 5971d831a..0ec562e28 100644
--- a/android_icu4j/src/main/java/android/icu/number/NumberFormatterImpl.java
+++ b/android_icu4j/src/main/java/android/icu/number/NumberFormatterImpl.java
@@ -16,6 +16,7 @@ import android.icu.impl.number.MicroProps;
import android.icu.impl.number.MicroPropsGenerator;
import android.icu.impl.number.MultiplierFormatHandler;
import android.icu.impl.number.MutablePatternModifier;
+import android.icu.impl.number.MutablePatternModifier.ImmutablePatternModifier;
import android.icu.impl.number.Padder;
import android.icu.impl.number.PatternStringParser;
import android.icu.impl.number.PatternStringParser.ParsedPatternInfo;
@@ -96,7 +97,6 @@ class NumberFormatterImpl {
*/
public MicroProps preProcess(DecimalQuantity inValue) {
MicroProps micros = microPropsGenerator.processQuantity(inValue);
- micros.rounder.apply(inValue);
if (micros.integerWidth.maxInt == -1) {
inValue.setMinInteger(micros.integerWidth.minInt);
} else {
@@ -110,7 +110,6 @@ class NumberFormatterImpl {
MicroProps micros = new MicroProps(false);
MicroPropsGenerator microPropsGenerator = macrosToMicroGenerator(macros, micros, false);
micros = microPropsGenerator.processQuantity(inValue);
- micros.rounder.apply(inValue);
if (micros.integerWidth.maxInt == -1) {
inValue.setMinInteger(micros.integerWidth.minInt);
} else {
@@ -335,10 +334,9 @@ class NumberFormatterImpl {
} else {
patternMod.setSymbols(micros.symbols, currency, unitWidth, null);
}
+ ImmutablePatternModifier immPatternMod = null;
if (safe) {
- chain = patternMod.createImmutableAndChain(chain);
- } else {
- chain = patternMod.addToChain(chain);
+ immPatternMod = patternMod.createImmutable();
}
// Outer modifier (CLDR units and currency long names)
@@ -361,8 +359,6 @@ class NumberFormatterImpl {
}
// Compact notation
- // NOTE: Compact notation can (but might not) override the middle modifier and rounding.
- // It therefore needs to go at the end of the chain.
if (macros.notation instanceof CompactNotation) {
if (rules == null) {
// Lazily create PluralRules
@@ -375,10 +371,18 @@ class NumberFormatterImpl {
micros.nsName,
compactType,
rules,
- safe ? patternMod : null,
+ patternMod,
+ safe,
chain);
}
+ // Always add the pattern modifier as the last element of the chain.
+ if (safe) {
+ chain = immPatternMod.addToChain(chain);
+ } else {
+ chain = patternMod.addToChain(chain);
+ }
+
return chain;
}
@@ -434,6 +438,19 @@ class NumberFormatterImpl {
// Add the fraction digits
length += writeFractionDigits(micros, quantity, string, length + index);
+
+ if (length == 0) {
+ // Force output of the digit for value 0
+ if (micros.symbols.getCodePointZero() != -1) {
+ length += string.insertCodePoint(index,
+ micros.symbols.getCodePointZero(),
+ NumberFormat.Field.INTEGER);
+ } else {
+ length += string.insert(index,
+ micros.symbols.getDigitStringsLocal()[0],
+ NumberFormat.Field.INTEGER);
+ }
+ }
}
return length;
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 d86921c00..f76e71d99 100644
--- a/android_icu4j/src/main/java/android/icu/number/NumberPropertyMapper.java
+++ b/android_icu4j/src/main/java/android/icu/number/NumberPropertyMapper.java
@@ -7,7 +7,6 @@ import java.math.BigDecimal;
import java.math.MathContext;
import android.icu.impl.number.AffixPatternProvider;
-import android.icu.impl.number.CurrencyPluralInfoAffixProvider;
import android.icu.impl.number.CustomSymbolCurrency;
import android.icu.impl.number.DecimalFormatProperties;
import android.icu.impl.number.Grouper;
@@ -105,12 +104,7 @@ final class NumberPropertyMapper {
// AFFIXES //
/////////////
- AffixPatternProvider affixProvider;
- if (properties.getCurrencyPluralInfo() == null) {
- affixProvider = new PropertiesAffixPatternProvider(properties);
- } else {
- affixProvider = new CurrencyPluralInfoAffixProvider(properties.getCurrencyPluralInfo(), properties);
- }
+ AffixPatternProvider affixProvider = PropertiesAffixPatternProvider.forProperties(properties);
macros.affixProvider = affixProvider;
///////////
@@ -162,10 +156,8 @@ final class NumberPropertyMapper {
}
// Validate min/max int/frac.
// For backwards compatibility, minimum overrides maximum if the two conflict.
- // The following logic ensures that there is always a minimum of at least one digit.
if (minInt == 0 && maxFrac != 0) {
- // Force a digit after the decimal point.
- minFrac = minFrac <= 0 ? 1 : minFrac;
+ minFrac = (minFrac < 0 || (minFrac == 0 && maxInt == 0)) ? 1 : minFrac;
maxFrac = maxFrac < 0 ? -1 : maxFrac < minFrac ? minFrac : maxFrac;
minInt = 0;
maxInt = maxInt < 0 ? -1 : maxInt > RoundingUtils.MAX_INT_FRAC_SIG ? -1 : maxInt;
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 114e541ee..8dfb9ecf7 100644
--- a/android_icu4j/src/main/java/android/icu/number/NumberSkeletonImpl.java
+++ b/android_icu4j/src/main/java/android/icu/number/NumberSkeletonImpl.java
@@ -54,6 +54,7 @@ class NumberSkeletonImpl {
STATE_INCREMENT_PRECISION,
STATE_MEASURE_UNIT,
STATE_PER_MEASURE_UNIT,
+ STATE_IDENTIFIER_UNIT,
STATE_CURRENCY_UNIT,
STATE_INTEGER_WIDTH,
STATE_NUMBERING_SYSTEM,
@@ -77,6 +78,7 @@ class NumberSkeletonImpl {
STEM_BASE_UNIT,
STEM_PERCENT,
STEM_PERMILLE,
+ STEM_PERCENT_100, // concise-only
STEM_PRECISION_INTEGER,
STEM_PRECISION_UNLIMITED,
STEM_PRECISION_CURRENCY_STANDARD,
@@ -99,6 +101,8 @@ class NumberSkeletonImpl {
STEM_UNIT_WIDTH_SHORT,
STEM_UNIT_WIDTH_FULL_NAME,
STEM_UNIT_WIDTH_ISO_CODE,
+ STEM_UNIT_WIDTH_FORMAL,
+ STEM_UNIT_WIDTH_VARIANT,
STEM_UNIT_WIDTH_HIDDEN,
STEM_SIGN_AUTO,
STEM_SIGN_ALWAYS,
@@ -114,12 +118,24 @@ class NumberSkeletonImpl {
STEM_PRECISION_INCREMENT,
STEM_MEASURE_UNIT,
STEM_PER_MEASURE_UNIT,
+ STEM_UNIT,
STEM_CURRENCY,
STEM_INTEGER_WIDTH,
STEM_NUMBERING_SYSTEM,
STEM_SCALE,
};
+ /** Default wildcard char, accepted on input and printed in output */
+ static final char WILDCARD_CHAR = '*';
+
+ /** Alternative wildcard char, accept on input but not printed in output */
+ static final char ALT_WILDCARD_CHAR = '+';
+
+ /** Checks whether the char is a wildcard on input */
+ static boolean isWildcardChar(char c) {
+ return c == WILDCARD_CHAR || c == ALT_WILDCARD_CHAR;
+ }
+
/** For mapping from ordinal back to StemEnum in Java. */
static final StemEnum[] STEM_ENUM_VALUES = StemEnum.values();
@@ -160,6 +176,8 @@ class NumberSkeletonImpl {
b.add("unit-width-short", StemEnum.STEM_UNIT_WIDTH_SHORT.ordinal());
b.add("unit-width-full-name", StemEnum.STEM_UNIT_WIDTH_FULL_NAME.ordinal());
b.add("unit-width-iso-code", StemEnum.STEM_UNIT_WIDTH_ISO_CODE.ordinal());
+ b.add("unit-width-formal", StemEnum.STEM_UNIT_WIDTH_FORMAL.ordinal());
+ b.add("unit-width-variant", StemEnum.STEM_UNIT_WIDTH_VARIANT.ordinal());
b.add("unit-width-hidden", StemEnum.STEM_UNIT_WIDTH_HIDDEN.ordinal());
b.add("sign-auto", StemEnum.STEM_SIGN_AUTO.ordinal());
b.add("sign-always", StemEnum.STEM_SIGN_ALWAYS.ordinal());
@@ -175,11 +193,27 @@ class NumberSkeletonImpl {
b.add("precision-increment", StemEnum.STEM_PRECISION_INCREMENT.ordinal());
b.add("measure-unit", StemEnum.STEM_MEASURE_UNIT.ordinal());
b.add("per-measure-unit", StemEnum.STEM_PER_MEASURE_UNIT.ordinal());
+ b.add("unit", StemEnum.STEM_UNIT.ordinal());
b.add("currency", StemEnum.STEM_CURRENCY.ordinal());
b.add("integer-width", StemEnum.STEM_INTEGER_WIDTH.ordinal());
b.add("numbering-system", StemEnum.STEM_NUMBERING_SYSTEM.ordinal());
b.add("scale", StemEnum.STEM_SCALE.ordinal());
+ // Section 3 (concise tokens):
+ b.add("K", StemEnum.STEM_COMPACT_SHORT.ordinal());
+ b.add("KK", StemEnum.STEM_COMPACT_LONG.ordinal());
+ b.add("%", StemEnum.STEM_PERCENT.ordinal());
+ b.add("%x100", StemEnum.STEM_PERCENT_100.ordinal());
+ b.add(",_", StemEnum.STEM_GROUP_OFF.ordinal());
+ b.add(",?", StemEnum.STEM_GROUP_MIN2.ordinal());
+ b.add(",!", StemEnum.STEM_GROUP_ON_ALIGNED.ordinal());
+ b.add("+!", StemEnum.STEM_SIGN_ALWAYS.ordinal());
+ b.add("+_", StemEnum.STEM_SIGN_NEVER.ordinal());
+ b.add("()", StemEnum.STEM_SIGN_ACCOUNTING.ordinal());
+ 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());
+
// Build the CharsTrie
// TODO: Use SLOW or FAST here?
return b.buildCharSequence(StringTrieBuilder.Option.FAST).toString();
@@ -286,6 +320,10 @@ class NumberSkeletonImpl {
return UnitWidth.FULL_NAME;
case STEM_UNIT_WIDTH_ISO_CODE:
return UnitWidth.ISO_CODE;
+ case STEM_UNIT_WIDTH_FORMAL:
+ return UnitWidth.FORMAL;
+ case STEM_UNIT_WIDTH_VARIANT:
+ return UnitWidth.VARIANT;
case STEM_UNIT_WIDTH_HIDDEN:
return UnitWidth.HIDDEN;
default:
@@ -399,6 +437,12 @@ class NumberSkeletonImpl {
case ISO_CODE:
sb.append("unit-width-iso-code");
break;
+ case FORMAL:
+ sb.append("unit-width-formal");
+ break;
+ case VARIANT:
+ sb.append("unit-width-variant");
+ break;
case HIDDEN:
sb.append("unit-width-hidden");
break;
@@ -605,6 +649,14 @@ class NumberSkeletonImpl {
checkNull(macros.precision, segment);
BlueprintHelpers.parseDigitsStem(segment, macros);
return ParseState.STATE_NULL;
+ case 'E':
+ checkNull(macros.notation, segment);
+ BlueprintHelpers.parseScientificStem(segment, macros);
+ return ParseState.STATE_NULL;
+ case '0':
+ checkNull(macros.notation, segment);
+ BlueprintHelpers.parseIntegerStem(segment, macros);
+ return ParseState.STATE_NULL;
}
// Now look at the stemsTrie, which is already be pointing at our stem.
@@ -642,6 +694,13 @@ class NumberSkeletonImpl {
macros.unit = StemToObject.unit(stem);
return ParseState.STATE_NULL;
+ case STEM_PERCENT_100:
+ checkNull(macros.scale, segment);
+ checkNull(macros.unit, segment);
+ macros.scale = Scale.powerOfTen(2);
+ macros.unit = NoUnit.PERCENT;
+ return ParseState.STATE_NULL;
+
case STEM_PRECISION_INTEGER:
case STEM_PRECISION_UNLIMITED:
case STEM_PRECISION_CURRENCY_STANDARD:
@@ -685,6 +744,8 @@ class NumberSkeletonImpl {
case STEM_UNIT_WIDTH_SHORT:
case STEM_UNIT_WIDTH_FULL_NAME:
case STEM_UNIT_WIDTH_ISO_CODE:
+ case STEM_UNIT_WIDTH_FORMAL:
+ case STEM_UNIT_WIDTH_VARIANT:
case STEM_UNIT_WIDTH_HIDDEN:
checkNull(macros.unitWidth, segment);
macros.unitWidth = StemToObject.unitWidth(stem);
@@ -721,6 +782,11 @@ class NumberSkeletonImpl {
checkNull(macros.perUnit, segment);
return ParseState.STATE_PER_MEASURE_UNIT;
+ case STEM_UNIT:
+ checkNull(macros.unit, segment);
+ checkNull(macros.perUnit, segment);
+ return ParseState.STATE_IDENTIFIER_UNIT;
+
case STEM_CURRENCY:
checkNull(macros.unit, segment);
return ParseState.STATE_CURRENCY_UNIT;
@@ -762,6 +828,9 @@ class NumberSkeletonImpl {
case STATE_PER_MEASURE_UNIT:
BlueprintHelpers.parseMeasurePerUnitOption(segment, macros);
return ParseState.STATE_NULL;
+ case STATE_IDENTIFIER_UNIT:
+ BlueprintHelpers.parseIdentifierUnitOption(segment, macros);
+ return ParseState.STATE_NULL;
case STATE_INCREMENT_PRECISION:
BlueprintHelpers.parseIncrementOption(segment, macros);
return ParseState.STATE_NULL;
@@ -883,7 +952,7 @@ class NumberSkeletonImpl {
/** @return Whether we successfully found and parsed an exponent width option. */
private static boolean parseExponentWidthOption(StringSegment segment, MacroProps macros) {
- if (segment.charAt(0) != '+') {
+ if (!isWildcardChar(segment.charAt(0))) {
return false;
}
int offset = 1;
@@ -904,7 +973,7 @@ class NumberSkeletonImpl {
}
private static void generateExponentWidthOption(int minExponentDigits, StringBuilder sb) {
- sb.append('+');
+ sb.append(WILDCARD_CHAR);
appendMultiple(sb, 'e', minExponentDigits);
}
@@ -971,7 +1040,7 @@ class NumberSkeletonImpl {
}
private static void parseMeasurePerUnitOption(StringSegment segment, MacroProps macros) {
- // A little bit of a hack: safe the current unit (numerator), call the main measure unit
+ // A little bit of a hack: save the current unit (numerator), call the main measure unit
// parsing code, put back the numerator unit, and put the new unit into per-unit.
MeasureUnit numerator = macros.unit;
parseMeasureUnitOption(segment, macros);
@@ -979,6 +1048,17 @@ class NumberSkeletonImpl {
macros.unit = numerator;
}
+ private static void parseIdentifierUnitOption(StringSegment segment, MacroProps macros) {
+ MeasureUnit[] units = MeasureUnit.parseCoreUnitIdentifier(segment.asString());
+ if (units == null) {
+ throw new SkeletonSyntaxException("Invalid core unit identifier", segment);
+ }
+ macros.unit = units[0];
+ if (units.length == 2) {
+ macros.perUnit = units[1];
+ }
+ }
+
private static void parseFractionStem(StringSegment segment, MacroProps macros) {
assert segment.charAt(0) == '.';
int offset = 1;
@@ -992,7 +1072,7 @@ class NumberSkeletonImpl {
}
}
if (offset < segment.length()) {
- if (segment.charAt(offset) == '+') {
+ if (isWildcardChar(segment.charAt(offset))) {
maxFrac = -1;
offset++;
} else {
@@ -1013,7 +1093,11 @@ class NumberSkeletonImpl {
}
// Use the public APIs to enforce bounds checking
if (maxFrac == -1) {
- macros.precision = Precision.minFraction(minFrac);
+ if (minFrac == 0) {
+ macros.precision = Precision.unlimited();
+ } else {
+ macros.precision = Precision.minFraction(minFrac);
+ }
} else {
macros.precision = Precision.minMaxFraction(minFrac, maxFrac);
}
@@ -1027,7 +1111,7 @@ class NumberSkeletonImpl {
sb.append('.');
appendMultiple(sb, '0', minFrac);
if (maxFrac == -1) {
- sb.append('+');
+ sb.append(WILDCARD_CHAR);
} else {
appendMultiple(sb, '#', maxFrac - minFrac);
}
@@ -1046,7 +1130,7 @@ class NumberSkeletonImpl {
}
}
if (offset < segment.length()) {
- if (segment.charAt(offset) == '+') {
+ if (isWildcardChar(segment.charAt(offset))) {
maxSig = -1;
offset++;
} else {
@@ -1076,12 +1160,77 @@ class NumberSkeletonImpl {
private static void generateDigitsStem(int minSig, int maxSig, StringBuilder sb) {
appendMultiple(sb, '@', minSig);
if (maxSig == -1) {
- sb.append('+');
+ sb.append(WILDCARD_CHAR);
} else {
appendMultiple(sb, '#', maxSig - minSig);
}
}
+ private static void parseScientificStem(StringSegment segment, MacroProps macros) {
+ assert(segment.charAt(0) == 'E');
+ block:
+ {
+ int offset = 1;
+ if (segment.length() == offset) {
+ break block;
+ }
+ boolean isEngineering = false;
+ if (segment.charAt(offset) == 'E') {
+ isEngineering = true;
+ offset++;
+ if (segment.length() == offset) {
+ break block;
+ }
+ }
+ SignDisplay signDisplay = SignDisplay.AUTO;
+ if (segment.charAt(offset) == '+') {
+ offset++;
+ if (segment.length() == offset) {
+ break block;
+ }
+ if (segment.charAt(offset) == '!') {
+ signDisplay = SignDisplay.ALWAYS;
+ } else if (segment.charAt(offset) == '?') {
+ signDisplay = SignDisplay.EXCEPT_ZERO;
+ } else {
+ break block;
+ }
+ offset++;
+ if (segment.length() == offset) {
+ break block;
+ }
+ }
+ int minDigits = 0;
+ for (; offset < segment.length(); offset++) {
+ if (segment.charAt(offset) != '0') {
+ break block;
+ }
+ minDigits++;
+ }
+ macros.notation = (isEngineering ? Notation.engineering() : Notation.scientific())
+ .withExponentSignDisplay(signDisplay)
+ .withMinExponentDigits(minDigits);
+ return;
+ }
+ throw new SkeletonSyntaxException("Invalid scientific stem", segment);
+ }
+
+ private static void parseIntegerStem(StringSegment segment, MacroProps macros) {
+ assert(segment.charAt(0) == '0');
+ int offset = 1;
+ for (; offset < segment.length(); offset++) {
+ if (segment.charAt(offset) != '0') {
+ offset--;
+ break;
+ }
+ }
+ if (offset < segment.length()) {
+ throw new SkeletonSyntaxException("Invalid integer stem", segment);
+ }
+ macros.integerWidth = IntegerWidth.zeroFillTo(offset);
+ return;
+ }
+
/** @return Whether we successfully found and parsed a frac-sig option. */
private static boolean parseFracSigOption(StringSegment segment, MacroProps macros) {
if (segment.charAt(0) != '@') {
@@ -1103,7 +1252,7 @@ class NumberSkeletonImpl {
// Invalid: @, @@, @@@
// Invalid: @@#, @@##, @@@#
if (offset < segment.length()) {
- if (segment.charAt(offset) == '+') {
+ if (isWildcardChar(segment.charAt(offset))) {
maxSig = -1;
offset++;
} else if (minSig > 1) {
@@ -1157,7 +1306,7 @@ class NumberSkeletonImpl {
int offset = 0;
int minInt = 0;
int maxInt;
- if (segment.charAt(0) == '+') {
+ if (isWildcardChar(segment.charAt(0))) {
maxInt = -1;
offset++;
} else {
@@ -1195,7 +1344,7 @@ class NumberSkeletonImpl {
private static void generateIntegerWidthOption(int minInt, int maxInt, StringBuilder sb) {
if (maxInt == -1) {
- sb.append('+');
+ sb.append(WILDCARD_CHAR);
} else {
appendMultiple(sb, '#', maxInt - minInt);
}
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 f60520cd2..e0dd17433 100644
--- a/android_icu4j/src/main/java/android/icu/number/Precision.java
+++ b/android_icu4j/src/main/java/android/icu/number/Precision.java
@@ -370,8 +370,6 @@ public abstract class Precision {
static final CurrencyRounderImpl MONETARY_STANDARD = new CurrencyRounderImpl(CurrencyUsage.STANDARD);
static final CurrencyRounderImpl MONETARY_CASH = new CurrencyRounderImpl(CurrencyUsage.CASH);
- static final PassThroughRounderImpl PASS_THROUGH = new PassThroughRounderImpl();
-
static Precision constructInfinite() {
return NONE;
}
@@ -460,10 +458,6 @@ public abstract class Precision {
return returnValue.withMode(base.mathContext);
}
- static Precision constructPassThrough() {
- return PASS_THROUGH;
- }
-
/**
* Returns a valid working Rounder. If the Rounder is a CurrencyRounder, applies the given currency.
* Otherwise, simply passes through the argument.
@@ -755,24 +749,6 @@ public abstract class Precision {
}
}
- static class PassThroughRounderImpl extends Precision {
-
- public PassThroughRounderImpl() {
- }
-
- @Override
- Precision createCopy() {
- PassThroughRounderImpl copy = new PassThroughRounderImpl();
- copy.mathContext = mathContext;
- return copy;
- }
-
- @Override
- public void apply(DecimalQuantity value) {
- // TODO: Assert that value has already been rounded
- }
- }
-
private static int getRoundingMagnitudeFraction(int maxFrac) {
if (maxFrac == -1) {
return Integer.MIN_VALUE;
diff --git a/android_icu4j/src/main/java/android/icu/number/ScientificNotation.java b/android_icu4j/src/main/java/android/icu/number/ScientificNotation.java
index 145ecb1cb..591703c28 100644
--- a/android_icu4j/src/main/java/android/icu/number/ScientificNotation.java
+++ b/android_icu4j/src/main/java/android/icu/number/ScientificNotation.java
@@ -190,8 +190,13 @@ public class ScientificNotation extends Notation {
micros.modInner = this;
}
+ // Change the exponent only after we select appropriate plural form
+ // for formatting purposes so that we preserve expected formatted
+ // string behavior.
+ quantity.adjustExponent(exponent);
+
// We already performed rounding. Do not perform it again.
- micros.rounder = Precision.constructPassThrough();
+ micros.rounder = null;
return micros;
}
diff --git a/android_icu4j/src/main/java/android/icu/text/ChineseDateFormat.java b/android_icu4j/src/main/java/android/icu/text/ChineseDateFormat.java
index 7b6495c63..5e8c05a83 100644
--- a/android_icu4j/src/main/java/android/icu/text/ChineseDateFormat.java
+++ b/android_icu4j/src/main/java/android/icu/text/ChineseDateFormat.java
@@ -128,12 +128,13 @@ public class ChineseDateFormat extends SimpleDateFormat {
char ch, int count, int beginOffset,
int fieldNum, DisplayContext capitalizationContext,
FieldPosition pos,
+ char patternCharToOutput,
Calendar cal) {
// Logic to handle 'G' for chinese calendar is moved into SimpleDateFormat,
// and obsolete pattern char 'l' is now ignored in SimpleDateFormat, so we
// just use its implementation
- super.subFormat(buf, ch, count, beginOffset, fieldNum, capitalizationContext, pos, cal);
+ super.subFormat(buf, ch, count, beginOffset, fieldNum, capitalizationContext, pos, patternCharToOutput, cal);
// The following is no longer an issue for this subclass...
// TODO: add code to set FieldPosition for 'G' and 'l' fields. This
diff --git a/android_icu4j/src/main/java/android/icu/text/ConstrainedFieldPosition.java b/android_icu4j/src/main/java/android/icu/text/ConstrainedFieldPosition.java
index 3e7cb9ddf..139162fd0 100644
--- a/android_icu4j/src/main/java/android/icu/text/ConstrainedFieldPosition.java
+++ b/android_icu4j/src/main/java/android/icu/text/ConstrainedFieldPosition.java
@@ -17,7 +17,6 @@ import java.util.Objects;
*
* @author sffc
* @hide Only a subset of ICU is exposed in Android
- * @hide draft / provisional / internal are hidden on Android
*/
public class ConstrainedFieldPosition {
@@ -78,8 +77,6 @@ public class ConstrainedFieldPosition {
* Initializes a CategoryFieldPosition.
*
* By default, the CategoryFieldPosition has no iteration constraints.
- *
- * @hide draft / provisional / internal are hidden on Android
*/
public ConstrainedFieldPosition() {
reset();
@@ -90,8 +87,6 @@ public class ConstrainedFieldPosition {
*
* - Removes any constraints that may have been set on the instance.
* - Resets the iteration position.
- *
- * @hide draft / provisional / internal are hidden on Android
*/
public void reset() {
fConstraint = ConstraintType.NONE;
@@ -126,7 +121,6 @@ public class ConstrainedFieldPosition {
*
* @param field
* The field to fix when iterating.
- * @hide draft / provisional / internal are hidden on Android
*/
public void constrainField(Field field) {
if (field == null) {
@@ -158,7 +152,6 @@ public class ConstrainedFieldPosition {
*
* @param classConstraint
* The field class to fix when iterating.
- * @hide draft / provisional / internal are hidden on Android
*/
public void constrainClass(Class<?> classConstraint) {
if (classConstraint == null) {
@@ -208,7 +201,6 @@ public class ConstrainedFieldPosition {
* FormattedValue#nextPosition returns TRUE.
*
* @return The field saved in the instance. See above for null conditions.
- * @hide draft / provisional / internal are hidden on Android
*/
public Field getField() {
return fField;
@@ -220,7 +212,6 @@ public class ConstrainedFieldPosition {
* The return value is well-defined only after FormattedValue#nextPosition returns TRUE.
*
* @return The start index saved in the instance.
- * @hide draft / provisional / internal are hidden on Android
*/
public int getStart() {
return fStart;
@@ -232,7 +223,6 @@ public class ConstrainedFieldPosition {
* The return value is well-defined only after FormattedValue#nextPosition returns TRUE.
*
* @return The end index saved in the instance.
- * @hide draft / provisional / internal are hidden on Android
*/
public int getLimit() {
return fLimit;
@@ -244,7 +234,6 @@ public class ConstrainedFieldPosition {
* The return value is well-defined only after FormattedValue#nextPosition returns TRUE.
*
* @return The value for the current position. Might be null.
- * @hide draft / provisional / internal are hidden on Android
*/
public Object getFieldValue() {
return fValue;
@@ -258,7 +247,6 @@ public class ConstrainedFieldPosition {
* Users of FormattedValue should not need to call this method.
*
* @return The current iteration context from {@link #setInt64IterationContext}.
- * @hide draft / provisional / internal are hidden on Android
*/
public long getInt64IterationContext() {
return fContext;
@@ -271,7 +259,6 @@ public class ConstrainedFieldPosition {
*
* @param context
* The new iteration context.
- * @hide draft / provisional / internal are hidden on Android
*/
public void setInt64IterationContext(long context) {
fContext = context;
@@ -293,7 +280,6 @@ public class ConstrainedFieldPosition {
* The new inclusive start index.
* @param limit
* The new exclusive end index.
- * @hide draft / provisional / internal are hidden on Android
*/
public void setState(Field field, Object value, int start, int limit) {
// Check matchesField only as an assertion (debug build)
@@ -314,7 +300,6 @@ public class ConstrainedFieldPosition {
* @param field The field to test.
* @param fieldValue The field value to test. Should be null if there is no value.
* @return Whether the field should be included given the constraints.
- * @hide draft / provisional / internal are hidden on Android
*/
public boolean matchesField(Field field, Object fieldValue) {
if (field == null) {
@@ -337,7 +322,6 @@ public class ConstrainedFieldPosition {
/**
* {@inheritDoc}
- * @hide draft / provisional / internal are hidden on Android
*/
@Override
public String toString() {
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 18c38957b..dcdcb7307 100644
--- a/android_icu4j/src/main/java/android/icu/text/CurrencyDisplayNames.java
+++ b/android_icu4j/src/main/java/android/icu/text/CurrencyDisplayNames.java
@@ -106,9 +106,10 @@ public abstract class CurrencyDisplayNames {
public abstract ULocale getULocale();
/**
- * Returns the symbol for the currency with the provided ISO code. If
- * there is no data for the ISO code, substitutes isoCode, or returns null
- * if noSubstitute was set in the factory method.
+ * Returns the symbol for the currency with the provided ISO code.
+ * <p>
+ * If there is no data for this symbol, substitutes isoCode,
+ * or returns null if noSubstitute was set in the factory method.
*
* @param isoCode the three-letter ISO code.
* @return the symbol.
@@ -117,7 +118,12 @@ public abstract class CurrencyDisplayNames {
/**
* Returns the narrow symbol for the currency with the provided ISO code.
- * If there is no data for narrow symbol, substitutes the default symbol,
+ * <p>
+ * The narrow currency symbol is similar to the regular currency symbol,
+ * but it always takes the shortest form;
+ * for example, "$" instead of "US$" for USD in en-CA.
+ * <p>
+ * If there is no data for this symbol, substitutes the default symbol,
* or returns null if noSubstitute was set in the factory method.
*
* @param isoCode the three-letter ISO code.
@@ -126,6 +132,37 @@ public abstract class CurrencyDisplayNames {
public abstract String getNarrowSymbol(String isoCode);
/**
+ * Returns the formal symbol for the currency with the provided ISO code.
+ * <p>
+ * The formal currency symbol is similar to the regular currency symbol,
+ * but it always takes the form used in formal settings such as banking;
+ * for example, "NT$" instead of "$" for TWD in zh-TW.
+ * <p>
+ * If there is no data for this symbol, substitutes the default symbol,
+ * or returns null if noSubstitute was set in the factory method.
+ *
+ * @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);
+
+ /**
+ * Returns the variant symbol for the currency with the provided ISO code.
+ * <p>
+ * The variant symbol for a currency is an alternative symbol that is not
+ * necessarily as widely used as the regular symbol.
+ * <p>
+ * If there is no data for variant symbol, substitutes the default symbol,
+ * or returns null if noSubstitute was set in the factory method.
+ *
+ * @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);
+
+ /**
* Returns the 'long name' for the currency with the provided ISO code.
* If there is no data for the ISO code, substitutes isoCode, or returns null
* if noSubstitute was set in the factory method.
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 ed226d329..62dd15515 100644
--- a/android_icu4j/src/main/java/android/icu/text/DateFormat.java
+++ b/android_icu4j/src/main/java/android/icu/text/DateFormat.java
@@ -491,6 +491,37 @@ public abstract class DateFormat extends UFormat {
*/
private EnumSet<BooleanAttribute> booleanAttributes = EnumSet.allOf(BooleanAttribute.class);
+ /**
+ * 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;
+ };
+
/*
* Capitalization setting, hoisted to DateFormat ICU 53
* Note that SimpleDateFormat serialization may call getContext/setContext to read/write
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 f465782ee..ce5a0f050 100644
--- a/android_icu4j/src/main/java/android/icu/text/DateFormatSymbols.java
+++ b/android_icu4j/src/main/java/android/icu/text/DateFormatSymbols.java
@@ -683,7 +683,7 @@ public class DateFormatSymbols implements Serializable, Cloneable {
*/
private static final Map<String, CapitalizationContextUsage> contextUsageTypeMap;
static {
- contextUsageTypeMap=new HashMap<String, CapitalizationContextUsage>();
+ contextUsageTypeMap=new HashMap<>();
contextUsageTypeMap.put("month-format-except-narrow", CapitalizationContextUsage.MONTH_FORMAT);
contextUsageTypeMap.put("month-standalone-except-narrow", CapitalizationContextUsage.MONTH_STANDALONE);
contextUsageTypeMap.put("month-narrow", CapitalizationContextUsage.MONTH_NARROW);
@@ -742,7 +742,7 @@ public class DateFormatSymbols implements Serializable, Cloneable {
/**
* <strong>[icu]</strong> Returns narrow era name strings. For example: "A" and "B".
* @return the narrow era strings.
- * @hide draft / provisional / internal are hidden on Android
+ * @hide Hide new API in Android temporarily
*/
@libcore.api.IntraCoreApi
public String[] getNarrowEras() {
@@ -752,7 +752,7 @@ public class DateFormatSymbols implements Serializable, Cloneable {
/**
* <strong>[icu]</strong> Sets narrow era name strings. For example: "A" and "B".
* @param newNarrowEras the new narrow era strings.
- * @hide draft / provisional / internal are hidden on Android
+ * @hide Hide new API in Android temporarily
*/
public void setNarrowEras(String[] newNarrowEras) {
narrowEras = duplicate(newNarrowEras);
@@ -1669,9 +1669,9 @@ public class DateFormatSymbols implements Serializable, Cloneable {
private static final class CalendarDataSink extends UResource.Sink {
// Data structures to store resources from the resource bundle
- Map<String, String[]> arrays = new TreeMap<String, String[]>();
- Map<String, Map<String, String>> maps = new TreeMap<String, Map<String, String>>();
- List<String> aliasPathPairs = new ArrayList<String>();
+ Map<String, String[]> arrays = new TreeMap<>();
+ Map<String, Map<String, String>> maps = new TreeMap<>();
+ List<String> aliasPathPairs = new ArrayList<>();
// Current and next calendar resource table which should be loaded
String currentCalendarType = null;
@@ -1726,7 +1726,7 @@ public class DateFormatSymbols implements Serializable, Cloneable {
// Whenever an alias to the next calendar (except gregorian) is encountered, register the
// calendar type it's pointing to
if (resourcesToVisitNext == null) {
- resourcesToVisitNext = new HashSet<String>();
+ resourcesToVisitNext = new HashSet<>();
}
resourcesToVisitNext.add(aliasRelativePath);
continue;
@@ -1814,7 +1814,7 @@ public class DateFormatSymbols implements Serializable, Cloneable {
if (value.getType() == ICUResourceBundle.STRING) {
// We are on a leaf, store the map elements into the stringMap
if (i == 0) {
- stringMap = new HashMap<String, String>();
+ stringMap = new HashMap<>();
maps.put(path, stringMap);
}
assert stringMap != null;
@@ -2106,7 +2106,7 @@ public class DateFormatSymbols implements Serializable, Cloneable {
ULocale uloc = rb.getULocale();
setLocale(uloc, uloc);
- capitalization = new HashMap<CapitalizationContextUsage,boolean[]>();
+ capitalization = new HashMap<>();
boolean[] noTransforms = new boolean[2];
noTransforms[0] = false;
noTransforms[1] = false;
@@ -2301,6 +2301,20 @@ public class DateFormatSymbols implements Serializable, Cloneable {
initializeData(locale, calType);
}
+ // Android patch (http://b/30464240) start: Add constructor taking a calendar type.
+ /**
+ * Variant of DateFormatSymbols(Calendar, ULocale) that takes the calendar type
+ * instead of a Calendar instance.
+ * @see #DateFormatSymbols(Calendar, Locale)
+ * @deprecated This API is ICU internal only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public DateFormatSymbols(ULocale locale, String calType) {
+ initializeData(locale, calType);
+ }
+ // Android patch end.
+
/**
* Fetches a custom calendar's DateFormatSymbols out of the given resource
* bundle. Symbols that are not overridden are inherited from the
diff --git a/android_icu4j/src/main/java/android/icu/text/DateIntervalFormat.java b/android_icu4j/src/main/java/android/icu/text/DateIntervalFormat.java
index 5be5b2134..8d9365665 100644
--- a/android_icu4j/src/main/java/android/icu/text/DateIntervalFormat.java
+++ b/android_icu4j/src/main/java/android/icu/text/DateIntervalFormat.java
@@ -231,7 +231,7 @@ import android.icu.util.UResourceBundle;
*
* // a series of set interval patterns.
* // Only ERA, YEAR, MONTH, DATE, DAY_OF_MONTH, DAY_OF_WEEK, AM_PM, HOUR, HOUR_OF_DAY,
- * MINUTE and SECOND are supported.
+ * MINUTE, SECOND and MILLISECOND are supported.
* dtitvinf.setIntervalPattern("yMMMd", Calendar.YEAR, "'y ~ y'");
* dtitvinf.setIntervalPattern("yMMMd", Calendar.MONTH, "yyyy 'diff' MMM d - MMM d");
* dtitvinf.setIntervalPattern("yMMMd", Calendar.DATE, "yyyy MMM d ~ d");
@@ -276,7 +276,6 @@ public class DateIntervalFormat extends UFormat {
* Not intended for public subclassing.
*
* @hide Only a subset of ICU is exposed in Android
- * @hide draft / provisional / internal are hidden on Android
*/
public static final class FormattedDateInterval implements FormattedValue {
private final String string;
@@ -289,7 +288,6 @@ public class DateIntervalFormat extends UFormat {
/**
* {@inheritDoc}
- * @hide draft / provisional / internal are hidden on Android
*/
@Override
public String toString() {
@@ -298,7 +296,6 @@ public class DateIntervalFormat extends UFormat {
/**
* {@inheritDoc}
- * @hide draft / provisional / internal are hidden on Android
*/
@Override
public int length() {
@@ -307,7 +304,6 @@ public class DateIntervalFormat extends UFormat {
/**
* {@inheritDoc}
- * @hide draft / provisional / internal are hidden on Android
*/
@Override
public char charAt(int index) {
@@ -316,7 +312,6 @@ public class DateIntervalFormat extends UFormat {
/**
* {@inheritDoc}
- * @hide draft / provisional / internal are hidden on Android
*/
@Override
public CharSequence subSequence(int start, int end) {
@@ -325,7 +320,6 @@ public class DateIntervalFormat extends UFormat {
/**
* {@inheritDoc}
- * @hide draft / provisional / internal are hidden on Android
*/
@Override
public <A extends Appendable> A appendTo(A appendable) {
@@ -334,7 +328,6 @@ public class DateIntervalFormat extends UFormat {
/**
* {@inheritDoc}
- * @hide draft / provisional / internal are hidden on Android
*/
@Override
public boolean nextPosition(ConstrainedFieldPosition cfpos) {
@@ -343,7 +336,6 @@ public class DateIntervalFormat extends UFormat {
/**
* {@inheritDoc}
- * @hide draft / provisional / internal are hidden on Android
*/
@Override
public AttributedCharacterIterator toCharacterIterator() {
@@ -355,7 +347,6 @@ public class DateIntervalFormat extends UFormat {
* Class for span fields in FormattedDateInterval.
*
* @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 = -6330879259553618133L;
@@ -366,8 +357,6 @@ public class DateIntervalFormat extends UFormat {
* Instances of DATE_INTERVAL_SPAN should have an associated value. If
* 0, the date fields within the span are for the "from" date; if 1,
* the date fields within the span are for the "to" date.
- *
- * @hide draft / provisional / internal are hidden on Android
*/
public static final SpanField DATE_INTERVAL_SPAN = new SpanField("date-interval-span");
@@ -376,7 +365,7 @@ public class DateIntervalFormat extends UFormat {
}
/**
- * serizalization method resolve instances to the constant
+ * serialization method resolve instances to the constant
* DateIntervalFormat.SpanField values
* @hide draft / provisional / internal are hidden on Android
*/
@@ -778,7 +767,7 @@ public class DateIntervalFormat extends UFormat {
*
* @param dtInterval DateInterval to be formatted.
* @return A FormattedDateInterval containing the format result.
- * @hide draft / provisional / internal are hidden on Android
+ * @hide Hide new API in Android temporarily
*/
public FormattedDateInterval formatToValue(DateInterval dtInterval) {
StringBuffer sb = new StringBuffer();
@@ -839,6 +828,9 @@ public class DateIntervalFormat extends UFormat {
} else if ( fromCalendar.get(Calendar.SECOND) !=
toCalendar.get(Calendar.SECOND) ) {
field = Calendar.SECOND;
+ } else if ( fromCalendar.get(Calendar.MILLISECOND) !=
+ toCalendar.get(Calendar.MILLISECOND) ) {
+ field = Calendar.MILLISECOND;
} else {
return null;
}
@@ -882,7 +874,7 @@ public class DateIntervalFormat extends UFormat {
* @param toCalendar calendar set to the to date in date interval
* to be formatted into date interval string
* @return A FormattedDateInterval containing the format result.
- * @hide draft / provisional / internal are hidden on Android
+ * @hide Hide new API in Android temporarily
*/
public FormattedDateInterval formatToValue(Calendar fromCalendar, Calendar toCalendar) {
StringBuffer sb = new StringBuffer();
@@ -933,16 +925,19 @@ public class DateIntervalFormat extends UFormat {
} else if ( fromCalendar.get(Calendar.MINUTE) !=
toCalendar.get(Calendar.MINUTE) ) {
field = Calendar.MINUTE;
- } else if ( fromCalendar.get(Calendar.SECOND) !=
+ } else if ( fromCalendar.get(Calendar.SECOND) !=
toCalendar.get(Calendar.SECOND) ) {
field = Calendar.SECOND;
- } else {
+ } else if ( fromCalendar.get(Calendar.MILLISECOND) !=
+ toCalendar.get(Calendar.MILLISECOND) ) {
+ field = Calendar.MILLISECOND;
+ } else {
/* ignore the millisecond etc. small fields' difference.
* use single date when all the above are the same.
*/
return fDateFormat.format(fromCalendar, appendTo, pos, attributes);
}
- boolean fromToOnSameDay = (field==Calendar.AM_PM || field==Calendar.HOUR || field==Calendar.MINUTE || field==Calendar.SECOND);
+ boolean fromToOnSameDay = (field==Calendar.AM_PM || field==Calendar.HOUR || field==Calendar.MINUTE || field==Calendar.SECOND || field==Calendar.MILLISECOND);
// get interval pattern
PatternInfo intervalPattern = fIntervalPatterns.get(
@@ -1013,11 +1008,11 @@ public class DateIntervalFormat extends UFormat {
fInfo.getFallbackIntervalPattern(), patternSB, 2, 2);
long state = 0;
while (true) {
- state = SimpleFormatterImpl.Int64Iterator.step(compiledPattern, state, appendTo);
- if (state == SimpleFormatterImpl.Int64Iterator.DONE) {
+ state = SimpleFormatterImpl.IterInternal.step(state, compiledPattern, appendTo);
+ if (state == SimpleFormatterImpl.IterInternal.DONE) {
break;
}
- if (SimpleFormatterImpl.Int64Iterator.getArgIndex(state) == 0) {
+ if (SimpleFormatterImpl.IterInternal.getArgIndex(state) == 0) {
if (output != null) {
output.register(0);
}
@@ -1071,11 +1066,11 @@ public class DateIntervalFormat extends UFormat {
// {1} is single date portion
long state = 0;
while (true) {
- state = SimpleFormatterImpl.Int64Iterator.step(compiledPattern, state, appendTo);
- if (state == SimpleFormatterImpl.Int64Iterator.DONE) {
+ state = SimpleFormatterImpl.IterInternal.step(state, compiledPattern, appendTo);
+ if (state == SimpleFormatterImpl.IterInternal.DONE) {
break;
}
- if (SimpleFormatterImpl.Int64Iterator.getArgIndex(state) == 0) {
+ if (SimpleFormatterImpl.IterInternal.getArgIndex(state) == 0) {
fDateFormat.applyPattern(fTimePattern);
fallbackFormatRange(fromCalendar, toCalendar, appendTo, patternSB, pos, output, attributes);
} else {
diff --git a/android_icu4j/src/main/java/android/icu/text/DateIntervalInfo.java b/android_icu4j/src/main/java/android/icu/text/DateIntervalInfo.java
index d96276100..761481804 100644
--- a/android_icu4j/src/main/java/android/icu/text/DateIntervalInfo.java
+++ b/android_icu4j/src/main/java/android/icu/text/DateIntervalInfo.java
@@ -144,7 +144,7 @@ import android.icu.util.UResourceBundle;
* the interval patterns using setIntervalPattern function as so desired.
* Currently, users can only set interval patterns when the following
* calendar fields are different: ERA, YEAR, MONTH, DATE, DAY_OF_MONTH,
- * DAY_OF_WEEK, AM_PM, HOUR, HOUR_OF_DAY, MINUTE and SECOND.
+ * DAY_OF_WEEK, AM_PM, HOUR, HOUR_OF_DAY, MINUTE, SECOND, and MILLISECOND.
* Interval patterns when other calendar fields are different is not supported.
* <P>
* DateIntervalInfo objects are cloneable.
@@ -289,7 +289,7 @@ public class DateIntervalInfo implements Cloneable, Freezable<DateIntervalInfo>,
private static final long serialVersionUID = 1;
private static final int MINIMUM_SUPPORTED_CALENDAR_FIELD =
- Calendar.SECOND;
+ Calendar.MILLISECOND;
//private static boolean DEBUG = true;
private static String CALENDAR_KEY = "calendar";
@@ -415,8 +415,9 @@ public class DateIntervalInfo implements Cloneable, Freezable<DateIntervalInfo>,
* Calendar.HOUR_OF_DAY
* Calendar.MINUTE
* Calendar.SECOND
+ * Calendar.MILLISECOND
*/
- private static final String ACCEPTED_PATTERN_LETTERS = "GyMdahHms";
+ private static final String ACCEPTED_PATTERN_LETTERS = "GyMdahHmsS";
// Output data
DateIntervalInfo dateIntervalInfo;
@@ -695,7 +696,7 @@ public class DateIntervalInfo implements Cloneable, Freezable<DateIntervalInfo>,
* Restriction:
* Currently, users can only set interval patterns when the following
* calendar fields are different: ERA, YEAR, MONTH, DATE, DAY_OF_MONTH,
- * DAY_OF_WEEK, AM_PM, HOUR, HOUR_OF_DAY, MINUTE, and SECOND.
+ * DAY_OF_WEEK, AM_PM, HOUR, HOUR_OF_DAY, MINUTE, SECOND, and MILLISECOND.
* Interval patterns when other calendar fields are different are
* not supported.
*
@@ -839,7 +840,7 @@ public class DateIntervalInfo implements Cloneable, Freezable<DateIntervalInfo>,
public PatternInfo getIntervalPattern(String skeleton, int field)
{
if ( field > MINIMUM_SUPPORTED_CALENDAR_FIELD ) {
- throw new IllegalArgumentException("no support for field less than SECOND");
+ throw new IllegalArgumentException("no support for field less than MILLISECOND");
}
Map<String, PatternInfo> patternsOfOneSkeleton = fIntervalPatterns.get(skeleton);
if ( patternsOfOneSkeleton != null ) {
diff --git a/android_icu4j/src/main/java/android/icu/text/DateTimePatternGenerator.java b/android_icu4j/src/main/java/android/icu/text/DateTimePatternGenerator.java
index 6ad49c215..497cfa2bb 100644
--- a/android_icu4j/src/main/java/android/icu/text/DateTimePatternGenerator.java
+++ b/android_icu4j/src/main/java/android/icu/text/DateTimePatternGenerator.java
@@ -364,6 +364,26 @@ public class DateTimePatternGenerator implements Freezable<DateTimePatternGenera
String[] list = getAllowedHourFormatsLangCountry(language, country);
+ // We need to check if there is an hour cycle on locale
+ Character defaultCharFromLocale = null;
+ String hourCycle = uLocale.getKeywordValue("hours");
+ if (hourCycle != null) {
+ switch(hourCycle) {
+ case "h24":
+ defaultCharFromLocale = 'k';
+ break;
+ case "h23":
+ defaultCharFromLocale = 'H';
+ break;
+ case "h12":
+ defaultCharFromLocale = 'h';
+ break;
+ case "h11":
+ defaultCharFromLocale = 'K';
+ break;
+ }
+ }
+
// Check if the region has an alias
if (list == null) {
try {
@@ -376,11 +396,11 @@ public class DateTimePatternGenerator implements Freezable<DateTimePatternGenera
}
if (list != null) {
- defaultHourFormatChar = list[0].charAt(0);
+ defaultHourFormatChar = defaultCharFromLocale != null ? defaultCharFromLocale : list[0].charAt(0);
allowedHourFormats = Arrays.copyOfRange(list, 1, list.length - 1);
} else {
allowedHourFormats = LAST_RESORT_ALLOWED_HOUR_FORMAT;
- defaultHourFormatChar = allowedHourFormats[0].charAt(0);
+ defaultHourFormatChar = (defaultCharFromLocale != null) ? defaultCharFromLocale : allowedHourFormats[0].charAt(0);
}
}
@@ -1187,7 +1207,6 @@ public class DateTimePatternGenerator implements Freezable<DateTimePatternGenera
private static final int APPENDITEM_WIDTH_INT = APPENDITEM_WIDTH.ordinal();
private static final DisplayWidth[] CLDR_FIELD_WIDTH = DisplayWidth.values();
-
// Option masks for getBestPattern, replaceFieldTypes (individual masks may be ORed together)
/**
@@ -1290,6 +1309,20 @@ public class DateTimePatternGenerator implements Freezable<DateTimePatternGenera
}
/**
+ * Return the default hour cycle.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public DateFormat.HourCycle getDefaultHourCycle() {
+ switch(getDefaultHourFormatChar()) {
+ case 'h': return DateFormat.HourCycle.HOUR_CYCLE_12;
+ case 'H': return DateFormat.HourCycle.HOUR_CYCLE_23;
+ case 'k': return DateFormat.HourCycle.HOUR_CYCLE_24;
+ case 'K': return DateFormat.HourCycle.HOUR_CYCLE_11;
+ default: throw new AssertionError("should be unreachable");
+ }
+ }
+
+ /**
* The private interface to set a display name for a particular date/time field,
* in one of several possible display widths.
*
@@ -2024,6 +2057,7 @@ public class DateTimePatternGenerator implements Freezable<DateTimePatternGenera
// if (SHOW_DISTANCE) System.out.println("Searching for: " + source.pattern
// + ", mask: " + showMask(includeMask));
int bestDistance = Integer.MAX_VALUE;
+ int bestMissingFieldMask = Integer.MIN_VALUE;
PatternWithMatcher bestPatternWithMatcher = new PatternWithMatcher("", null);
DistanceInfo tempInfo = new DistanceInfo();
for (DateTimeMatcher trial : skeleton2pattern.keySet()) {
@@ -2033,8 +2067,16 @@ public class DateTimePatternGenerator implements Freezable<DateTimePatternGenera
int distance = source.getDistance(trial, includeMask, tempInfo);
// if (SHOW_DISTANCE) System.out.println("\tDistance: " + trial.pattern + ":\t"
// + distance + ",\tmissing fields: " + tempInfo);
- if (distance < bestDistance) {
+
+ // Because we iterate over a map the order is undefined. Can change between implementations,
+ // versions, and will very likely be different between Java and C/C++.
+ // So if we have patterns with the same distance we also look at the missingFieldMask,
+ // and we favour the smallest one. Because the field is a bitmask this technically means we
+ // favour differences in the "least significant fields". For example we prefer the one with differences
+ // in seconds field vs one with difference in the hours field.
+ if (distance < bestDistance || (distance == bestDistance && bestMissingFieldMask < tempInfo.missingFieldMask)) {
bestDistance = distance;
+ bestMissingFieldMask = tempInfo.missingFieldMask;
PatternWithSkeletonFlag patternWithSkelFlag = skeleton2pattern.get(trial);
bestPatternWithMatcher.pattern = patternWithSkelFlag.pattern;
// If the best raw match had a specified skeleton then return it too.
@@ -2093,8 +2135,10 @@ public class DateTimePatternGenerator implements Freezable<DateTimePatternGenera
// - "field" is the field from the found pattern.
//
// The adjusted field should consist of characters from the originally requested
- // skeleton, except in the case of HOUR or MONTH or WEEKDAY or YEAR, in which case it
- // should consist of characters from the found pattern.
+ // skeleton, except in the case of MONTH or WEEKDAY or YEAR, in which case it
+ // should consist of characters from the found pattern. There is some adjustment
+ // in some cases of HOUR to "defaultHourFormatChar". There is explanation
+ // how it is done below.
//
// The length of the adjusted field (adjFieldLen) should match that in the originally
// requested skeleton, except that in the following cases the length of the adjusted field
@@ -2139,8 +2183,25 @@ public class DateTimePatternGenerator implements Freezable<DateTimePatternGenera
&& (type != YEAR || reqFieldChar=='Y'))
? reqFieldChar
: fieldBuilder.charAt(0);
- if (type == HOUR && flags.contains(DTPGflags.SKELETON_USES_CAP_J)) {
- c = defaultHourFormatChar;
+ if (type == HOUR) {
+ // The adjustment here is required to match spec (https://www.unicode.org/reports/tr35/tr35-dates.html#dfst-hour).
+ // It is necessary to match the hour-cycle preferred by the Locale.
+ // Given that, we need to do the following adjustments:
+ // 1. When hour-cycle is h11 it should replace 'h' by 'K'.
+ // 2. When hour-cycle is h23 it should replace 'H' by 'k'.
+ // 3. When hour-cycle is h24 it should replace 'k' by 'H'.
+ // 4. When hour-cycle is h12 it should replace 'K' by 'h'.
+ if (flags.contains(DTPGflags.SKELETON_USES_CAP_J) || reqFieldChar == defaultHourFormatChar) {
+ c = defaultHourFormatChar;
+ } else if (reqFieldChar == 'h' && defaultHourFormatChar == 'K') {
+ c = 'K';
+ } else if (reqFieldChar == 'H' && defaultHourFormatChar == 'k') {
+ c = 'k';
+ } else if (reqFieldChar == 'k' && defaultHourFormatChar == 'H') {
+ c = 'H';
+ } else if (reqFieldChar == 'K' && defaultHourFormatChar == 'h') {
+ c = 'h';
+ }
}
fieldBuilder = new StringBuilder();
for (int i = adjFieldLen; i > 0; --i) fieldBuilder.append(c);
@@ -2639,6 +2700,32 @@ public class DateTimePatternGenerator implements Freezable<DateTimePatternGenera
if (subField > 0) subField += value.length();
type[field] = subField;
}
+
+ // #20739, we have a skeleton with minutes and milliseconds, but no seconds
+ //
+ // Theoretically we would need to check and fix all fields with "gaps":
+ // for example year-day (no month), month-hour (no day), and so on, All the possible field combinations.
+ // Plus some smartness: year + hour => should we add month, or add day-of-year?
+ // What about month + day-of-week, or month + am/pm indicator.
+ // I think beyond a certain point we should not try to fix bad developer input and try guessing what they mean.
+ // Garbage in, garbage out.
+ if (!original.isFieldEmpty(MINUTE) && !original.isFieldEmpty(FRACTIONAL_SECOND) && original.isFieldEmpty(SECOND)) {
+ // Force the use of seconds
+ for (int i = 0; i < types.length; ++i) {
+ int[] row = types[i];
+ if (row[1] == SECOND) {
+ // first entry for SECOND
+ original.populate(SECOND, (char)row[0], row[3]);
+ baseOriginal.populate(SECOND, (char)row[0], row[3]);
+ // We add value.length, same as above, when type is first initialized.
+ // The value we want to "fake" here is "s", and 1 means "s".length()
+ int subField = row[2];
+ type[SECOND] = (subField > 0) ? subField + 1 : subField;
+ break;
+ }
+ }
+ }
+
// #13183, handle special behavior for day period characters (a, b, B)
if (!original.isFieldEmpty(HOUR)) {
if (original.getFieldChar(HOUR)=='h' || original.getFieldChar(HOUR)=='K') {
diff --git a/android_icu4j/src/main/java/android/icu/text/DecimalFormat.java b/android_icu4j/src/main/java/android/icu/text/DecimalFormat.java
index 42d46c831..65a2c8e81 100644
--- a/android_icu4j/src/main/java/android/icu/text/DecimalFormat.java
+++ b/android_icu4j/src/main/java/android/icu/text/DecimalFormat.java
@@ -13,9 +13,14 @@ import java.text.AttributedCharacterIterator;
import java.text.FieldPosition;
import java.text.ParsePosition;
+import android.icu.impl.FormattedStringBuilder;
+import android.icu.impl.FormattedValueStringBuilderImpl;
+import android.icu.impl.Utility;
import android.icu.impl.number.AffixUtils;
import android.icu.impl.number.DecimalFormatProperties;
import android.icu.impl.number.DecimalFormatProperties.ParseMode;
+import android.icu.impl.number.DecimalQuantity;
+import android.icu.impl.number.DecimalQuantity_DualStorageBCD;
import android.icu.impl.number.Padder;
import android.icu.impl.number.Padder.PadPosition;
import android.icu.impl.number.PatternStringParser;
@@ -693,9 +698,11 @@ public class DecimalFormat extends NumberFormat {
*/
@Override
public StringBuffer format(double number, StringBuffer result, FieldPosition fieldPosition) {
- FormattedNumber output = formatter.format(number);
- fieldPositionHelper(output, fieldPosition, result.length());
- output.appendTo(result);
+ DecimalQuantity dq = new DecimalQuantity_DualStorageBCD(number);
+ FormattedStringBuilder string = new FormattedStringBuilder();
+ formatter.formatImpl(dq, string);
+ fieldPositionHelper(dq, string, fieldPosition, result.length());
+ Utility.appendTo(string, result);
return result;
}
@@ -704,9 +711,11 @@ public class DecimalFormat extends NumberFormat {
*/
@Override
public StringBuffer format(long number, StringBuffer result, FieldPosition fieldPosition) {
- FormattedNumber output = formatter.format(number);
- fieldPositionHelper(output, fieldPosition, result.length());
- output.appendTo(result);
+ DecimalQuantity dq = new DecimalQuantity_DualStorageBCD(number);
+ FormattedStringBuilder string = new FormattedStringBuilder();
+ formatter.formatImpl(dq, string);
+ fieldPositionHelper(dq, string, fieldPosition, result.length());
+ Utility.appendTo(string, result);
return result;
}
@@ -715,9 +724,11 @@ public class DecimalFormat extends NumberFormat {
*/
@Override
public StringBuffer format(BigInteger number, StringBuffer result, FieldPosition fieldPosition) {
- FormattedNumber output = formatter.format(number);
- fieldPositionHelper(output, fieldPosition, result.length());
- output.appendTo(result);
+ DecimalQuantity dq = new DecimalQuantity_DualStorageBCD(number);
+ FormattedStringBuilder string = new FormattedStringBuilder();
+ formatter.formatImpl(dq, string);
+ fieldPositionHelper(dq, string, fieldPosition, result.length());
+ Utility.appendTo(string, result);
return result;
}
@@ -727,9 +738,11 @@ public class DecimalFormat extends NumberFormat {
@Override
public StringBuffer format(
java.math.BigDecimal number, StringBuffer result, FieldPosition fieldPosition) {
- FormattedNumber output = formatter.format(number);
- fieldPositionHelper(output, fieldPosition, result.length());
- output.appendTo(result);
+ DecimalQuantity dq = new DecimalQuantity_DualStorageBCD(number);
+ FormattedStringBuilder string = new FormattedStringBuilder();
+ formatter.formatImpl(dq, string);
+ fieldPositionHelper(dq, string, fieldPosition, result.length());
+ Utility.appendTo(string, result);
return result;
}
@@ -738,9 +751,11 @@ public class DecimalFormat extends NumberFormat {
*/
@Override
public StringBuffer format(BigDecimal number, StringBuffer result, FieldPosition fieldPosition) {
- FormattedNumber output = formatter.format(number);
- fieldPositionHelper(output, fieldPosition, result.length());
- output.appendTo(result);
+ DecimalQuantity dq = new DecimalQuantity_DualStorageBCD(number);
+ FormattedStringBuilder string = new FormattedStringBuilder();
+ formatter.formatImpl(dq, string);
+ fieldPositionHelper(dq, string, fieldPosition, result.length());
+ Utility.appendTo(string, result);
return result;
}
@@ -765,12 +780,14 @@ public class DecimalFormat extends NumberFormat {
// because its caching mechanism will not provide any benefit here.
DecimalFormatSymbols localSymbols = (DecimalFormatSymbols) symbols.clone();
localSymbols.setCurrency(currAmt.getCurrency());
- FormattedNumber output = formatter
- .symbols(localSymbols)
+
+ DecimalQuantity dq = new DecimalQuantity_DualStorageBCD(currAmt.getNumber());
+ FormattedStringBuilder string = new FormattedStringBuilder();
+ formatter.symbols(localSymbols)
.unit(currAmt.getCurrency())
- .format(currAmt.getNumber());
- fieldPositionHelper(output, fieldPosition, result.length());
- output.appendTo(result);
+ .formatImpl(dq, string);
+ fieldPositionHelper(dq, string, fieldPosition, result.length());
+ Utility.appendTo(string, result);
return result;
}
@@ -1016,7 +1033,7 @@ public class DecimalFormat extends NumberFormat {
*
* @return Whether the sign is shown on positive numbers and zero.
* @see #setSignAlwaysShown
- * @hide draft / provisional / internal are hidden on Android
+ * @hide Hide new API in Android temporarily
*/
public synchronized boolean isSignAlwaysShown() {
// This is not in the exported properties
@@ -1044,7 +1061,7 @@ public class DecimalFormat extends NumberFormat {
* signs in the pattern is undefined.
*
* @param value true to always show a sign; false to hide the sign on positive numbers and zero.
- * @hide draft / provisional / internal are hidden on Android
+ * @hide Hide new API in Android temporarily
*/
public synchronized void setSignAlwaysShown(boolean value) {
properties.setSignAlwaysShown(value);
@@ -1824,7 +1841,7 @@ public class DecimalFormat extends NumberFormat {
* <strong>[icu]</strong> Returns the minimum number of digits before grouping is triggered.
*
* @see #setMinimumGroupingDigits
- * @hide draft / provisional / internal are hidden on Android
+ * @hide Hide new API in Android temporarily
*/
public synchronized int getMinimumGroupingDigits() {
if (properties.getMinimumGroupingDigits() > 0) {
@@ -1839,7 +1856,7 @@ public class DecimalFormat extends NumberFormat {
* to 2, in <em>en-US</em>, 1234 will be printed as "1234" and 12345 will be printed as "12,345".
*
* @param number The minimum number of digits before grouping is triggered.
- * @hide draft / provisional / internal are hidden on Android
+ * @hide Hide new API in Android temporarily
*/
public synchronized void setMinimumGroupingDigits(int number) {
properties.setMinimumGroupingDigits(number);
@@ -2123,7 +2140,7 @@ public synchronized void setParseStrictMode(ParseMode parseMode) {
* <strong>[icu]</strong> Returns whether to ignore exponents when parsing.
*
* @see #setParseNoExponent
- * @hide draft / provisional / internal are hidden on Android
+ * @hide Hide new API in Android temporarily
*/
public synchronized boolean isParseNoExponent() {
return properties.getParseNoExponent();
@@ -2135,7 +2152,7 @@ public synchronized void setParseStrictMode(ParseMode parseMode) {
* 5).
*
* @param value true to prevent exponents from being parsed; false to allow them to be parsed.
- * @hide draft / provisional / internal are hidden on Android
+ * @hide Hide new API in Android temporarily
*/
public synchronized void setParseNoExponent(boolean value) {
properties.setParseNoExponent(value);
@@ -2146,7 +2163,7 @@ public synchronized void setParseStrictMode(ParseMode parseMode) {
* <strong>[icu]</strong> Returns whether to force case (uppercase/lowercase) to match when parsing.
*
* @see #setParseNoExponent
- * @hide draft / provisional / internal are hidden on Android
+ * @hide Hide new API in Android temporarily
*/
public synchronized boolean isParseCaseSensitive() {
return properties.getParseCaseSensitive();
@@ -2159,7 +2176,7 @@ public synchronized void setParseStrictMode(ParseMode parseMode) {
*
* @param value true to force case (uppercase/lowercase) to match when parsing; false to ignore
* case and perform case folding.
- * @hide draft / provisional / internal are hidden on Android
+ * @hide Hide new API in Android temporarily
*/
public synchronized void setParseCaseSensitive(boolean value) {
properties.setParseCaseSensitive(value);
@@ -2398,11 +2415,13 @@ public synchronized void setParseStrictMode(ParseMode parseMode) {
PatternStringParser.parseToExistingProperties(pattern, properties, ignoreRounding);
}
- static void fieldPositionHelper(FormattedNumber formatted, FieldPosition fieldPosition, int offset) {
+ static void fieldPositionHelper(
+ DecimalQuantity dq, FormattedStringBuilder string, FieldPosition fieldPosition, int offset) {
// always return first occurrence:
fieldPosition.setBeginIndex(0);
fieldPosition.setEndIndex(0);
- boolean found = formatted.nextFieldPosition(fieldPosition);
+ dq.populateUFieldPosition(fieldPosition);
+ boolean found = FormattedValueStringBuilderImpl.nextFieldPosition(string, fieldPosition);;
if (found && offset != 0) {
fieldPosition.setBeginIndex(fieldPosition.getBeginIndex() + offset);
fieldPosition.setEndIndex(fieldPosition.getEndIndex() + offset);
diff --git a/android_icu4j/src/main/java/android/icu/text/FormattedValue.java b/android_icu4j/src/main/java/android/icu/text/FormattedValue.java
index 02daa93c7..ff67f1247 100644
--- a/android_icu4j/src/main/java/android/icu/text/FormattedValue.java
+++ b/android_icu4j/src/main/java/android/icu/text/FormattedValue.java
@@ -13,7 +13,6 @@ import android.icu.util.ICUUncheckedIOException;
*
* @author sffc
* @hide Only a subset of ICU is exposed in Android
- * @hide draft / provisional / internal are hidden on Android
*/
public interface FormattedValue extends CharSequence {
/**
@@ -22,7 +21,6 @@ public interface FormattedValue extends CharSequence {
* Consider using {@link #appendTo} for greater efficiency.
*
* @return The formatted string.
- * @hide draft / provisional / internal are hidden on Android
*/
@Override
public String toString();
@@ -36,7 +34,6 @@ public interface FormattedValue extends CharSequence {
* @param appendable The Appendable to which to append the string output.
* @return The same Appendable, for chaining.
* @throws ICUUncheckedIOException if the Appendable throws IOException
- * @hide draft / provisional / internal are hidden on Android
*/
public <A extends Appendable> A appendTo(A appendable);
@@ -58,7 +55,6 @@ public interface FormattedValue extends CharSequence {
* only one specific field; see {@link ConstrainedFieldPosition#constrainField}.
* @return true if a new occurrence of the field was found;
* false otherwise.
- * @hide draft / provisional / internal are hidden on Android
*/
public boolean nextPosition(ConstrainedFieldPosition cfpos);
@@ -68,7 +64,6 @@ public interface FormattedValue extends CharSequence {
* Consider using {@link #nextPosition} if you are trying to get field information.
*
* @return An AttributedCharacterIterator containing full field information.
- * @hide draft / provisional / internal are hidden on Android
*/
public AttributedCharacterIterator toCharacterIterator();
}
diff --git a/android_icu4j/src/main/java/android/icu/text/ListFormatter.java b/android_icu4j/src/main/java/android/icu/text/ListFormatter.java
index c54f68361..795af87b4 100644
--- a/android_icu4j/src/main/java/android/icu/text/ListFormatter.java
+++ b/android_icu4j/src/main/java/android/icu/text/ListFormatter.java
@@ -9,19 +9,26 @@
*/
package android.icu.text;
-import java.io.IOException;
+import java.io.InvalidObjectException;
+import java.text.AttributedCharacterIterator;
+import java.text.Format;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.Locale;
+import java.util.regex.Pattern;
+import android.icu.impl.FormattedStringBuilder;
+import android.icu.impl.FormattedValueStringBuilderImpl;
+import android.icu.impl.FormattedValueStringBuilderImpl.SpanFieldPlaceholder;
import android.icu.impl.ICUCache;
import android.icu.impl.ICUData;
import android.icu.impl.ICUResourceBundle;
import android.icu.impl.SimpleCache;
import android.icu.impl.SimpleFormatterImpl;
-import android.icu.util.ICUUncheckedIOException;
+import android.icu.impl.SimpleFormatterImpl.IterInternal;
+import android.icu.impl.Utility;
import android.icu.util.ULocale;
import android.icu.util.UResourceBundle;
@@ -33,14 +40,19 @@ import android.icu.util.UResourceBundle;
*/
final public class ListFormatter {
// Compiled SimpleFormatter patterns.
- private final String two;
private final String start;
private final String middle;
- private final String end;
private final ULocale locale;
+ private interface PatternHandler {
+ public String getTwoPattern(String text);
+ public String getEndPattern(String text);
+ }
+ private final PatternHandler patternHandler;
+
/**
* Indicates the style of Listformatter
+ * TODO(ICU-20888): Remove this in ICU 68.
* @deprecated This API is ICU internal only.
* @hide Only a subset of ICU is exposed in Android
* @hide draft / provisional / internal are hidden on Android
@@ -100,6 +112,225 @@ final public class ListFormatter {
}
/**
+ * Type of meaning expressed by the list.
+ *
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public enum Type {
+ /**
+ * Conjunction formatting, e.g. "Alice, Bob, Charlie, and Delta".
+ *
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ AND,
+
+ /**
+ * Disjunction (or alternative, or simply one of) formatting, e.g.
+ * "Alice, Bob, Charlie, or Delta".
+ *
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ OR,
+
+ /**
+ * Formatting of a list of values with units, e.g. "5 pounds, 12 ounces".
+ *
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ UNITS
+ };
+
+ /**
+ * Verbosity level of the list patterns.
+ *
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public enum Width {
+ /**
+ * Use list formatting with full words (no abbreviations) when possible.
+ *
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ WIDE,
+
+ /**
+ * Use list formatting of typical length.
+ *
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ SHORT,
+
+ /**
+ * Use list formatting of the shortest possible length.
+ *
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ NARROW,
+ };
+
+ /**
+ * Class for span fields in FormattedList.
+ *
+ * @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 = 3563544214705634403L;
+
+ /**
+ * The concrete field used for spans in FormattedList.
+ *
+ * Instances of LIST_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 LIST_SPAN = new SpanField("list-span");
+
+ private SpanField(String name) {
+ super(name);
+ }
+
+ /**
+ * serialization method resolve instances to the constant
+ * ListFormatter.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(LIST_SPAN.getName()))
+ return LIST_SPAN;
+
+ throw new InvalidObjectException("An invalid object.");
+ }
+ }
+
+ /**
+ * Field selectors for format fields defined by ListFormatter.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public static final class Field extends Format.Field {
+ private static final long serialVersionUID = -8071145668708265437L;
+
+ /**
+ * The literal text in the result which came from the resources.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public static Field LITERAL = new Field("literal");
+
+ /**
+ * The element text in the result which came from the input strings.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public static Field ELEMENT = new Field("element");
+
+ private Field(String name) {
+ super(name);
+ }
+
+ /**
+ * Serizalization method resolve instances to the constant Field values
+ *
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Override
+ protected Object readResolve() throws InvalidObjectException {
+ if (this.getName().equals(LITERAL.getName()))
+ return LITERAL;
+ if (this.getName().equals(ELEMENT.getName()))
+ return ELEMENT;
+
+ throw new InvalidObjectException("An invalid object.");
+ }
+ }
+
+ /**
+ * An immutable class containing the result of a list formatting operation.
+ *
+ * Instances of this class are immutable and thread-safe.
+ *
+ * Not intended for public subclassing.
+ *
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public static final class FormattedList implements FormattedValue {
+ private final FormattedStringBuilder string;
+
+ FormattedList(FormattedStringBuilder string) {
+ this.string = string;
+ }
+
+ /**
+ * {@inheritDoc}
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Override
+ public String toString() {
+ return string.toString();
+ }
+
+ /**
+ * {@inheritDoc}
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Override
+ public int length() {
+ return string.length();
+ }
+
+ /**
+ * {@inheritDoc}
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Override
+ public char charAt(int index) {
+ return string.charAt(index);
+ }
+
+ /**
+ * {@inheritDoc}
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Override
+ public CharSequence subSequence(int start, int end) {
+ return string.subString(start, end);
+ }
+
+ /**
+ * {@inheritDoc}
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Override
+ public <A extends Appendable> A appendTo(A appendable) {
+ return Utility.appendTo(string, appendable);
+ }
+
+ /**
+ * {@inheritDoc}
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Override
+ public boolean nextPosition(ConstrainedFieldPosition cfpos) {
+ return FormattedValueStringBuilderImpl.nextPosition(string, cfpos, null);
+ }
+
+ /**
+ * {@inheritDoc}
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Override
+ public AttributedCharacterIterator toCharacterIterator() {
+ return FormattedValueStringBuilderImpl.toCharacterIterator(string, null);
+ }
+ }
+
+ /**
* <b>Internal:</b> Create a ListFormatter from component strings,
* with definitions as in LDML.
*
@@ -129,11 +360,10 @@ final public class ListFormatter {
}
private ListFormatter(String two, String start, String middle, String end, ULocale locale) {
- this.two = two;
this.start = start;
this.middle = middle;
- this.end = end;
this.locale = locale;
+ this.patternHandler = createPatternHandler(two, end);
}
private static String compilePattern(String pattern, StringBuilder sb) {
@@ -146,9 +376,14 @@ final public class ListFormatter {
* @param locale
* the locale in question.
* @return ListFormatter
+ * @hide draft / provisional / internal are hidden on Android
*/
- public static ListFormatter getInstance(ULocale locale) {
- return getInstance(locale, Style.STANDARD);
+ public static ListFormatter getInstance(ULocale locale, Type type, Width width) {
+ String styleName = typeWidthToStyleString(type, width);
+ if (styleName == null) {
+ throw new IllegalArgumentException("Invalid list format type/width");
+ }
+ return cache.get(locale, styleName);
}
/**
@@ -157,9 +392,10 @@ final public class ListFormatter {
* @param locale
* the locale in question.
* @return ListFormatter
+ * @hide draft / provisional / internal are hidden on Android
*/
- public static ListFormatter getInstance(Locale locale) {
- return getInstance(ULocale.forLocale(locale), Style.STANDARD);
+ public static ListFormatter getInstance(Locale locale, Type type, Width width) {
+ return getInstance(ULocale.forLocale(locale), type, width);
}
/**
@@ -177,6 +413,28 @@ final public class ListFormatter {
}
/**
+ * Create a list formatter that is appropriate for a locale.
+ *
+ * @param locale
+ * the locale in question.
+ * @return ListFormatter
+ */
+ public static ListFormatter getInstance(ULocale locale) {
+ return getInstance(locale, Style.STANDARD);
+ }
+
+ /**
+ * Create a list formatter that is appropriate for a locale.
+ *
+ * @param locale
+ * the locale in question.
+ * @return ListFormatter
+ */
+ public static ListFormatter getInstance(Locale locale) {
+ return getInstance(ULocale.forLocale(locale), Style.STANDARD);
+ }
+
+ /**
* Create a list formatter that is appropriate for the default FORMAT locale.
*
* @return ListFormatter
@@ -204,30 +462,174 @@ final public class ListFormatter {
* @return items formatted into a string
*/
public String format(Collection<?> items) {
- return format(items, -1).toString();
+ return formatImpl(items, false).toString();
+ }
+
+ /**
+ * Format a list of objects to a FormattedList. You can access the offsets
+ * of each element from the FormattedList.
+ *
+ * @param items
+ * items to format. The toString() method is called on each.
+ * @return items formatted into a FormattedList
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public FormattedList formatToValue(Object... items) {
+ return formatToValue(Arrays.asList(items));
+ }
+
+
+ /**
+ * Format a collection of objects to a FormattedList. You can access the offsets
+ * of each element from the FormattedList.
+ *
+ * @param items
+ * items to format. The toString() method is called on each.
+ * @return items formatted into a FormattedList
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public FormattedList formatToValue(Collection<?> items) {
+ return formatImpl(items, true).toValue();
}
// Formats a collection of objects and returns the formatted string plus the offset
// in the string where the index th element appears. index is zero based. If index is
// negative or greater than or equal to the size of items then this function returns -1 for
// the offset.
- FormattedListBuilder format(Collection<?> items, int index) {
+ FormattedListBuilder formatImpl(Collection<?> items, boolean needsFields) {
Iterator<?> it = items.iterator();
int count = items.size();
switch (count) {
case 0:
- return new FormattedListBuilder("", false);
+ return new FormattedListBuilder("", needsFields);
case 1:
- return new FormattedListBuilder(it.next(), index == 0);
+ return new FormattedListBuilder(it.next(), needsFields);
case 2:
- return new FormattedListBuilder(it.next(), index == 0).append(two, it.next(), index == 1);
+ Object first = it.next();
+ Object second = it.next();
+ return new FormattedListBuilder(first, needsFields)
+ .append(patternHandler.getTwoPattern(String.valueOf(second)), second, 1);
}
- FormattedListBuilder builder = new FormattedListBuilder(it.next(), index == 0);
- builder.append(start, it.next(), index == 1);
+ FormattedListBuilder builder = new FormattedListBuilder(it.next(), needsFields);
+ builder.append(start, it.next(), 1);
for (int idx = 2; idx < count - 1; ++idx) {
- builder.append(middle, it.next(), index == idx);
+ builder.append(middle, it.next(), idx);
+ }
+ Object last = it.next();
+ return builder.append(patternHandler.getEndPattern(String.valueOf(last)), last, count - 1);
+ }
+
+ // A static handler just returns the pattern without considering the input text.
+ private class StaticHandler implements PatternHandler {
+ StaticHandler(String two, String end) {
+ twoPattern = two;
+ endPattern = end;
+ }
+
+ @Override
+ public String getTwoPattern(String text) { return twoPattern; }
+
+ @Override
+ public String getEndPattern(String text) { return endPattern; }
+
+ private final String twoPattern;
+ private final String endPattern;
+ }
+
+ // A contextual handler returns one of the two patterns depending on whether the text matched the regexp.
+ private class ContextualHandler implements PatternHandler {
+ ContextualHandler(Pattern regexp, String thenTwo, String elseTwo, String thenEnd, String elseEnd) {
+ this.regexp = regexp;
+ thenTwoPattern = thenTwo;
+ elseTwoPattern = elseTwo;
+ thenEndPattern = thenEnd;
+ elseEndPattern = elseEnd;
+ }
+
+ @Override
+ public String getTwoPattern(String text) {
+ if(regexp.matcher(text).matches()) {
+ return thenTwoPattern;
+ } else {
+ return elseTwoPattern;
+ }
+ }
+
+ @Override
+ public String getEndPattern(String text) {
+ if(regexp.matcher(text).matches()) {
+ return thenEndPattern;
+ } else {
+ return elseEndPattern;
+ }
+ }
+
+ private final Pattern regexp;
+ private final String thenTwoPattern;
+ private final String elseTwoPattern;
+ private final String thenEndPattern;
+ private final String elseEndPattern;
+
+ }
+
+ // Pattern in the ICU Data which might be replaced y by e.
+ private static final String compiledY = compilePattern("{0} y {1}", new StringBuilder());
+
+ // The new pattern to replace y to e
+ private static final String compiledE = compilePattern("{0} e {1}", new StringBuilder());
+
+ // Pattern in the ICU Data which might be replaced o by u.
+ private static final String compiledO = compilePattern("{0} o {1}", new StringBuilder());
+
+ // The new pattern to replace u to o
+ private static final String compiledU = compilePattern("{0} u {1}", new StringBuilder());
+
+ // Condition to change to e.
+ // Starts with "hi" or "i" but not with "hie" nor "hia"a
+ private static final Pattern changeToE = Pattern.compile("(i.*|hi|hi[^ae].*)", Pattern.CASE_INSENSITIVE);
+
+ // Condition to change to u.
+ // Starts with "o", "ho", and "8". Also "11" by itself.
+ private static final Pattern changeToU = Pattern.compile("((o|ho|8).*|11)", Pattern.CASE_INSENSITIVE);
+
+ // Pattern in the ICU Data which might need to add a DASH after VAV.
+ private static final String compiledVav = compilePattern("{0} \u05D5{1}", new StringBuilder());
+
+ // Pattern to add a DASH after VAV.
+ private static final String compiledVavDash = compilePattern("{0} \u05D5-{1}", new StringBuilder());
+
+ // Condition to change to VAV follow by a dash.
+ // Starts with non Hebrew letter.
+ private static final Pattern changeToVavDash = Pattern.compile("^[\\P{InHebrew}].*$");
+
+ // A factory function to create function based on locale
+ // Handle specal case of Spanish and Hebrew
+ private PatternHandler createPatternHandler(String two, String end) {
+ if (this.locale != null) {
+ String language = this.locale.getLanguage();
+ if (language.equals("es")) {
+ boolean twoIsY = two.equals(compiledY);
+ boolean endIsY = end.equals(compiledY);
+ if (twoIsY || endIsY) {
+ return new ContextualHandler(
+ changeToE, twoIsY ? compiledE : two, two, endIsY ? compiledE : end, end);
+ }
+ boolean twoIsO = two.equals(compiledO);
+ boolean endIsO = end.equals(compiledO);
+ if (twoIsO || endIsO) {
+ return new ContextualHandler(
+ changeToU, twoIsO ? compiledU : two, two, endIsO ? compiledU : end, end);
+ }
+ } else if (language.equals("he") || language.equals("iw")) {
+ boolean twoIsVav = two.equals(compiledVav);
+ boolean endIsVav = end.equals(compiledVav);
+ if (twoIsVav || endIsVav) {
+ return new ContextualHandler(changeToVavDash,
+ twoIsVav ? compiledVavDash : two, two, endIsVav ? compiledVavDash : end, end);
+ }
+ }
}
- return builder.append(end, it.next(), index == count - 1);
+ return new StaticHandler(two, end);
}
/**
@@ -241,7 +643,7 @@ final public class ListFormatter {
if (count <= 0) {
throw new IllegalArgumentException("count must be > 0");
}
- ArrayList<String> list = new ArrayList<String>();
+ ArrayList<String> list = new ArrayList<>();
for (int i = 0; i < count; i++) {
list.add(String.format("{%d}", i));
}
@@ -260,64 +662,74 @@ final public class ListFormatter {
// Builds a formatted list
static class FormattedListBuilder {
- private StringBuilder current;
- private int offset;
+ private FormattedStringBuilder string;
+ boolean needsFields;
- // Start is the first object in the list; If recordOffset is true, records the offset of
- // this first object.
- public FormattedListBuilder(Object start, boolean recordOffset) {
- this.current = new StringBuilder(start.toString());
- this.offset = recordOffset ? 0 : -1;
+ // Start is the first object in the list; If needsFields is true, enable the slightly
+ // more expensive code path that records offsets of each element.
+ public FormattedListBuilder(Object start, boolean needsFields) {
+ string = new FormattedStringBuilder();
+ this.needsFields = needsFields;
+ string.setAppendableField(Field.LITERAL);
+ appendElement(start, 0);
}
// Appends additional object. pattern is a template indicating where the new object gets
// added in relation to the rest of the list. {0} represents the rest of the list; {1}
- // represents the new object in pattern. next is the object to be added. If recordOffset
- // is true, records the offset of next in the formatted string.
- public FormattedListBuilder append(String pattern, Object next, boolean recordOffset) {
- int[] offsets = (recordOffset || offsetRecorded()) ? new int[2] : null;
- SimpleFormatterImpl.formatAndReplace(
- pattern, current, offsets, current, next.toString());
- if (offsets != null) {
- if (offsets[0] == -1 || offsets[1] == -1) {
- throw new IllegalArgumentException(
- "{0} or {1} missing from pattern " + pattern);
+ // represents the new object in pattern. next is the object to be added. position is the
+ // index of the next object in the list of inputs.
+ public FormattedListBuilder append(String compiledPattern, Object next, int position) {
+ assert SimpleFormatterImpl.getArgumentLimit(compiledPattern) == 2;
+ string.setAppendIndex(0);
+ long state = 0;
+ while (true) {
+ state = IterInternal.step(state, compiledPattern, string);
+ if (state == IterInternal.DONE) {
+ break;
}
- if (recordOffset) {
- offset = offsets[1];
+ int argIndex = IterInternal.getArgIndex(state);
+ if (argIndex == 0) {
+ string.setAppendIndex(string.length());
} else {
- offset += offsets[0];
+ appendElement(next, position);
}
}
return this;
}
- public void appendTo(Appendable appendable) {
- try {
- appendable.append(current);
- } catch(IOException e) {
- throw new ICUUncheckedIOException(e);
+ private void appendElement(Object element, int position) {
+ if (needsFields) {
+ SpanFieldPlaceholder field = new SpanFieldPlaceholder();
+ field.spanField = SpanField.LIST_SPAN;
+ field.normalField = Field.ELEMENT;
+ field.value = position;
+ string.append(element.toString(), field);
+ } else {
+ string.append(element.toString(), null);
}
}
- @Override
- public String toString() {
- return current.toString();
+ public void appendTo(Appendable appendable) {
+ Utility.appendTo(string, appendable);
}
- // Gets the last recorded offset or -1 if no offset recorded.
- public int getOffset() {
- return offset;
+ public int getOffset(int fieldPositionFoundIndex) {
+ return FormattedValueStringBuilderImpl.findSpan(string, fieldPositionFoundIndex);
}
- private boolean offsetRecorded() {
- return offset >= 0;
+ @Override
+ public String toString() {
+ return string.toString();
+ }
+
+ public FormattedList toValue() {
+ return new FormattedList(string);
}
}
private static class Cache {
private final ICUCache<String, ListFormatter> cache =
- new SimpleCache<String, ListFormatter>();
+ new SimpleCache<>();
public ListFormatter get(ULocale locale, String style) {
String key = String.format("%s:%s", locale.toString(), style);
@@ -343,4 +755,42 @@ final public class ListFormatter {
}
static Cache cache = new Cache();
+
+ static String typeWidthToStyleString(Type type, Width width) {
+ switch (type) {
+ case AND:
+ switch (width) {
+ case WIDE:
+ return "standard";
+ case SHORT:
+ return "standard-short";
+ case NARROW:
+ return "standard-narrow";
+ }
+ break;
+
+ case OR:
+ switch (width) {
+ case WIDE:
+ return "or";
+ case SHORT:
+ return "or-short";
+ case NARROW:
+ return "or-narrow";
+ }
+ break;
+
+ case UNITS:
+ switch (width) {
+ case WIDE:
+ return "unit";
+ case SHORT:
+ return "unit-short";
+ case NARROW:
+ return "unit-narrow";
+ }
+ }
+
+ return null;
+ }
}
diff --git a/android_icu4j/src/main/java/android/icu/text/MeasureFormat.java b/android_icu4j/src/main/java/android/icu/text/MeasureFormat.java
index f9d079145..4122d189e 100644
--- a/android_icu4j/src/main/java/android/icu/text/MeasureFormat.java
+++ b/android_icu4j/src/main/java/android/icu/text/MeasureFormat.java
@@ -32,13 +32,16 @@ import java.util.concurrent.ConcurrentHashMap;
import android.icu.impl.DontCareFieldPosition;
import android.icu.impl.FormattedStringBuilder;
+import android.icu.impl.FormattedValueStringBuilderImpl;
import android.icu.impl.ICUData;
import android.icu.impl.ICUResourceBundle;
import android.icu.impl.SimpleCache;
import android.icu.impl.SimpleFormatterImpl;
+import android.icu.impl.Utility;
+import android.icu.impl.number.DecimalQuantity;
+import android.icu.impl.number.DecimalQuantity_DualStorageBCD;
import android.icu.impl.number.LongNameHandler;
import android.icu.impl.number.RoundingUtils;
-import android.icu.number.FormattedNumber;
import android.icu.number.IntegerWidth;
import android.icu.number.LocalizedNumberFormatter;
import android.icu.number.NumberFormatter;
@@ -298,9 +301,10 @@ public class MeasureFormat extends UFormat {
} else if (obj instanceof Measure[]) {
formatMeasuresInternal(toAppendTo, fpos, (Measure[]) obj);
} else if (obj instanceof Measure) {
- FormattedNumber result = formatMeasure((Measure) obj);
- result.nextFieldPosition(fpos); // No offset: toAppendTo.length() is considered below
- result.appendTo(toAppendTo);
+ FormattedStringBuilder result = formatMeasure((Measure) obj);
+ // No offset: toAppendTo.length() is considered below
+ FormattedValueStringBuilderImpl.nextFieldPosition(result, fpos);
+ Utility.appendTo(result, toAppendTo);
} else {
throw new IllegalArgumentException(obj.toString());
}
@@ -361,11 +365,13 @@ public class MeasureFormat extends UFormat {
MeasureUnit perUnit,
StringBuilder appendTo,
FieldPosition pos) {
- FormattedNumber result = getUnitFormatterFromCache(NUMBER_FORMATTER_STANDARD,
- measure.getUnit(),
- perUnit).format(measure.getNumber());
- DecimalFormat.fieldPositionHelper(result, pos, appendTo.length());
- result.appendTo(appendTo);
+ DecimalQuantity dq = new DecimalQuantity_DualStorageBCD(measure.getNumber());
+ FormattedStringBuilder string = new FormattedStringBuilder();
+ getUnitFormatterFromCache(
+ NUMBER_FORMATTER_STANDARD, measure.getUnit(), perUnit
+ ).formatImpl(dq, string);
+ DecimalFormat.fieldPositionHelper(dq, string, pos, appendTo.length());
+ Utility.appendTo(string, appendTo);
return appendTo;
}
@@ -407,9 +413,9 @@ public class MeasureFormat extends UFormat {
return;
}
if (measures.length == 1) {
- FormattedNumber result = formatMeasure(measures[0]);
- result.nextFieldPosition(fieldPosition);
- result.appendTo(appendTo);
+ FormattedStringBuilder result = formatMeasure(measures[0]);
+ FormattedValueStringBuilderImpl.nextFieldPosition(result, fieldPosition);
+ Utility.appendTo(result, appendTo);
return;
}
@@ -438,7 +444,7 @@ public class MeasureFormat extends UFormat {
results[i] = formatMeasureInteger(measures[i]).toString();
}
}
- FormattedListBuilder builder = listFormatter.format(Arrays.asList(results), -1);
+ FormattedListBuilder builder = listFormatter.formatImpl(Arrays.asList(results), false);
builder.appendTo(appendTo);
}
@@ -727,20 +733,26 @@ public class MeasureFormat extends UFormat {
/// END NUMBER FORMATTER CACHING MACHINERY ///
- private FormattedNumber formatMeasure(Measure measure) {
+ private FormattedStringBuilder formatMeasure(Measure measure) {
MeasureUnit unit = measure.getUnit();
+ DecimalQuantity dq = new DecimalQuantity_DualStorageBCD(measure.getNumber());
+ FormattedStringBuilder string = new FormattedStringBuilder();
if (unit instanceof Currency) {
- return getUnitFormatterFromCache(NUMBER_FORMATTER_CURRENCY, unit, null)
- .format(measure.getNumber());
+ getUnitFormatterFromCache(NUMBER_FORMATTER_CURRENCY, unit, null)
+ .formatImpl(dq, string);
} else {
- return getUnitFormatterFromCache(NUMBER_FORMATTER_STANDARD, unit, null)
- .format(measure.getNumber());
+ getUnitFormatterFromCache(NUMBER_FORMATTER_STANDARD, unit, null)
+ .formatImpl(dq, string);
}
+ return string;
}
- private FormattedNumber formatMeasureInteger(Measure measure) {
- return getUnitFormatterFromCache(NUMBER_FORMATTER_INTEGER, measure.getUnit(), null)
- .format(measure.getNumber());
+ private FormattedStringBuilder formatMeasureInteger(Measure measure) {
+ DecimalQuantity dq = new DecimalQuantity_DualStorageBCD(measure.getNumber());
+ FormattedStringBuilder string = new FormattedStringBuilder();
+ getUnitFormatterFromCache(NUMBER_FORMATTER_INTEGER, measure.getUnit(), null)
+ .formatImpl(dq, string);
+ return string;
}
private void formatMeasuresSlowTrack(
@@ -756,27 +768,27 @@ public class MeasureFormat extends UFormat {
int fieldPositionFoundIndex = -1;
for (int i = 0; i < measures.length; ++i) {
- FormattedNumber result;
+ FormattedStringBuilder result;
if (i == measures.length - 1) {
result = formatMeasure(measures[i]);
} else {
result = formatMeasureInteger(measures[i]);
}
if (fieldPositionFoundIndex == -1) {
- result.nextFieldPosition(fpos);
+ FormattedValueStringBuilderImpl.nextFieldPosition(result, fpos);
if (fpos.getEndIndex() != 0) {
fieldPositionFoundIndex = i;
}
}
results[i] = result.toString();
}
- ListFormatter.FormattedListBuilder builder = listFormatter.format(Arrays.asList(results),
- fieldPositionFoundIndex);
+ ListFormatter.FormattedListBuilder builder = listFormatter.formatImpl(Arrays.asList(results), true);
// Fix up FieldPosition indexes if our field is found.
- if (builder.getOffset() != -1) {
- fieldPosition.setBeginIndex(fpos.getBeginIndex() + builder.getOffset());
- fieldPosition.setEndIndex(fpos.getEndIndex() + builder.getOffset());
+ int offset = builder.getOffset(fieldPositionFoundIndex);
+ if (offset != -1) {
+ fieldPosition.setBeginIndex(fpos.getBeginIndex() + offset);
+ fieldPosition.setEndIndex(fpos.getEndIndex() + offset);
}
builder.appendTo(appendTo);
}
diff --git a/android_icu4j/src/main/java/android/icu/text/NumberFormat.java b/android_icu4j/src/main/java/android/icu/text/NumberFormat.java
index aad1a3cc9..eeecd6e6f 100644
--- a/android_icu4j/src/main/java/android/icu/text/NumberFormat.java
+++ b/android_icu4j/src/main/java/android/icu/text/NumberFormat.java
@@ -1795,12 +1795,12 @@ public abstract class NumberFormat extends UFormat {
public static final Field CURRENCY = new Field("currency");
/**
- * @hide draft / provisional / internal are hidden on Android
+ * @hide Hide new API in Android temporarily
*/
public static final Field MEASURE_UNIT = new Field("measure unit");
/**
- * @hide draft / provisional / internal are hidden on Android
+ * @hide Hide new API in Android temporarily
*/
public static final Field COMPACT = new Field("compact");
diff --git a/android_icu4j/src/main/java/android/icu/text/PluralRules.java b/android_icu4j/src/main/java/android/icu/text/PluralRules.java
index 737b6b923..7dbba6951 100644
--- a/android_icu4j/src/main/java/android/icu/text/PluralRules.java
+++ b/android_icu4j/src/main/java/android/icu/text/PluralRules.java
@@ -173,20 +173,11 @@ public class PluralRules implements Serializable {
static final UnicodeSet ALLOWED_ID = new UnicodeSet("[a-z]").freeze();
// TODO Remove RulesList by moving its API and fields into PluralRules.
+
/**
- * @deprecated This API is ICU internal only.
- * @hide original deprecated declaration
- * @hide draft / provisional / internal are hidden on Android
- */
- @Deprecated
- public static final String CATEGORY_SEPARATOR = "; ";
- /**
- * @deprecated This API is ICU internal only.
* @hide original deprecated declaration
- * @hide draft / provisional / internal are hidden on Android
*/
- @Deprecated
- public static final String KEYWORD_RULE_SEPARATOR = ": ";
+ private static final String CATEGORY_SEPARATOR = "; ";
private static final long serialVersionUID = 1;
@@ -473,6 +464,16 @@ public class PluralRules implements Serializable {
w,
/**
+ * Suppressed exponent for compact notation (exponent needed in
+ * scientific notation with compact notation to approximate i).
+ *
+ * @deprecated This API is ICU internal only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ e,
+
+ /**
* THIS OPERAND IS DEPRECATED AND HAS BEEN REMOVED FROM THE SPEC.
*
* <p>Returns the integer value, but will fail if the number has fraction digits.
@@ -847,6 +848,7 @@ public class PluralRules implements Serializable {
case t: return decimalDigitsWithoutTrailingZeros;
case v: return visibleDecimalDigitCount;
case w: return visibleDecimalDigitCountWithoutTrailingZeros;
+ case e: return 0;
default: return source;
}
}
@@ -2113,7 +2115,7 @@ public class PluralRules implements Serializable {
*
* @param number The number for which the rule has to be determined.
* @return The keyword of the selected rule.
- * @hide draft / provisional / internal are hidden on Android
+ * @hide Hide new API in Android temporarily
*/
public String select(FormattedNumber number) {
return rules.select(number.getFixedDecimal());
@@ -2287,12 +2289,9 @@ public class PluralRules implements Serializable {
}
/**
- * @deprecated This API is ICU internal only.
* @hide original deprecated declaration
- * @hide draft / provisional / internal are hidden on Android
*/
- @Deprecated
- public boolean addSample(String keyword, Number sample, int maxCount, Set<Double> result) {
+ private boolean addSample(String keyword, Number sample, int maxCount, Set<Double> result) {
String selectedKeyword = sample instanceof FixedDecimal ? select((FixedDecimal)sample) : select(sample.doubleValue());
if (selectedKeyword.equals(keyword)) {
result.add(sample.doubleValue());
@@ -2542,12 +2541,9 @@ public class PluralRules implements Serializable {
}
/**
- * @deprecated internal
* @hide original deprecated declaration
- * @hide draft / provisional / internal are hidden on Android
*/
- @Deprecated
- public Boolean isLimited(String keyword) {
+ Boolean isLimited(String keyword) {
return rules.isLimited(keyword, SampleType.INTEGER);
}
diff --git a/android_icu4j/src/main/java/android/icu/text/RBBIRuleScanner.java b/android_icu4j/src/main/java/android/icu/text/RBBIRuleScanner.java
index 71a84f12a..77a76f0d1 100644
--- a/android_icu4j/src/main/java/android/icu/text/RBBIRuleScanner.java
+++ b/android_icu4j/src/main/java/android/icu/text/RBBIRuleScanner.java
@@ -74,7 +74,7 @@ class RBBIRuleScanner {
RBBISymbolTable fSymbolTable; // symbol table, holds definitions of
// $variable symbols.
- HashMap<String, RBBISetTableEl> fSetTable = new HashMap<String, RBBISetTableEl>(); // UnicocodeSet hash table, holds indexes to
+ HashMap<String, RBBISetTableEl> fSetTable = new HashMap<>(); // UnicocodeSet hash table, holds indexes to
// the sets created while parsing rules.
// The key is the string used for creating
// the set.
@@ -934,7 +934,7 @@ class RBBIRuleScanner {
// Perform any action specified by this row in the state table.
if (doParseActions(tableEl.fAction) == false) {
// Break out of the state machine loop if the
- // the action signalled some kind of error, or
+ // the action signaled some kind of error, or
// the action was to exit, occurs on normal end-of-rules-input.
break;
}
@@ -1071,7 +1071,7 @@ class RBBIRuleScanner {
error(RBBIRuleBuilder.U_BRK_RULE_EMPTY_SET);
}
- // Advance the RBBI parse postion over the UnicodeSet pattern.
+ // Advance the RBBI parse position over the UnicodeSet pattern.
// Don't just set fScanIndex because the line/char positions maintained
// for error reporting would be thrown off.
i = pos.getIndex();
@@ -1090,12 +1090,17 @@ class RBBIRuleScanner {
n.fText = fRB.fRules.substring(n.fFirstPos, n.fLastPos);
// findSetFor() serves several purposes here:
// - Adopts storage for the UnicodeSet, will be responsible for deleting.
- // - Mantains collection of all sets in use, needed later for establishing
+ // - Maintains collection of all sets in use, needed later for establishing
// character categories for run time engine.
- // - Eliminates mulitiple instances of the same set.
+ // - Eliminates multiple instances of the same set.
// - Creates a new uset node if necessary (if this isn't a duplicate.)
findSetFor(n.fText, n, uset);
}
+ /**
+ * @return the number of rules that have been seen.
+ */
+ int numRules() {
+ return fRuleNum;
+ }
}
-
diff --git a/android_icu4j/src/main/java/android/icu/text/RBBITableBuilder.java b/android_icu4j/src/main/java/android/icu/text/RBBITableBuilder.java
index 99fd43f97..73561f28f 100644
--- a/android_icu4j/src/main/java/android/icu/text/RBBITableBuilder.java
+++ b/android_icu4j/src/main/java/android/icu/text/RBBITableBuilder.java
@@ -54,8 +54,8 @@ class RBBITableBuilder {
// in RBBITableBuilder.fDStates
RBBIStateDescriptor(int maxInputSymbol) {
- fTagVals = new TreeSet<Integer>();
- fPositions = new HashSet<RBBINode>();
+ fTagVals = new TreeSet<>();
+ fPositions = new HashSet<>();
fDtran = new int[maxInputSymbol+1]; // fDtran needs to be pre-sized.
// It is indexed by input symbols, and will
// hold the next state number for each
@@ -75,6 +75,9 @@ class RBBITableBuilder {
/** Synthesized safe table, a List of row arrays. */
private List<short[]> fSafeTable;
+ /** Map from rule number (fVal in look ahead nodes) to sequential lookahead index. */
+ int[] fLookAheadRuleMap;
+
//-----------------------------------------------------------------------------
//
// Constructor for RBBITableBuilder.
@@ -85,7 +88,7 @@ class RBBITableBuilder {
RBBITableBuilder(RBBIRuleBuilder rb, int rootNodeIx) {
fRootIx = rootNodeIx;
fRB = rb;
- fDStates = new ArrayList<RBBIStateDescriptor>();
+ fDStates = new ArrayList<>();
}
@@ -138,7 +141,7 @@ class RBBITableBuilder {
RBBINode cn = new RBBINode(RBBINode.opCat);
cn.fLeftChild = fRB.fTreeRoots[fRootIx];
fRB.fTreeRoots[fRootIx].fParent = cn;
- cn.fRightChild = new RBBINode(RBBINode.endMark);
+ RBBINode endMarkerNode = cn.fRightChild = new RBBINode(RBBINode.endMark);
cn.fRightChild.fParent = cn;
fRB.fTreeRoots[fRootIx] = cn;
@@ -173,7 +176,7 @@ class RBBITableBuilder {
// For "chained" rules, modify the followPos sets
//
if (fRB.fChainRules) {
- calcChainedFollowPos(fRB.fTreeRoots[fRootIx]);
+ calcChainedFollowPos(fRB.fTreeRoots[fRootIx], endMarkerNode);
}
//
@@ -187,6 +190,7 @@ class RBBITableBuilder {
// Build the DFA state transition tables.
//
buildStateTable();
+ mapLookAheadRules();
flagAcceptingStates();
flagLookAheadStates();
flagTaggedStates();
@@ -391,13 +395,9 @@ class RBBITableBuilder {
// to implement rule chaining. NOT described by Aho
//
//-----------------------------------------------------------------------------
- void calcChainedFollowPos(RBBINode tree) {
-
- List<RBBINode> endMarkerNodes = new ArrayList<RBBINode>();
- List<RBBINode> leafNodes = new ArrayList<RBBINode>();
+ void calcChainedFollowPos(RBBINode tree, RBBINode endMarkNode) {
- // get a list of all endmarker nodes.
- tree.findNodes(endMarkerNodes, RBBINode.endMark);
+ List<RBBINode> leafNodes = new ArrayList<>();
// get a list all leaf nodes
tree.findNodes(leafNodes, RBBINode.leafChar);
@@ -406,10 +406,10 @@ class RBBITableBuilder {
// with inbound chaining enabled, which is the union of the
// firstPosition sets from each of the rule root nodes.
- List<RBBINode> ruleRootNodes = new ArrayList<RBBINode>();
+ List<RBBINode> ruleRootNodes = new ArrayList<>();
addRuleRootNodes(ruleRootNodes, tree);
- Set<RBBINode> matchStartNodes = new HashSet<RBBINode>();
+ Set<RBBINode> matchStartNodes = new HashSet<>();
for (RBBINode node: ruleRootNodes) {
if (node.fChainIn) {
matchStartNodes.addAll(node.fFirstPosSet);
@@ -418,28 +418,26 @@ class RBBITableBuilder {
// Iterate over all leaf nodes,
//
- for (RBBINode tNode : leafNodes) {
- RBBINode endNode = null;
+ for (RBBINode endNode : leafNodes) {
// Identify leaf nodes that correspond to overall rule match positions.
- // These include an endMarkerNode in their followPos sets.
- for (RBBINode endMarkerNode : endMarkerNodes) {
- if (tNode.fFollowPos.contains(endMarkerNode)) {
- endNode = tNode;
- break;
- }
- }
- if (endNode == null) {
- // node wasn't an end node. Try again with the next.
+ // These include the endMarkNode in their followPos sets.
+ //
+ // Note: do not consider other end marker nodes, those that are added to
+ // look-ahead rules. These can't chain; a match immediately stops
+ // further matching. This leaves exactly one end marker node, the one
+ // at the end of the complete tree.
+
+ if (!endNode.fFollowPos.contains(endMarkNode)) {
continue;
}
// We've got a node that can end a match.
- // Line Break Specific hack: If this node's val correspond to the $CM char class,
- // don't chain from it.
- // TODO: Add rule syntax for this behavior, get specifics out of here and
- // into the rule file.
+ // !!LBCMNoChain implementation: If this node's val correspond to
+ // the Line Break $CM char class, don't chain from it.
+ // TODO: Remove this. !!LBCMNoChain is deprecated, and is not used
+ // by any of the standard ICU rules.
if (fRB.fLBCMNoChain) {
int c = this.fRB.fSetBuilder.getFirstChar(endNode.fVal);
if (c != -1) {
@@ -572,7 +570,7 @@ class RBBITableBuilder {
for (RBBINode p : T.fPositions) {
if ((p.fType == RBBINode.leafChar) && (p.fVal == a)) {
if (U == null) {
- U = new HashSet<RBBINode>();
+ U = new HashSet<>();
}
U.addAll(p.fFollowPos);
}
@@ -611,7 +609,66 @@ class RBBITableBuilder {
}
}
-
+ /**
+ * mapLookAheadRules
+ *
+ */
+ void mapLookAheadRules() {
+ fLookAheadRuleMap = new int[fRB.fScanner.numRules() + 1];
+ int laSlotsInUse = 0;
+
+ for (RBBIStateDescriptor sd: fDStates) {
+ int laSlotForState = 0;
+
+ // Establish the look-ahead slot for this state, if the state covers
+ // any look-ahead nodes - corresponding to the '/' in look-ahead rules.
+
+ // If any of the look-ahead nodes already have a slot assigned, use it,
+ // otherwise assign a new one.
+
+ boolean sawLookAheadNode = false;
+ for (RBBINode node: sd.fPositions) {
+ if (node.fType != RBBINode.lookAhead) {
+ continue;
+ }
+ sawLookAheadNode = true;
+ int ruleNum = node.fVal; // Set when rule was originally parsed.
+ assert(ruleNum < fLookAheadRuleMap.length);
+ assert(ruleNum > 0);
+ int laSlot = fLookAheadRuleMap[ruleNum];
+ if (laSlot != 0) {
+ if (laSlotForState == 0) {
+ laSlotForState = laSlot;
+ } else {
+ // TODO: figure out if this can fail, change to setting an error code if so.
+ assert(laSlot == laSlotForState);
+ }
+ }
+ }
+ if (!sawLookAheadNode) {
+ continue;
+ }
+
+ if (laSlotForState == 0) {
+ laSlotForState = ++laSlotsInUse;
+ }
+
+ // For each look ahead node covered by this state,
+ // set the mapping from the node's rule number to the look ahead slot.
+ // There can be multiple nodes/rule numbers going to the same la slot.
+
+ for (RBBINode node: sd.fPositions) {
+ if (node.fType != RBBINode.lookAhead) {
+ continue;
+ }
+ int ruleNum = node.fVal; // Set when rule was originally parsed.
+ int existingVal = fLookAheadRuleMap[ruleNum];
+ assert(existingVal == 0 || existingVal == laSlotForState);
+ fLookAheadRuleMap[ruleNum] = laSlotForState;
+ }
+ }
+
+ }
//-----------------------------------------------------------------------------
//
@@ -623,7 +680,7 @@ class RBBITableBuilder {
//
//-----------------------------------------------------------------------------
void flagAcceptingStates() {
- List<RBBINode> endMarkerNodes = new ArrayList<RBBINode>();
+ List<RBBINode> endMarkerNodes = new ArrayList<>();
RBBINode endMarker;
int i;
int n;
@@ -641,29 +698,20 @@ class RBBITableBuilder {
// If no other value was specified, force it to -1.
if (sd.fAccepting==0) {
- // State hasn't been marked as accepting yet. Do it now.
- sd.fAccepting = endMarker.fVal;
+ // State hasn't been marked as accepting yet. Do it now.
+ sd.fAccepting = fLookAheadRuleMap[endMarker.fVal];
if (sd.fAccepting == 0) {
sd.fAccepting = -1;
- }
+ }
}
if (sd.fAccepting==-1 && endMarker.fVal != 0) {
- // Both lookahead and non-lookahead accepting for this state.
- // Favor the look-ahead. Expedient for line break.
- // TODO: need a more elegant resolution for conflicting rules.
- sd.fAccepting = endMarker.fVal;
- }
- // implicit else:
- // if sd.fAccepting already had a value other than 0 or -1, leave it be.
-
- // If the end marker node is from a look-ahead rule, set
- // the fLookAhead field for this state also.
- if (endMarker.fLookAheadEnd) {
- // TODO: don't change value if already set?
- // TODO: allow for more than one active look-ahead rule in engine.
- // Make value here an index to a side array in engine?
- sd.fLookAhead = sd.fAccepting;
+ // Both lookahead and non-lookahead accepting for this state.
+ // Favor the look-ahead, because a look-ahead match needs to
+ // immediately stop the run-time engine. First match, not longest.
+ sd.fAccepting = fLookAheadRuleMap[endMarker.fVal];
}
+ // implicit else:
+ // if sd.fAccepting already had a value other than 0 or -1, leave it be.
}
}
}
@@ -676,7 +724,7 @@ class RBBITableBuilder {
//
//-----------------------------------------------------------------------------
void flagLookAheadStates() {
- List<RBBINode> lookAheadNodes = new ArrayList<RBBINode>();
+ List<RBBINode> lookAheadNodes = new ArrayList<>();
RBBINode lookAheadNode;
int i;
int n;
@@ -684,11 +732,12 @@ class RBBITableBuilder {
fRB.fTreeRoots[fRootIx].findNodes(lookAheadNodes, RBBINode.lookAhead);
for (i=0; i<lookAheadNodes.size(); i++) {
lookAheadNode = lookAheadNodes.get(i);
-
for (n=0; n<fDStates.size(); n++) {
RBBIStateDescriptor sd = fDStates.get(n);
if (sd.fPositions.contains(lookAheadNode)) {
- sd.fLookAhead = lookAheadNode.fVal;
+ int lookaheadSlot = fLookAheadRuleMap[lookAheadNode.fVal];
+ assert(sd.fLookAhead == 0 || sd.fLookAhead == lookaheadSlot);
+ sd.fLookAhead = lookaheadSlot;
}
}
}
@@ -703,7 +752,7 @@ class RBBITableBuilder {
//
//-----------------------------------------------------------------------------
void flagTaggedStates() {
- List<RBBINode> tagNodes = new ArrayList<RBBINode>();
+ List<RBBINode> tagNodes = new ArrayList<>();
RBBINode tagNode;
int i;
int n;
@@ -766,12 +815,12 @@ class RBBITableBuilder {
fRB.fRuleStatusVals.add(Integer.valueOf(1)); // Num of statuses in group
fRB.fRuleStatusVals.add(Integer.valueOf(0)); // and our single status of zero
- SortedSet<Integer> s0 = new TreeSet<Integer>();
- Integer izero = Integer.valueOf(0);
- fRB.fStatusSets.put(s0, izero);
- SortedSet<Integer> s1 = new TreeSet<Integer>();
- s1.add(izero);
- fRB.fStatusSets.put(s0, izero);
+ SortedSet<Integer> s0 = new TreeSet<>(); // mapping for rules with no explicit tagging
+ fRB.fStatusSets.put(s0, Integer.valueOf(0)); // (key is an empty set).
+
+ SortedSet<Integer> s1 = new TreeSet<>(); // mapping for rules with explicit tagging of {0}
+ s1.add(Integer.valueOf(0));
+ fRB.fStatusSets.put(s1, Integer.valueOf(0));
}
// For each state, check whether the state's status tag values are
@@ -987,16 +1036,6 @@ class RBBITableBuilder {
}
sd.fDtran[col] = newVal;
}
- if (sd.fAccepting == duplState) {
- sd.fAccepting = keepState;
- } else if (sd.fAccepting > duplState) {
- sd.fAccepting--;
- }
- if (sd.fLookAhead == duplState) {
- sd.fLookAhead = keepState;
- } else if (sd.fLookAhead > duplState) {
- sd.fLookAhead--;
- }
}
}
@@ -1168,7 +1207,7 @@ class RBBITableBuilder {
// fLookAhead, etc. are not needed for the safe table, and are omitted at this stage of building.
assert(fSafeTable == null);
- fSafeTable = new ArrayList<short[]>();
+ fSafeTable = new ArrayList<>();
for (int row=0; row<numCharClasses + 2; ++row) {
fSafeTable.add(new short[numCharClasses]);
}
diff --git a/android_icu4j/src/main/java/android/icu/text/RelativeDateTimeFormatter.java b/android_icu4j/src/main/java/android/icu/text/RelativeDateTimeFormatter.java
index d6a538fdf..dd2e4c6ec 100644
--- a/android_icu4j/src/main/java/android/icu/text/RelativeDateTimeFormatter.java
+++ b/android_icu4j/src/main/java/android/icu/text/RelativeDateTimeFormatter.java
@@ -9,7 +9,6 @@
*/
package android.icu.text;
-import java.io.IOException;
import java.io.InvalidObjectException;
import java.text.AttributedCharacterIterator;
import java.text.Format;
@@ -26,13 +25,12 @@ import android.icu.impl.SimpleFormatterImpl;
import android.icu.impl.SoftCache;
import android.icu.impl.StandardPlural;
import android.icu.impl.UResource;
+import android.icu.impl.Utility;
import android.icu.impl.number.DecimalQuantity;
import android.icu.impl.number.DecimalQuantity_DualStorageBCD;
-import android.icu.impl.number.SimpleModifier;
import android.icu.lang.UCharacter;
import android.icu.util.Calendar;
import android.icu.util.ICUException;
-import android.icu.util.ICUUncheckedIOException;
import android.icu.util.ULocale;
import android.icu.util.UResourceBundle;
@@ -223,7 +221,7 @@ public final class RelativeDateTimeFormatter {
/**
* Quarter
- * @hide draft / provisional / internal are hidden on Android
+ * @hide Hide new API in Android temporarily
*/
QUARTER,
@@ -366,22 +364,17 @@ public final class RelativeDateTimeFormatter {
* constants defined here.
* <p>
* @hide Only a subset of ICU is exposed in Android
- * @hide draft / provisional / internal are hidden on Android
*/
public static class Field extends Format.Field {
private static final long serialVersionUID = -5327685528663492325L;
/**
* Represents a literal text string, like "tomorrow" or "days ago".
- *
- * @hide draft / provisional / internal are hidden on Android
*/
public static final Field LITERAL = new Field("literal");
/**
* Represents a number quantity, like "3" in "3 days ago".
- *
- * @hide draft / provisional / internal are hidden on Android
*/
public static final Field NUMERIC = new Field("numeric");
@@ -415,7 +408,6 @@ public final class RelativeDateTimeFormatter {
*
* @author sffc
* @hide Only a subset of ICU is exposed in Android
- * @hide draft / provisional / internal are hidden on Android
*/
public static class FormattedRelativeDateTime implements FormattedValue {
@@ -427,8 +419,6 @@ public final class RelativeDateTimeFormatter {
/**
* {@inheritDoc}
- *
- * @hide draft / provisional / internal are hidden on Android
*/
@Override
public String toString() {
@@ -437,8 +427,6 @@ public final class RelativeDateTimeFormatter {
/**
* {@inheritDoc}
- *
- * @hide draft / provisional / internal are hidden on Android
*/
@Override
public int length() {
@@ -447,8 +435,6 @@ public final class RelativeDateTimeFormatter {
/**
* {@inheritDoc}
- *
- * @hide draft / provisional / internal are hidden on Android
*/
@Override
public char charAt(int index) {
@@ -457,8 +443,6 @@ public final class RelativeDateTimeFormatter {
/**
* {@inheritDoc}
- *
- * @hide draft / provisional / internal are hidden on Android
*/
@Override
public CharSequence subSequence(int start, int end) {
@@ -467,24 +451,14 @@ public final class RelativeDateTimeFormatter {
/**
* {@inheritDoc}
- *
- * @hide draft / provisional / internal are hidden on Android
*/
@Override
public <A extends Appendable> A appendTo(A appendable) {
- try {
- appendable.append(string);
- } catch (IOException e) {
- // Throw as an unchecked exception to avoid users needing try/catch
- throw new ICUUncheckedIOException(e);
- }
- return appendable;
+ return Utility.appendTo(string, appendable);
}
/**
* {@inheritDoc}
- *
- * @hide draft / provisional / internal are hidden on Android
*/
@Override
public boolean nextPosition(ConstrainedFieldPosition cfpos) {
@@ -493,8 +467,6 @@ public final class RelativeDateTimeFormatter {
/**
* {@inheritDoc}
- *
- * @hide draft / provisional / internal are hidden on Android
*/
@Override
public AttributedCharacterIterator toCharacterIterator() {
@@ -626,7 +598,7 @@ public final class RelativeDateTimeFormatter {
* @return the formatted relative datetime
* @throws IllegalArgumentException if direction is something other than
* NEXT or LAST.
- * @hide draft / provisional / internal are hidden on Android
+ * @hide Hide new API in Android temporarily
*/
public FormattedRelativeDateTime formatToValue(double quantity, Direction direction, RelativeUnit unit) {
checkNoAdjustForContext();
@@ -654,8 +626,7 @@ public final class RelativeDateTimeFormatter {
StandardPlural pluralForm = StandardPlural.orOtherFromString(pluralKeyword);
String compiledPattern = getRelativeUnitPluralPattern(style, unit, pastFutureIndex, pluralForm);
- SimpleModifier modifier = new SimpleModifier(compiledPattern, Field.LITERAL, false);
- modifier.formatAsPrefixSuffix(output, 0, output.length());
+ SimpleFormatterImpl.formatPrefixSuffix(compiledPattern, Field.LITERAL, 0, output.length(), output);
return output;
}
@@ -695,7 +666,7 @@ public final class RelativeDateTimeFormatter {
* date, e.g. RelativeDateTimeUnit.WEEK,
* RelativeDateTimeUnit.FRIDAY.
* @return The formatted string (may be empty in case of error)
- * @hide draft / provisional / internal are hidden on Android
+ * @hide Hide new API in Android temporarily
*/
public FormattedRelativeDateTime formatNumericToValue(double offset, RelativeDateTimeUnit unit) {
checkNoAdjustForContext();
@@ -768,7 +739,7 @@ public final class RelativeDateTimeFormatter {
* return null to signal that no formatted string is available.
* @throws IllegalArgumentException if the direction is incompatible with
* unit this can occur with NOW which can only take PLAIN.
- * @hide draft / provisional / internal are hidden on Android
+ * @hide Hide new API in Android temporarily
*/
public FormattedRelativeDateTime formatToValue(Direction direction, AbsoluteUnit unit) {
checkNoAdjustForContext();
@@ -838,7 +809,7 @@ public final class RelativeDateTimeFormatter {
* date, e.g. RelativeDateTimeUnit.WEEK,
* RelativeDateTimeUnit.FRIDAY.
* @return The formatted string (may be empty in case of error)
- * @hide draft / provisional / internal are hidden on Android
+ * @hide Hide new API in Android temporarily
*/
public FormattedRelativeDateTime formatToValue(double offset, RelativeDateTimeUnit unit) {
checkNoAdjustForContext();
diff --git a/android_icu4j/src/main/java/android/icu/text/RuleBasedBreakIterator.java b/android_icu4j/src/main/java/android/icu/text/RuleBasedBreakIterator.java
index 7425ab9f4..df416d1d2 100644
--- a/android_icu4j/src/main/java/android/icu/text/RuleBasedBreakIterator.java
+++ b/android_icu4j/src/main/java/android/icu/text/RuleBasedBreakIterator.java
@@ -916,9 +916,14 @@ public class RuleBasedBreakIterator extends BreakIterator {
}
}
+ // If we are at the position of the '/' in a look-ahead (hard break) rule;
+ // record the current position, to be returned later, if the full rule matches.
+ // TODO: Move this check before the previous check of fAccepting.
+ // This would enable hard-break rules with no following context.
+ // But there are line break test failures when trying this. Investigate.
+ // Issue ICU-20837
int rule = stateTable[row + RBBIDataWrapper.LOOKAHEAD];
if (rule != 0) {
- // At the position of a '/' in a look-ahead match. Record it.
int pos = text.getIndex();
if (c >= UTF16.SUPPLEMENTARY_MIN_VALUE && c <= UTF16.CODEPOINT_MAX_VALUE) {
// The iterator has been left in the middle of a surrogate pair.
diff --git a/android_icu4j/src/main/java/android/icu/text/SimpleDateFormat.java b/android_icu4j/src/main/java/android/icu/text/SimpleDateFormat.java
index ac698bf60..67a645507 100644
--- a/android_icu4j/src/main/java/android/icu/text/SimpleDateFormat.java
+++ b/android_icu4j/src/main/java/android/icu/text/SimpleDateFormat.java
@@ -1386,10 +1386,10 @@ public class SimpleDateFormat extends DateFormat {
}
if (useFastFormat) {
subFormat(toAppendTo, item.type, item.length, toAppendTo.length(),
- i, capitalizationContext, pos, cal);
+ i, capitalizationContext, pos, item.type, cal);
} else {
toAppendTo.append(subFormat(item.type, item.length, toAppendTo.length(),
- i, capitalizationContext, pos, cal));
+ i, capitalizationContext, pos, item.type, cal));
}
if (attributes != null) {
// Check the sub format length
@@ -1535,7 +1535,7 @@ public class SimpleDateFormat extends DateFormat {
throws IllegalArgumentException
{
// Note: formatData is ignored
- return subFormat(ch, count, beginOffset, 0, DisplayContext.CAPITALIZATION_NONE, pos, cal);
+ return subFormat(ch, count, beginOffset, 0, DisplayContext.CAPITALIZATION_NONE, pos, ch, cal);
}
/**
@@ -1543,17 +1543,17 @@ public class SimpleDateFormat extends DateFormat {
* adds fieldNum and capitalizationContext parameters.
*
* @deprecated This API is ICU internal only.
- * @hide original deprecated declaration
* @hide draft / provisional / internal are hidden on Android
*/
@Deprecated
protected String subFormat(char ch, int count, int beginOffset,
int fieldNum, DisplayContext capitalizationContext,
FieldPosition pos,
+ char patternCharToOutput,
Calendar cal)
{
StringBuffer buf = new StringBuffer();
- subFormat(buf, ch, count, beginOffset, fieldNum, capitalizationContext, pos, cal);
+ subFormat(buf, ch, count, beginOffset, fieldNum, capitalizationContext, pos, patternCharToOutput, cal);
return buf.toString();
}
@@ -1567,7 +1567,6 @@ public class SimpleDateFormat extends DateFormat {
* has to pass it in to us.
*
* @deprecated This API is ICU internal only.
- * @hide original deprecated declaration
* @hide draft / provisional / internal are hidden on Android
*/
@Deprecated
@@ -1576,6 +1575,7 @@ public class SimpleDateFormat extends DateFormat {
char ch, int count, int beginOffset,
int fieldNum, DisplayContext capitalizationContext,
FieldPosition pos,
+ char patternCharToOutput,
Calendar cal) {
final int maxIntCount = Integer.MAX_VALUE;
@@ -1934,7 +1934,10 @@ public class SimpleDateFormat extends DateFormat {
if (toAppend == null) {
// Time isn't exactly midnight or noon (as displayed) or localized string doesn't
// exist for requested period. Fall back to am/pm instead.
- subFormat(buf, 'a', count, beginOffset, fieldNum, capitalizationContext, pos, cal);
+ // We are passing a different patternCharToOutput because we want to add
+ // 'b' to field position. This makes this fallback stable when
+ // there is a data change on locales.
+ subFormat(buf, 'a', count, beginOffset, fieldNum, capitalizationContext, pos, 'b', cal);
} else {
buf.append(toAppend);
}
@@ -1949,8 +1952,11 @@ public class SimpleDateFormat extends DateFormat {
if (ruleSet == null) {
// Data doesn't exist for the locale we're looking for.
// Fall back to am/pm.
- subFormat(buf, 'a', count, beginOffset, fieldNum, capitalizationContext, pos, cal);
- break;
+ // We are passing a different patternCharToOutput because we want to add
+ // 'B' to field position. This makes this fallback stable when
+ // there is a data change on locales.
+ subFormat(buf, 'a', count, beginOffset, fieldNum, capitalizationContext, pos, 'B', cal);
+ return;
}
// Get current display time.
@@ -2015,7 +2021,11 @@ public class SimpleDateFormat extends DateFormat {
if (periodType == DayPeriodRules.DayPeriod.AM ||
periodType == DayPeriodRules.DayPeriod.PM ||
toAppend == null) {
- subFormat(buf, 'a', count, beginOffset, fieldNum, capitalizationContext, pos, cal);
+ // We are passing a different patternCharToOutput because we want to add
+ // 'B' to field position. This makes this fallback stable when
+ // there is a data change on locales.
+ subFormat(buf, 'a', count, beginOffset, fieldNum, capitalizationContext, pos, 'B', cal);
+ return;
}
else {
buf.append(toAppend);
@@ -2079,12 +2089,13 @@ public class SimpleDateFormat extends DateFormat {
}
// Set the FieldPosition (for the first occurrence only)
+ int outputCharIndex = getIndexFromChar(patternCharToOutput);
if (pos.getBeginIndex() == pos.getEndIndex()) {
- if (pos.getField() == PATTERN_INDEX_TO_DATE_FORMAT_FIELD[patternCharIndex]) {
+ if (pos.getField() == PATTERN_INDEX_TO_DATE_FORMAT_FIELD[outputCharIndex]) {
pos.setBeginIndex(beginOffset);
pos.setEndIndex(beginOffset + buf.length() - bufstart);
} else if (pos.getFieldAttribute() ==
- PATTERN_INDEX_TO_DATE_FORMAT_ATTRIBUTE[patternCharIndex]) {
+ PATTERN_INDEX_TO_DATE_FORMAT_ATTRIBUTE[outputCharIndex]) {
pos.setBeginIndex(beginOffset);
pos.setEndIndex(beginOffset + buf.length() - bufstart);
}
@@ -4341,10 +4352,10 @@ public class SimpleDateFormat extends DateFormat {
PatternItem item = (PatternItem)items[i];
if (useFastFormat) {
subFormat(appendTo, item.type, item.length, appendTo.length(),
- i, capSetting, pos, fromCalendar);
+ i, capSetting, pos, item.type, fromCalendar);
} else {
appendTo.append(subFormat(item.type, item.length, appendTo.length(),
- i, capSetting, pos, fromCalendar));
+ i, capSetting, pos, item.type, fromCalendar));
}
}
}
@@ -4359,10 +4370,10 @@ public class SimpleDateFormat extends DateFormat {
PatternItem item = (PatternItem)items[i];
if (useFastFormat) {
subFormat(appendTo, item.type, item.length, appendTo.length(),
- i, capSetting, pos, toCalendar);
+ i, capSetting, pos, item.type, toCalendar);
} else {
appendTo.append(subFormat(item.type, item.length, appendTo.length(),
- i, capSetting, pos, toCalendar));
+ i, capSetting, pos, item.type, toCalendar));
}
}
}
diff --git a/android_icu4j/src/main/java/android/icu/text/UFormat.java b/android_icu4j/src/main/java/android/icu/text/UFormat.java
index d4057966d..30a81141b 100644
--- a/android_icu4j/src/main/java/android/icu/text/UFormat.java
+++ b/android_icu4j/src/main/java/android/icu/text/UFormat.java
@@ -31,15 +31,12 @@ public abstract class UFormat extends Format {
* SpanField classes usually have an associated value.
*
* @hide Only a subset of ICU is exposed in Android
- * @hide draft / provisional / internal are hidden on Android
*/
public static abstract class SpanField extends Format.Field {
private static final long serialVersionUID = -4732719509273350606L;
/**
* Construct a new instance.
- *
- * @hide draft / provisional / internal are hidden on Android
*/
protected SpanField(String name) {
super(name);
diff --git a/android_icu4j/src/main/java/android/icu/util/BytesTrie.java b/android_icu4j/src/main/java/android/icu/util/BytesTrie.java
index 2a0ff789b..583fc3e14 100644
--- a/android_icu4j/src/main/java/android/icu/util/BytesTrie.java
+++ b/android_icu4j/src/main/java/android/icu/util/BytesTrie.java
@@ -53,8 +53,6 @@ public final class BytesTrie implements Cloneable, Iterable<BytesTrie.Entry> {
* Makes a shallow copy of the other trie reader object and its state.
* Does not copy the byte array which will be shared.
* Same as clone() but without the throws clause.
- *
- * @hide draft / provisional / internal are hidden on Android
*/
public BytesTrie(BytesTrie other) {
bytes_ = other.bytes_;
@@ -89,7 +87,6 @@ public final class BytesTrie implements Cloneable, Iterable<BytesTrie.Entry> {
*
* @return opaque state value
* @see #resetToState64
- * @hide draft / provisional / internal are hidden on Android
*/
public long getState64() {
return ((long)remainingMatchLength_ << 32) | pos_;
@@ -107,7 +104,6 @@ public final class BytesTrie implements Cloneable, Iterable<BytesTrie.Entry> {
* @see #getState64
* @see #resetToState
* @see #reset
- * @hide draft / provisional / internal are hidden on Android
*/
public BytesTrie resetToState64(long state) {
remainingMatchLength_ = (int)(state >> 32);
diff --git a/android_icu4j/src/main/java/android/icu/util/Calendar.java b/android_icu4j/src/main/java/android/icu/util/Calendar.java
index 12e750e74..4c4a7e40f 100644
--- a/android_icu4j/src/main/java/android/icu/util/Calendar.java
+++ b/android_icu4j/src/main/java/android/icu/util/Calendar.java
@@ -4580,7 +4580,7 @@ public abstract class Calendar implements Serializable, Cloneable, Comparable<Ca
}
/**
- * Simple, immutable struct-like class for access to the CLDR weekend data.
+ * Simple, immutable struct-like class for access to the CLDR week data.
*/
public static final class WeekData {
/**
@@ -4675,7 +4675,7 @@ public abstract class Calendar implements Serializable, Cloneable, Comparable<Ca
}
/**
- * <strong>[icu]</strong> Return simple, immutable struct-like class for access to the CLDR weekend data.
+ * <strong>[icu]</strong> Return simple, immutable struct-like class for access to the CLDR week data.
* @param region The input region. The results are undefined if the region code is not valid.
* @return the WeekData for the input region. It is never null.
*/
@@ -4684,7 +4684,7 @@ public abstract class Calendar implements Serializable, Cloneable, Comparable<Ca
}
/**
- * <strong>[icu]</strong> Return simple, immutable struct-like class for access to the weekend data in this calendar.
+ * <strong>[icu]</strong> Return simple, immutable struct-like class for access to the week data in this calendar.
* @return the WeekData for this calendar.
*/
public WeekData getWeekData() {
@@ -4751,7 +4751,7 @@ public abstract class Calendar implements Serializable, Cloneable, Comparable<Ca
private static final WeekDataCache WEEK_DATA_CACHE = new WeekDataCache();
/*
- * Set this calendar to contain week and weekend data for the given region.
+ * Set this calendar to contain week and week data for the given region.
*/
private void setWeekData(String region) {
if (region == null) {
diff --git a/android_icu4j/src/main/java/android/icu/util/CharsTrie.java b/android_icu4j/src/main/java/android/icu/util/CharsTrie.java
index a01236b44..ebfd7e3c0 100644
--- a/android_icu4j/src/main/java/android/icu/util/CharsTrie.java
+++ b/android_icu4j/src/main/java/android/icu/util/CharsTrie.java
@@ -56,8 +56,6 @@ public final class CharsTrie implements Cloneable, Iterable<CharsTrie.Entry> {
* Makes a shallow copy of the other trie reader object and its state.
* Does not copy the char array which will be shared.
* Same as clone() but without the throws clause.
- *
- * @hide draft / provisional / internal are hidden on Android
*/
public CharsTrie(CharsTrie other) {
chars_ = other.chars_;
@@ -92,7 +90,6 @@ public final class CharsTrie implements Cloneable, Iterable<CharsTrie.Entry> {
*
* @return opaque state value
* @see #resetToState64
- * @hide draft / provisional / internal are hidden on Android
*/
public long getState64() {
return ((long)remainingMatchLength_ << 32) | pos_;
@@ -110,7 +107,6 @@ public final class CharsTrie implements Cloneable, Iterable<CharsTrie.Entry> {
* @see #getState64
* @see #resetToState
* @see #reset
- * @hide draft / provisional / internal are hidden on Android
*/
public CharsTrie resetToState64(long state) {
remainingMatchLength_ = (int)(state >> 32);
diff --git a/android_icu4j/src/main/java/android/icu/util/Currency.java b/android_icu4j/src/main/java/android/icu/util/Currency.java
index 93865538b..4e141b39c 100644
--- a/android_icu4j/src/main/java/android/icu/util/Currency.java
+++ b/android_icu4j/src/main/java/android/icu/util/Currency.java
@@ -87,13 +87,35 @@ public class Currency extends MeasureUnit {
/**
* Selector for getName() indicating the narrow currency symbol.
- * The narrow currency symbol is similar to the regular currency
- * symbol, but it always takes the shortest form: for example,
- * "$" instead of "US$" for USD in en-CA.
+ * <p>
+ * The narrow currency symbol is similar to the regular currency symbol,
+ * but it always takes the shortest form;
+ * for example, "$" instead of "US$" for USD in en-CA.
*/
public static final int NARROW_SYMBOL_NAME = 3;
/**
+ * Selector for getName() indicating the formal currency symbol.
+ * <p>
+ * The formal currency symbol is similar to the regular currency symbol,
+ * but it always takes the form used in formal settings such as banking;
+ * for example, "NT$" instead of "$" for TWD in zh-TW.
+ *
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public static final int FORMAL_SYMBOL_NAME = 4;
+
+ /**
+ * Selector for getName() indicating the variant currency symbol.
+ * <p>
+ * The variant symbol for a currency is an alternative symbol that is not
+ * necessarily as widely used as the regular symbol.
+ *
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public static final int VARIANT_SYMBOL_NAME = 5;
+
+ /**
* Currency Usage used for Decimal Format
*/
public enum CurrencyUsage{
@@ -545,6 +567,10 @@ public class Currency extends MeasureUnit {
return names.getSymbol(subType);
case NARROW_SYMBOL_NAME:
return names.getNarrowSymbol(subType);
+ case FORMAL_SYMBOL_NAME:
+ return names.getFormalSymbol(subType);
+ case VARIANT_SYMBOL_NAME:
+ return names.getVariantSymbol(subType);
case LONG_NAME:
return names.getName(subType);
default:
diff --git a/android_icu4j/src/main/java/android/icu/util/GlobalizationPreferences.java b/android_icu4j/src/main/java/android/icu/util/GlobalizationPreferences.java
index f51ea61c8..52e797fd7 100644
--- a/android_icu4j/src/main/java/android/icu/util/GlobalizationPreferences.java
+++ b/android_icu4j/src/main/java/android/icu/util/GlobalizationPreferences.java
@@ -9,7 +9,6 @@
*/
package android.icu.util;
-import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
@@ -19,6 +18,7 @@ import java.util.List;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
+import java.util.Set;
import android.icu.impl.Utility;
import android.icu.text.BreakIterator;
@@ -249,14 +249,10 @@ public class GlobalizationPreferences implements Freezable<GlobalizationPreferen
if (isFrozen()) {
throw new UnsupportedOperationException("Attempt to modify immutable object");
}
- ULocale[] acceptLocales = null;
- try {
- acceptLocales = ULocale.parseAcceptLanguage(acceptLanguageString, true);
- } catch (ParseException pe) {
- //TODO: revisit after 3.8
- throw new IllegalArgumentException("Invalid Accept-Language string");
- }
- return setLocales(acceptLocales);
+ Set<ULocale> acceptSet = LocalePriorityList.add(acceptLanguageString).build().getULocales();
+ // processLocales() wants a List even though it only iterates front-to-back.
+ locales = processLocales(new ArrayList<>(acceptSet));
+ return this;
}
/**
@@ -784,6 +780,9 @@ public class GlobalizationPreferences implements Freezable<GlobalizationPreferen
* @hide draft / provisional / internal are hidden on Android
*/
protected List<ULocale> processLocales(List<ULocale> inputLocales) {
+ // Note: Some of the callers, and non-ICU call sites, could be simpler/more efficient
+ // if this method took a Collection or even an Iterable.
+ // Maybe we can change it since this is still @draft and probably not widely overridden.
List<ULocale> result = new ArrayList<>();
/*
* Step 1: Relocate later occurrence of more specific locale
@@ -793,9 +792,7 @@ public class GlobalizationPreferences implements Freezable<GlobalizationPreferen
* Before - en_US, fr_FR, zh, en_US_Boston, zh_TW, zh_Hant, fr_CA
* After - en_US_Boston, en_US, fr_FR, zh_TW, zh_Hant, zh, fr_CA
*/
- for (int i = 0; i < inputLocales.size(); i++) {
- ULocale uloc = inputLocales.get(i);
-
+ for (ULocale uloc : inputLocales) {
String language = uloc.getLanguage();
String script = uloc.getScript();
String country = uloc.getCountry();
diff --git a/android_icu4j/src/main/java/android/icu/util/LocaleMatcher.java b/android_icu4j/src/main/java/android/icu/util/LocaleMatcher.java
index e5868561d..39a54ac90 100644
--- a/android_icu4j/src/main/java/android/icu/util/LocaleMatcher.java
+++ b/android_icu4j/src/main/java/android/icu/util/LocaleMatcher.java
@@ -11,12 +11,11 @@ package android.icu.util;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.HashMap;
import java.util.Iterator;
-import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
-import java.util.Objects;
import android.icu.impl.locale.LSR;
import android.icu.impl.locale.LocaleDistance;
@@ -65,7 +64,7 @@ import android.icu.impl.locale.XLikelySubtags;
* @hide Only a subset of ICU is exposed in Android
*/
public final class LocaleMatcher {
- private static final LSR UND_LSR = new LSR("und","","");
+ private static final LSR UND_LSR = new LSR("und","","", LSR.EXPLICIT_LSR);
// In ULocale, "und" and "" make the same object.
private static final ULocale UND_ULOCALE = new ULocale("und");
// In Locale, "und" and "" make different objects.
@@ -154,6 +153,40 @@ public final class LocaleMatcher {
}
/**
+ * Builder option for whether to include or ignore one-way (fallback) match data.
+ * The LocaleMatcher uses CLDR languageMatch data which includes fallback (oneway=true) entries.
+ * Sometimes it is desirable to ignore those.
+ *
+ * <p>For example, consider a web application with the UI in a given language,
+ * with a link to another, related web app.
+ * The link should include the UI language, and the target server may also use
+ * the client’s Accept-Language header data.
+ * The target server has its own list of supported languages.
+ * One may want to favor UI language consistency, that is,
+ * if there is a decent match for the original UI language, we want to use it,
+ * but not if it is merely a fallback.
+ *
+ * @see LocaleMatcher.Builder#setDirection(LocaleMatcher.Direction)
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public enum Direction {
+ /**
+ * Locale matching includes one-way matches such as Breton→French. (default)
+ *
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ WITH_ONE_WAY,
+ /**
+ * Locale matching limited to two-way matches including e.g. Danish↔Norwegian
+ * but ignoring one-way matches.
+ *
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ ONLY_TWO_WAY
+ }
+
+ /**
* Data for the best-matching pair of a desired and a supported locale.
*
* @hide Only a subset of ICU is exposed in Android
@@ -309,6 +342,7 @@ public final class LocaleMatcher {
private final int thresholdDistance;
private final int demotionPerDesiredLocale;
private final FavorSubtag favorSubtag;
+ private final Direction direction;
// These are in input order.
private final ULocale[] supportedULocales;
@@ -319,9 +353,9 @@ public final class LocaleMatcher {
// The distance lookup loops over the supportedLSRs and returns the index of the best match.
private final LSR[] supportedLSRs;
private final int[] supportedIndexes;
+ private final int supportedLSRsLength;
private final ULocale defaultULocale;
private final Locale defaultLocale;
- private final int defaultLocaleIndex;
/**
* LocaleMatcher Builder.
@@ -336,6 +370,7 @@ public final class LocaleMatcher {
private Demotion demotion;
private ULocale defaultLocale;
private FavorSubtag favor;
+ private Direction direction;
private Builder() {}
@@ -465,6 +500,19 @@ public final class LocaleMatcher {
}
/**
+ * Option for whether to include or ignore one-way (fallback) match data.
+ * By default, they are included.
+ *
+ * @param direction the match direction to set.
+ * @return this Builder object
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public Builder setDirection(Direction direction) {
+ this.direction = direction;
+ return this;
+ }
+
+ /**
* <i>Internal only!</i>
*
* @param thresholdDistance the thresholdDistance to set, with -1 = default
@@ -500,19 +548,19 @@ public final class LocaleMatcher {
public String toString() {
StringBuilder s = new StringBuilder().append("{LocaleMatcher.Builder");
if (supportedLocales != null && !supportedLocales.isEmpty()) {
- s.append(" supported={").append(supportedLocales.toString()).append('}');
+ s.append(" supported={").append(supportedLocales).append('}');
}
if (defaultLocale != null) {
- s.append(" default=").append(defaultLocale.toString());
+ s.append(" default=").append(defaultLocale);
}
if (favor != null) {
- s.append(" distance=").append(favor.toString());
+ s.append(" distance=").append(favor);
}
if (thresholdDistance >= 0) {
s.append(String.format(" threshold=%d", thresholdDistance));
}
if (demotion != null) {
- s.append(" demotion=").append(demotion.toString());
+ s.append(" demotion=").append(demotion);
}
return s.append('}').toString();
}
@@ -554,114 +602,102 @@ public final class LocaleMatcher {
private LocaleMatcher(Builder builder) {
thresholdDistance = builder.thresholdDistance < 0 ?
LocaleDistance.INSTANCE.getDefaultScriptDistance() : builder.thresholdDistance;
- int supportedLocalesLength = builder.supportedLocales != null ?
- builder.supportedLocales.size() : 0;
ULocale udef = builder.defaultLocale;
Locale def = null;
- int idef = -1;
+ LSR defLSR = null;
+ if (udef != null) {
+ def = udef.toLocale();
+ defLSR = getMaximalLsrOrUnd(udef);
+ }
// Store the supported locales in input order,
// so that when different types are used (e.g., java.util.Locale)
// we can return those by parallel index.
+ int supportedLocalesLength = builder.supportedLocales != null ?
+ builder.supportedLocales.size() : 0;
supportedULocales = new ULocale[supportedLocalesLength];
supportedLocales = new Locale[supportedLocalesLength];
// Supported LRSs in input order.
LSR lsrs[] = new LSR[supportedLocalesLength];
- // Also find the first supported locale whose LSR is
- // the same as that for the default locale.
- LSR defLSR = null;
- if (udef != null) {
- def = udef.toLocale();
- defLSR = getMaximalLsrOrUnd(udef);
- }
int i = 0;
if (supportedLocalesLength > 0) {
for (ULocale locale : builder.supportedLocales) {
supportedULocales[i] = locale;
supportedLocales[i] = locale.toLocale();
- LSR lsr = lsrs[i] = getMaximalLsrOrUnd(locale);
- if (idef < 0 && defLSR != null && lsr.equals(defLSR)) {
- idef = i;
- }
+ lsrs[i] = getMaximalLsrOrUnd(locale);
++i;
}
}
// We need an unordered map from LSR to first supported locale with that LSR,
- // and an ordered list of (LSR, supported index).
- // We use a LinkedHashMap for both,
- // and insert the supported locales in the following order:
+ // and an ordered list of (LSR, supported index) for
+ // the supported locales in the following order:
// 1. Default locale, if it is supported.
// 2. Priority locales (aka "paradigm locales") in builder order.
// 3. Remaining locales in builder order.
- supportedLsrToIndex = new LinkedHashMap<>(supportedLocalesLength);
- // Note: We could work with a single LinkedHashMap by storing ~i (the binary-not index)
- // for the default and paradigm locales, counting the number of those locales,
- // and keeping two indexes to fill the LSR and index arrays with
- // priority vs. normal locales. In that loop we would need to entry.setValue(~i)
- // to restore non-negative indexes in the map.
- // Probably saves little but less readable.
- Map<LSR, Integer> otherLsrToIndex = null;
- if (idef >= 0) {
- supportedLsrToIndex.put(defLSR, idef);
- }
+ supportedLsrToIndex = new HashMap<>(supportedLocalesLength);
+ supportedLSRs = new LSR[supportedLocalesLength];
+ supportedIndexes = new int[supportedLocalesLength];
+ int suppLength = 0;
+ // Determine insertion order.
+ // Add locales immediately that are equivalent to the default.
+ byte[] order = new byte[supportedLocalesLength];
+ int numParadigms = 0;
i = 0;
for (ULocale locale : supportedULocales) {
- if (i == idef) {
- ++i;
- continue;
- }
LSR lsr = lsrs[i];
if (defLSR == null) {
assert i == 0;
udef = locale;
def = supportedLocales[0];
defLSR = lsr;
- idef = 0;
- supportedLsrToIndex.put(lsr, 0);
- } else if (idef >= 0 && lsr.equals(defLSR)) {
- // lsr.equals(defLSR) means that this supported locale is
- // a duplicate of the default locale.
- // Either an explicit default locale is supported, and we added it before the loop,
- // or there is no explicit default locale, and this is
- // a duplicate of the first supported locale.
- // In both cases, idef >= 0 now, so otherwise we can skip the comparison.
- // For a duplicate, putIfAbsent() is a no-op, so nothing to do.
+ suppLength = putIfAbsent(lsr, 0, suppLength);
+ } else if (lsr.isEquivalentTo(defLSR)) {
+ suppLength = putIfAbsent(lsr, i, suppLength);
} else if (LocaleDistance.INSTANCE.isParadigmLSR(lsr)) {
- putIfAbsent(supportedLsrToIndex, lsr, i);
+ order[i] = 2;
+ ++numParadigms;
} else {
- if (otherLsrToIndex == null) {
- otherLsrToIndex = new LinkedHashMap<>(supportedLocalesLength);
- }
- putIfAbsent(otherLsrToIndex, lsr, i);
+ order[i] = 3;
}
++i;
}
- if (otherLsrToIndex != null) {
- supportedLsrToIndex.putAll(otherLsrToIndex);
+ // Add supported paradigm locales.
+ int paradigmLimit = suppLength + numParadigms;
+ for (i = 0; i < supportedLocalesLength && suppLength < paradigmLimit; ++i) {
+ if (order[i] == 2) {
+ suppLength = putIfAbsent(lsrs[i], i, suppLength);
+ }
}
- int supportedLSRsLength = supportedLsrToIndex.size();
- supportedLSRs = new LSR[supportedLSRsLength];
- supportedIndexes = new int[supportedLSRsLength];
- i = 0;
- for (Map.Entry<LSR, Integer> entry : supportedLsrToIndex.entrySet()) {
- supportedLSRs[i] = entry.getKey(); // = lsrs[entry.getValue()]
- supportedIndexes[i++] = entry.getValue();
+ // Add remaining supported locales.
+ for (i = 0; i < supportedLocalesLength; ++i) {
+ if (order[i] == 3) {
+ suppLength = putIfAbsent(lsrs[i], i, suppLength);
+ }
}
+ supportedLSRsLength = suppLength;
+ // If supportedLSRsLength < supportedLocalesLength then
+ // we waste as many array slots as there are duplicate supported LSRs,
+ // but the amount of wasted space is small as long as there are few duplicates.
defaultULocale = udef;
defaultLocale = def;
- defaultLocaleIndex = idef;
demotionPerDesiredLocale =
builder.demotion == Demotion.NONE ? 0 :
LocaleDistance.INSTANCE.getDefaultDemotionPerDesiredLocale(); // null or REGION
favorSubtag = builder.favor;
+ direction = builder.direction;
+ if (TRACE_MATCHER) {
+ System.err.printf("new LocaleMatcher: %s\n", toString());
+ }
}
- private static final void putIfAbsent(Map<LSR, Integer> lsrToIndex, LSR lsr, int i) {
- Integer index = lsrToIndex.get(lsr);
- if (index == null) {
- lsrToIndex.put(lsr, i);
+ private final int putIfAbsent(LSR lsr, int i, int suppLength) {
+ if (!supportedLsrToIndex.containsKey(lsr)) {
+ supportedLsrToIndex.put(lsr, i);
+ supportedLSRs[suppLength] = lsr;
+ supportedIndexes[suppLength++] = i;
}
+ return suppLength;
}
private static final LSR getMaximalLsrOrUnd(ULocale locale) {
@@ -806,7 +842,7 @@ public final class LocaleMatcher {
}
private Result defaultResult() {
- return new Result(null, defaultULocale, null, defaultLocale, -1, defaultLocaleIndex);
+ return new Result(null, defaultULocale, null, defaultLocale, -1, -1);
}
private Result makeResult(ULocale desiredLocale, ULocaleLsrIterator lsrIter, int suppIndex) {
@@ -904,26 +940,35 @@ public final class LocaleMatcher {
private int getBestSuppIndex(LSR desiredLSR, LsrIterator remainingIter) {
int desiredIndex = 0;
int bestSupportedLsrIndex = -1;
- for (int bestDistance = thresholdDistance;;) {
+ StringBuilder sb = null;
+ if (TRACE_MATCHER) {
+ sb = new StringBuilder("LocaleMatcher desired:");
+ }
+ for (int bestShiftedDistance = LocaleDistance.shiftDistance(thresholdDistance);;) {
+ if (TRACE_MATCHER) {
+ sb.append(' ').append(desiredLSR);
+ }
// Quick check for exact maximized LSR.
Integer index = supportedLsrToIndex.get(desiredLSR);
if (index != null) {
int suppIndex = index;
if (TRACE_MATCHER) {
- System.err.printf("Returning %s: desiredLSR=supportedLSR\n",
- supportedULocales[suppIndex]);
+ System.err.printf("%s --> best=%s: desiredLSR=supportedLSR\n",
+ sb, supportedULocales[suppIndex]);
}
if (remainingIter != null) { remainingIter.rememberCurrent(desiredIndex); }
return suppIndex;
}
int bestIndexAndDistance = LocaleDistance.INSTANCE.getBestIndexAndDistance(
- desiredLSR, supportedLSRs, bestDistance, favorSubtag);
+ desiredLSR, supportedLSRs, supportedLSRsLength,
+ bestShiftedDistance, favorSubtag, direction);
if (bestIndexAndDistance >= 0) {
- bestDistance = bestIndexAndDistance & 0xff;
+ bestShiftedDistance = LocaleDistance.getShiftedDistance(bestIndexAndDistance);
if (remainingIter != null) { remainingIter.rememberCurrent(desiredIndex); }
- bestSupportedLsrIndex = bestIndexAndDistance >> 8;
+ bestSupportedLsrIndex = LocaleDistance.getIndex(bestIndexAndDistance);
}
- if ((bestDistance -= demotionPerDesiredLocale) <= 0) {
+ if ((bestShiftedDistance -= LocaleDistance.shiftDistance(demotionPerDesiredLocale))
+ <= 0) {
break;
}
if (remainingIter == null || !remainingIter.hasNext()) {
@@ -934,14 +979,14 @@ public final class LocaleMatcher {
}
if (bestSupportedLsrIndex < 0) {
if (TRACE_MATCHER) {
- System.err.printf("Returning default %s: no good match\n", defaultULocale);
+ System.err.printf("%s --> best=default %s: no good match\n", sb, defaultULocale);
}
return -1;
}
int suppIndex = supportedIndexes[bestSupportedLsrIndex];
if (TRACE_MATCHER) {
- System.err.printf("Returning %s: best matching supported locale\n",
- supportedULocales[suppIndex]);
+ System.err.printf("%s --> best=%s: best matching supported locale\n",
+ sb, supportedULocales[suppIndex]);
}
return suppIndex;
}
@@ -966,11 +1011,16 @@ public final class LocaleMatcher {
@Deprecated
public double match(ULocale desired, ULocale desiredMax, ULocale supported, ULocale supportedMax) {
// Returns the inverse of the distance: That is, 1-distance(desired, supported).
- int distance = LocaleDistance.INSTANCE.getBestIndexAndDistance(
+ int indexAndDistance = LocaleDistance.INSTANCE.getBestIndexAndDistance(
getMaximalLsrOrUnd(desired),
- new LSR[] { getMaximalLsrOrUnd(supported) },
- thresholdDistance, favorSubtag) & 0xff;
- return (100 - distance) / 100.0;
+ new LSR[] { getMaximalLsrOrUnd(supported) }, 1,
+ LocaleDistance.shiftDistance(thresholdDistance), favorSubtag, direction);
+ double distance = LocaleDistance.getDistanceDouble(indexAndDistance);
+ if (TRACE_MATCHER) {
+ System.err.printf("LocaleMatcher distance(desired=%s, supported=%s)=%g\n",
+ String.valueOf(desired), String.valueOf(supported), distance);
+ }
+ return (100.0 - distance) / 100.0;
}
/**
@@ -996,16 +1046,20 @@ public final class LocaleMatcher {
@Override
public String toString() {
StringBuilder s = new StringBuilder().append("{LocaleMatcher");
- if (supportedULocales.length > 0) {
- s.append(" supported={").append(supportedULocales[0].toString());
- for (int i = 1; i < supportedULocales.length; ++i) {
- s.append(", ").append(supportedULocales[i].toString());
+ // Supported languages in the order that we try to match them.
+ if (supportedLSRsLength > 0) {
+ s.append(" supportedLSRs={").append(supportedLSRs[0]);
+ for (int i = 1; i < supportedLSRsLength; ++i) {
+ s.append(", ").append(supportedLSRs[i]);
}
s.append('}');
}
- s.append(" default=").append(Objects.toString(defaultULocale));
+ s.append(" default=").append(defaultULocale);
if (favorSubtag != null) {
- s.append(" distance=").append(favorSubtag.toString());
+ s.append(" favor=").append(favorSubtag);
+ }
+ if (direction != null) {
+ s.append(" direction=").append(direction);
}
if (thresholdDistance >= 0) {
s.append(String.format(" threshold=%d", thresholdDistance));
diff --git a/android_icu4j/src/main/java/android/icu/util/MeasureUnit.java b/android_icu4j/src/main/java/android/icu/util/MeasureUnit.java
index b6bfe87b1..b2815df1f 100644
--- a/android_icu4j/src/main/java/android/icu/util/MeasureUnit.java
+++ b/android_icu4j/src/main/java/android/icu/util/MeasureUnit.java
@@ -194,6 +194,47 @@ public class MeasureUnit implements Serializable {
return MeasureUnit.addUnit(type, subType, factory);
}
+ private static MeasureUnit findBySubType(String subType) {
+ populateCache();
+ for (Map<String, MeasureUnit> unitsForType : cache.values()) {
+ if (unitsForType.containsKey(subType)) {
+ return unitsForType.get(subType);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * For ICU use only.
+ * @deprecated This API is ICU internal only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public static MeasureUnit[] parseCoreUnitIdentifier(String coreUnitIdentifier) {
+ // First search for the whole code unit identifier as a subType
+ MeasureUnit whole = findBySubType(coreUnitIdentifier);
+ if (whole != null) {
+ return new MeasureUnit[] { whole }; // found a numerator but not denominator
+ }
+
+ // If not found, try breaking apart numerator and denominator
+ int perIdx = coreUnitIdentifier.indexOf("-per-");
+ if (perIdx == -1) {
+ // String does not contain "-per-"
+ return null;
+ }
+ String numeratorStr = coreUnitIdentifier.substring(0, perIdx);
+ String denominatorStr = coreUnitIdentifier.substring(perIdx + 5);
+ MeasureUnit numerator = findBySubType(numeratorStr);
+ MeasureUnit denominator = findBySubType(denominatorStr);
+ if (numerator != null && denominator != null) {
+ return new MeasureUnit[] { numerator, denominator }; // found both a numerator and denominator
+ }
+
+ // The numerator or denominator were invalid
+ return null;
+ }
+
/**
* For ICU use only.
* @deprecated This API is ICU internal only.
@@ -372,9 +413,9 @@ public class MeasureUnit implements Serializable {
public static final MeasureUnit G_FORCE = MeasureUnit.internalGetInstance("acceleration", "g-force");
/**
- * Constant for unit of acceleration: meter-per-second-squared
+ * Constant for unit of acceleration: meter-per-square-second
*/
- public static final MeasureUnit METER_PER_SECOND_SQUARED = MeasureUnit.internalGetInstance("acceleration", "meter-per-second-squared");
+ public static final MeasureUnit METER_PER_SECOND_SQUARED = MeasureUnit.internalGetInstance("acceleration", "meter-per-square-second");
/**
* Constant for unit of angle: arc-minute
@@ -408,7 +449,7 @@ public class MeasureUnit implements Serializable {
/**
* Constant for unit of area: dunam
- * @hide draft / provisional / internal are hidden on Android
+ * @hide Hide new API in Android temporarily
*/
public static final MeasureUnit DUNAM = MeasureUnit.internalGetInstance("area", "dunam");
@@ -469,14 +510,14 @@ public class MeasureUnit implements Serializable {
/**
* Constant for unit of concentr: mole
- * @hide draft / provisional / internal are hidden on Android
+ * @hide Hide new API in Android temporarily
*/
public static final MeasureUnit MOLE = MeasureUnit.internalGetInstance("concentr", "mole");
/**
- * Constant for unit of concentr: part-per-million
+ * Constant for unit of concentr: permillion
*/
- public static final MeasureUnit PART_PER_MILLION = MeasureUnit.internalGetInstance("concentr", "part-per-million");
+ public static final MeasureUnit PART_PER_MILLION = MeasureUnit.internalGetInstance("concentr", "permillion");
/**
* Constant for unit of concentr: percent
@@ -490,14 +531,14 @@ public class MeasureUnit implements Serializable {
/**
* Constant for unit of concentr: permyriad
- * @hide draft / provisional / internal are hidden on Android
+ * @hide Hide new API in Android temporarily
*/
public static final MeasureUnit PERMYRIAD = MeasureUnit.internalGetInstance("concentr", "permyriad");
/**
- * Constant for unit of consumption: liter-per-100kilometers
+ * Constant for unit of consumption: liter-per-100-kilometer
*/
- public static final MeasureUnit LITER_PER_100KILOMETERS = MeasureUnit.internalGetInstance("consumption", "liter-per-100kilometers");
+ public static final MeasureUnit LITER_PER_100KILOMETERS = MeasureUnit.internalGetInstance("consumption", "liter-per-100-kilometer");
/**
* Constant for unit of consumption: liter-per-kilometer
@@ -581,7 +622,7 @@ public class MeasureUnit implements Serializable {
/**
* Constant for unit of duration: day-person
- * @hide draft / provisional / internal are hidden on Android
+ * @hide Hide new API in Android temporarily
*/
public static final MeasureUnit DAY_PERSON = MeasureUnit.internalGetInstance("duration", "day-person");
@@ -618,7 +659,7 @@ public class MeasureUnit implements Serializable {
/**
* Constant for unit of duration: month-person
- * @hide draft / provisional / internal are hidden on Android
+ * @hide Hide new API in Android temporarily
*/
public static final MeasureUnit MONTH_PERSON = MeasureUnit.internalGetInstance("duration", "month-person");
@@ -639,7 +680,7 @@ public class MeasureUnit implements Serializable {
/**
* Constant for unit of duration: week-person
- * @hide draft / provisional / internal are hidden on Android
+ * @hide Hide new API in Android temporarily
*/
public static final MeasureUnit WEEK_PERSON = MeasureUnit.internalGetInstance("duration", "week-person");
@@ -650,7 +691,7 @@ public class MeasureUnit implements Serializable {
/**
* Constant for unit of duration: year-person
- * @hide draft / provisional / internal are hidden on Android
+ * @hide Hide new API in Android temporarily
*/
public static final MeasureUnit YEAR_PERSON = MeasureUnit.internalGetInstance("duration", "year-person");
@@ -676,7 +717,7 @@ public class MeasureUnit implements Serializable {
/**
* Constant for unit of energy: british-thermal-unit
- * @hide draft / provisional / internal are hidden on Android
+ * @hide Hide new API in Android temporarily
*/
public static final MeasureUnit BRITISH_THERMAL_UNIT = MeasureUnit.internalGetInstance("energy", "british-thermal-unit");
@@ -687,7 +728,7 @@ public class MeasureUnit implements Serializable {
/**
* Constant for unit of energy: electronvolt
- * @hide draft / provisional / internal are hidden on Android
+ * @hide Hide new API in Android temporarily
*/
public static final MeasureUnit ELECTRONVOLT = MeasureUnit.internalGetInstance("energy", "electronvolt");
@@ -724,13 +765,13 @@ public class MeasureUnit implements Serializable {
/**
* Constant for unit of force: newton
- * @hide draft / provisional / internal are hidden on Android
+ * @hide Hide new API in Android temporarily
*/
public static final MeasureUnit NEWTON = MeasureUnit.internalGetInstance("force", "newton");
/**
* Constant for unit of force: pound-force
- * @hide draft / provisional / internal are hidden on Android
+ * @hide Hide new API in Android temporarily
*/
public static final MeasureUnit POUND_FORCE = MeasureUnit.internalGetInstance("force", "pound-force");
@@ -893,7 +934,7 @@ public class MeasureUnit implements Serializable {
/**
* Constant for unit of length: solar-radius
- * @hide draft / provisional / internal are hidden on Android
+ * @hide Hide new API in Android temporarily
*/
public static final MeasureUnit SOLAR_RADIUS = MeasureUnit.internalGetInstance("length", "solar-radius");
@@ -909,7 +950,7 @@ public class MeasureUnit implements Serializable {
/**
* Constant for unit of light: solar-luminosity
- * @hide draft / provisional / internal are hidden on Android
+ * @hide Hide new API in Android temporarily
*/
public static final MeasureUnit SOLAR_LUMINOSITY = MeasureUnit.internalGetInstance("light", "solar-luminosity");
@@ -920,13 +961,13 @@ public class MeasureUnit implements Serializable {
/**
* Constant for unit of mass: dalton
- * @hide draft / provisional / internal are hidden on Android
+ * @hide Hide new API in Android temporarily
*/
public static final MeasureUnit DALTON = MeasureUnit.internalGetInstance("mass", "dalton");
/**
* Constant for unit of mass: earth-mass
- * @hide draft / provisional / internal are hidden on Android
+ * @hide Hide new API in Android temporarily
*/
public static final MeasureUnit EARTH_MASS = MeasureUnit.internalGetInstance("mass", "earth-mass");
@@ -972,7 +1013,7 @@ public class MeasureUnit implements Serializable {
/**
* Constant for unit of mass: solar-mass
- * @hide draft / provisional / internal are hidden on Android
+ * @hide Hide new API in Android temporarily
*/
public static final MeasureUnit SOLAR_MASS = MeasureUnit.internalGetInstance("mass", "solar-mass");
@@ -1033,19 +1074,19 @@ public class MeasureUnit implements Serializable {
public static final MeasureUnit HECTOPASCAL = MeasureUnit.internalGetInstance("pressure", "hectopascal");
/**
- * Constant for unit of pressure: inch-hg
+ * Constant for unit of pressure: inch-ofhg
*/
- public static final MeasureUnit INCH_HG = MeasureUnit.internalGetInstance("pressure", "inch-hg");
+ public static final MeasureUnit INCH_HG = MeasureUnit.internalGetInstance("pressure", "inch-ofhg");
/**
* Constant for unit of pressure: kilopascal
- * @hide draft / provisional / internal are hidden on Android
+ * @hide Hide new API in Android temporarily
*/
public static final MeasureUnit KILOPASCAL = MeasureUnit.internalGetInstance("pressure", "kilopascal");
/**
* Constant for unit of pressure: megapascal
- * @hide draft / provisional / internal are hidden on Android
+ * @hide Hide new API in Android temporarily
*/
public static final MeasureUnit MEGAPASCAL = MeasureUnit.internalGetInstance("pressure", "megapascal");
@@ -1055,9 +1096,9 @@ public class MeasureUnit implements Serializable {
public static final MeasureUnit MILLIBAR = MeasureUnit.internalGetInstance("pressure", "millibar");
/**
- * Constant for unit of pressure: millimeter-of-mercury
+ * Constant for unit of pressure: millimeter-ofhg
*/
- public static final MeasureUnit MILLIMETER_OF_MERCURY = MeasureUnit.internalGetInstance("pressure", "millimeter-of-mercury");
+ public static final MeasureUnit MILLIMETER_OF_MERCURY = MeasureUnit.internalGetInstance("pressure", "millimeter-ofhg");
/**
* Constant for unit of pressure: pascal
@@ -1066,9 +1107,9 @@ public class MeasureUnit implements Serializable {
public static final MeasureUnit PASCAL = MeasureUnit.internalGetInstance("pressure", "pascal");
/**
- * Constant for unit of pressure: pound-per-square-inch
+ * Constant for unit of pressure: pound-force-per-square-inch
*/
- public static final MeasureUnit POUND_PER_SQUARE_INCH = MeasureUnit.internalGetInstance("pressure", "pound-per-square-inch");
+ public static final MeasureUnit POUND_PER_SQUARE_INCH = MeasureUnit.internalGetInstance("pressure", "pound-force-per-square-inch");
/**
* Constant for unit of speed: kilometer-per-hour
@@ -1112,15 +1153,15 @@ public class MeasureUnit implements Serializable {
/**
* Constant for unit of torque: newton-meter
- * @hide draft / provisional / internal are hidden on Android
+ * @hide Hide new API in Android temporarily
*/
public static final MeasureUnit NEWTON_METER = MeasureUnit.internalGetInstance("torque", "newton-meter");
/**
- * Constant for unit of torque: pound-foot
- * @hide draft / provisional / internal are hidden on Android
+ * Constant for unit of torque: pound-force-foot
+ * @hide Hide new API in Android temporarily
*/
- public static final MeasureUnit POUND_FOOT = MeasureUnit.internalGetInstance("torque", "pound-foot");
+ public static final MeasureUnit POUND_FOOT = MeasureUnit.internalGetInstance("torque", "pound-force-foot");
/**
* Constant for unit of volume: acre-foot
@@ -1129,7 +1170,7 @@ public class MeasureUnit implements Serializable {
/**
* Constant for unit of volume: barrel
- * @hide draft / provisional / internal are hidden on Android
+ * @hide Hide new API in Android temporarily
*/
public static final MeasureUnit BARREL = MeasureUnit.internalGetInstance("volume", "barrel");
@@ -1200,7 +1241,7 @@ public class MeasureUnit implements Serializable {
/**
* Constant for unit of volume: fluid-ounce-imperial
- * @hide draft / provisional / internal are hidden on Android
+ * @hide Hide new API in Android temporarily
*/
public static final MeasureUnit FLUID_OUNCE_IMPERIAL = MeasureUnit.internalGetInstance("volume", "fluid-ounce-imperial");
diff --git a/android_icu4j/src/main/java/android/icu/util/ULocale.java b/android_icu4j/src/main/java/android/icu/util/ULocale.java
index aef4496b3..d81f4c8f2 100644
--- a/android_icu4j/src/main/java/android/icu/util/ULocale.java
+++ b/android_icu4j/src/main/java/android/icu/util/ULocale.java
@@ -13,7 +13,6 @@ package android.icu.util;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
-import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -35,7 +34,6 @@ import android.icu.impl.ICUResourceBundle;
import android.icu.impl.ICUResourceTableAccess;
import android.icu.impl.LocaleIDParser;
import android.icu.impl.LocaleIDs;
-import android.icu.impl.LocaleUtility;
import android.icu.impl.SoftCache;
import android.icu.impl.locale.AsciiUtil;
import android.icu.impl.locale.BaseLocale;
@@ -348,23 +346,24 @@ public final class ULocale implements Serializable, Comparable<ULocale> {
* canonicalized id.
*/
private static String[][] CANONICALIZE_MAP = {
- { "art_LOJBAN", "jbo" }, /* registered name */
- { "cel_GAULISH", "cel__GAULISH" }, /* registered name */
- { "de_1901", "de__1901" }, /* registered name */
- { "de_1906", "de__1906" }, /* registered name */
- { "en_BOONT", "en__BOONT" }, /* registered name */
- { "en_SCOUSE", "en__SCOUSE" }, /* registered name */
+ { "art__LOJBAN", "jbo" }, /* registered name */
+ { "cel__GAULISH", "cel__GAULISH" }, /* registered name */
+ { "de__1901", "de__1901" }, /* registered name */
+ { "de__1906", "de__1906" }, /* registered name */
+ { "en__BOONT", "en__BOONT" }, /* registered name */
+ { "en__SCOUSE", "en__SCOUSE" }, /* registered name */
{ "hy__AREVELA", "hy", null, null }, /* Registered IANA variant */
{ "hy__AREVMDA", "hyw", null, null }, /* Registered IANA variant */
- { "sl_ROZAJ", "sl__ROZAJ" }, /* registered name */
- { "zh_GAN", "zh__GAN" }, /* registered name */
- { "zh_GUOYU", "zh" }, /* registered name */
- { "zh_HAKKA", "zh__HAKKA" }, /* registered name */
+ { "sl__ROZAJ", "sl__ROZAJ" }, /* registered name */
+ { "zh__GUOYU", "zh" }, /* registered name */
+ { "zh__HAKKA", "hak" }, /* registered name */
+ { "zh__XIANG", "hsn" }, /* registered name */
+ // Three letter subtags won't be treated as variants.
+ { "zh_GAN", "gan" }, /* registered name */
{ "zh_MIN", "zh__MIN" }, /* registered name */
- { "zh_MIN_NAN", "zh__MINNAN" }, /* registered name */
- { "zh_WUU", "zh__WUU" }, /* registered name */
- { "zh_XIANG", "zh__XIANG" }, /* registered name */
- { "zh_YUE", "zh__YUE" } /* registered name */
+ { "zh_MIN_NAN", "nan" }, /* registered name */
+ { "zh_WUU", "wuu" }, /* registered name */
+ { "zh_YUE", "yue" } /* registered name */
};
/**
@@ -376,15 +375,6 @@ public final class ULocale implements Serializable, Comparable<ULocale> {
}
/**
- * Construct a ULocale object from a {@link java.util.Locale}.
- * @param loc a {@link java.util.Locale}
- */
- private ULocale(Locale loc) {
- this.localeID = getName(forLocale(loc).toString());
- this.locale = loc;
- }
-
- /**
* <strong>[icu]</strong> Returns a ULocale object for a {@link java.util.Locale}.
* The ULocale is canonicalized.
* @param loc a {@link java.util.Locale}
@@ -452,7 +442,7 @@ public final class ULocale implements Serializable, Comparable<ULocale> {
}
/**
- * <strong>[icu]</strong> Creates a ULocale from the id by first canonicalizing the id.
+ * <strong>[icu]</strong> Creates a ULocale from the id by first canonicalizing the id according to CLDR.
* @param nonCanonicalID the locale id to canonicalize
* @return the locale created from the canonical version of the ID.
*/
@@ -460,6 +450,16 @@ public final class ULocale implements Serializable, Comparable<ULocale> {
return new ULocale(canonicalize(nonCanonicalID), (Locale)null);
}
+ /**
+ * Creates a ULocale from the locale by first canonicalizing the locale according to CLDR.
+ * @param locale the ULocale to canonicalize
+ * @return the ULocale created from the canonical version of the ULocale.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public static ULocale createCanonical(ULocale locale) {
+ return createCanonical(locale.getName());
+ }
+
private static String lscvToID(String lang, String script, String country, String variant) {
StringBuilder buf = new StringBuilder();
@@ -1124,8 +1124,8 @@ public final class ULocale implements Serializable, Comparable<ULocale> {
}
/**
- * <strong>[icu]</strong> Returns the canonical name for the specified locale ID. This is used to
- * convert POSIX and other grandfathered IDs to standard ICU form.
+ * <strong>[icu]</strong> Returns the canonical name according to CLDR for the specified locale ID.
+ * This is used to convert POSIX and other grandfathered IDs to standard ICU form.
* @param localeID the locale id
* @return the canonicalized id
*/
@@ -1158,6 +1158,144 @@ public final class ULocale implements Serializable, Comparable<ULocale> {
}
}
+ // If the BCP 47 primary language subtag matches the type attribute of a languageAlias
+ // element in Supplemental Data, replace the language subtag with the replacement value.
+ // If there are additional subtags in the replacement value, add them to the result, but
+ // only if there is no corresponding subtag already in the tag.
+ // Five special deprecated grandfathered codes (such as i-default) are in type attributes, and are also replaced.
+ try {
+ UResourceBundle languageAlias = UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME,
+ "metadata", ICUResourceBundle.ICU_DATA_CLASS_LOADER)
+ .get("alias")
+ .get("language");
+ // language _ variant
+ if (!parser.getVariant().isEmpty()) {
+ String [] variants = parser.getVariant().split("_");
+ for (String variant : variants) {
+ try {
+ // Note the key in the metadata.txt is formatted as language_variant
+ // instead of language__variant but lscvToID will generate
+ // language__variant so we have to build the string ourselves.
+ ULocale replaceLocale = new ULocale(languageAlias.get(
+ (new StringBuilder(parser.getLanguage().length() + 1 + parser.getVariant().length()))
+ .append(parser.getLanguage())
+ .append("_")
+ .append(variant)
+ .toString())
+ .get("replacement")
+ .getString());
+ StringBuilder replacedVariant = new StringBuilder(parser.getVariant().length());
+ for (String current : variants) {
+ if (current.equals(variant)) continue;
+ if (replacedVariant.length() > 0) replacedVariant.append("_");
+ replacedVariant.append(current);
+ }
+ parser = new LocaleIDParser(
+ (new StringBuilder(localeID.length()))
+ .append(lscvToID(replaceLocale.getLanguage(),
+ !parser.getScript().isEmpty() ? parser.getScript() : replaceLocale.getScript(),
+ !parser.getCountry().isEmpty() ? parser.getCountry() : replaceLocale.getCountry(),
+ replacedVariant.toString()))
+ .append(parser.getName().substring(parser.getBaseName().length()))
+ .toString());
+ } catch (MissingResourceException e) {
+ }
+ }
+ }
+
+ // language _ script _ country
+ // ug_Arab_CN -> ug_CN
+ if (!parser.getScript().isEmpty() && !parser.getCountry().isEmpty()) {
+ try {
+ ULocale replaceLocale = new ULocale(languageAlias.get(
+ lscvToID(parser.getLanguage(), parser.getScript(), parser.getCountry(), null))
+ .get("replacement")
+ .getString());
+ parser = new LocaleIDParser((new StringBuilder(localeID.length()))
+ .append(lscvToID(replaceLocale.getLanguage(),
+ replaceLocale.getScript(),
+ replaceLocale.getCountry(),
+ parser.getVariant()))
+ .append(parser.getName().substring(parser.getBaseName().length()))
+ .toString());
+ } catch (MissingResourceException e) {
+ }
+ }
+ // language _ country
+ // eg. az_AZ -> az_Latn_AZ
+ if (!parser.getCountry().isEmpty()) {
+ try {
+ ULocale replaceLocale = new ULocale(languageAlias.get(
+ lscvToID(parser.getLanguage(), null, parser.getCountry(), null))
+ .get("replacement")
+ .getString());
+ parser = new LocaleIDParser((new StringBuilder(localeID.length()))
+ .append(lscvToID(replaceLocale.getLanguage(),
+ parser.getScript().isEmpty() ? replaceLocale.getScript() : parser.getScript(),
+ replaceLocale.getCountry(),
+ parser.getVariant()))
+ .append(parser.getName().substring(parser.getBaseName().length()))
+ .toString());
+ } catch (MissingResourceException e) {
+ }
+ }
+ // only language
+ // e.g. twi -> ak
+ try {
+ ULocale replaceLocale = new ULocale(languageAlias.get(parser.getLanguage())
+ .get("replacement")
+ .getString());
+ parser = new LocaleIDParser((new StringBuilder(localeID.length()))
+ .append(lscvToID(replaceLocale.getLanguage(),
+ parser.getScript().isEmpty() ? replaceLocale.getScript() : parser.getScript() ,
+ parser.getCountry().isEmpty() ? replaceLocale.getCountry() : parser.getCountry() ,
+ parser.getVariant()))
+ .append(parser.getName().substring(parser.getBaseName().length()))
+ .toString());
+ } catch (MissingResourceException e) {
+ }
+ } catch (MissingResourceException e) {
+ }
+
+ // If the BCP 47 region subtag matches the type attribute of a
+ // territoryAlias element in Supplemental Data, replace the language
+ // subtag with the replacement value, as follows:
+ if (!parser.getCountry().isEmpty()) {
+ try {
+ String replacements[] = UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME,
+ "metadata", ICUResourceBundle.ICU_DATA_CLASS_LOADER)
+ .get("alias")
+ .get("territory")
+ .get(parser.getCountry())
+ .get("replacement")
+ .getString()
+ .split(" ");
+ String replacement = replacements[0];
+ // If there is a single territory in the replacement, use it.
+ // If there are multiple territories:
+ // Look up the most likely territory for the base language code (and script, if there is one).
+ // If that likely territory is in the list, use it.
+ // Otherwise, use the first territory in the list.
+ if (replacements.length > 1) {
+ String likelyCountry = ULocale.addLikelySubtags(
+ new ULocale(lscvToID(parser.getLanguage(), parser.getScript(), null, parser.getVariant())))
+ .getCountry();
+ for (String country : replacements) {
+ if (country.equals(likelyCountry)) {
+ replacement = likelyCountry;
+ break;
+ }
+ }
+ }
+ parser = new LocaleIDParser(
+ (new StringBuilder(localeID.length()))
+ .append(lscvToID(parser.getLanguage(), parser.getScript(), replacement, parser.getVariant()))
+ .append(parser.getName().substring(parser.getBaseName().length()))
+ .toString());
+ } catch (MissingResourceException e) {
+ }
+ }
+
return parser.getName();
}
@@ -1859,27 +1997,41 @@ public final class ULocale implements Serializable, Comparable<ULocale> {
* ROOT ULocale if if a ROOT locale was used as a fallback (because nothing else in
* availableLocales matched). No ULocale array element should be null; behavior is
* undefined if this is the case.
+ *
+ * <p>This is a thin wrapper over {@link LocalePriorityList} + {@link LocaleMatcher}.
+ *
* @param acceptLanguageList list in HTTP "Accept-Language:" format of acceptable locales
* @param availableLocales list of available locales. One of these will be returned.
* @param fallback if non-null, a 1-element array containing a boolean to be set with
* the fallback status
* @return one of the locales from the availableLocales list, or null if none match
+ * @see LocaleMatcher
+ * @see LocalePriorityList
*/
public static ULocale acceptLanguage(String acceptLanguageList, ULocale[] availableLocales,
boolean[] fallback) {
- if (acceptLanguageList == null) {
- throw new NullPointerException();
+ if (fallback != null) {
+ fallback[0] = true;
}
- ULocale acceptList[] = null;
+ LocalePriorityList desired;
try {
- acceptList = parseAcceptLanguage(acceptLanguageList, true);
- } catch (ParseException pe) {
- acceptList = null;
- }
- if (acceptList == null) {
+ desired = LocalePriorityList.add(acceptLanguageList).build();
+ } catch (IllegalArgumentException e) {
return null;
}
- return acceptLanguage(acceptList, availableLocales, fallback);
+ LocaleMatcher.Builder builder = LocaleMatcher.builder();
+ for (ULocale locale : availableLocales) {
+ builder.addSupportedULocale(locale);
+ }
+ LocaleMatcher matcher = builder.build();
+ LocaleMatcher.Result result = matcher.getBestMatchResult(desired);
+ if (result.getDesiredIndex() >= 0) {
+ if (fallback != null && result.getDesiredULocale().equals(result.getSupportedULocale())) {
+ fallback[0] = false;
+ }
+ return result.getSupportedULocale();
+ }
+ return null;
}
/**
@@ -1890,56 +2042,38 @@ public final class ULocale implements Serializable, Comparable<ULocale> {
* will be one of the locales in availableLocales, or the ROOT ULocale if if a ROOT
* locale was used as a fallback (because nothing else in availableLocales matched).
* No ULocale array element should be null; behavior is undefined if this is the case.
+ *
+ * <p>This is a thin wrapper over {@link LocaleMatcher}.
+ *
* @param acceptLanguageList list of acceptable locales
* @param availableLocales list of available locales. One of these will be returned.
* @param fallback if non-null, a 1-element array containing a boolean to be set with
* the fallback status
* @return one of the locales from the availableLocales list, or null if none match
+ * @see LocaleMatcher
*/
- public static ULocale acceptLanguage(ULocale[] acceptLanguageList, ULocale[]
- availableLocales, boolean[] fallback) {
- // fallbacklist
- int i,j;
- if(fallback != null) {
- fallback[0]=true;
+ public static ULocale acceptLanguage(ULocale[] acceptLanguageList, ULocale[] availableLocales,
+ boolean[] fallback) {
+ if (fallback != null) {
+ fallback[0] = true;
}
- for(i=0;i<acceptLanguageList.length;i++) {
- ULocale aLocale = acceptLanguageList[i];
- boolean[] setFallback = fallback;
- do {
- for(j=0;j<availableLocales.length;j++) {
- if(availableLocales[j].equals(aLocale)) {
- if(setFallback != null) {
- setFallback[0]=false; // first time with this locale - not a fallback.
- }
- return availableLocales[j];
- }
- // compare to scriptless alias, so locales such as
- // zh_TW, zh_CN are considered as available locales - see #7190
- if (aLocale.getScript().length() == 0
- && availableLocales[j].getScript().length() > 0
- && availableLocales[j].getLanguage().equals(aLocale.getLanguage())
- && availableLocales[j].getCountry().equals(aLocale.getCountry())
- && availableLocales[j].getVariant().equals(aLocale.getVariant())) {
- ULocale minAvail = ULocale.minimizeSubtags(availableLocales[j]);
- if (minAvail.getScript().length() == 0) {
- if(setFallback != null) {
- setFallback[0] = false; // not a fallback.
- }
- return aLocale;
- }
- }
- }
- Locale loc = aLocale.toLocale();
- Locale parent = LocaleUtility.fallback(loc);
- if(parent != null) {
- aLocale = new ULocale(parent);
- } else {
- aLocale = null;
- }
- setFallback = null; // Do not set fallback in later iterations
- } while (aLocale != null);
+ LocaleMatcher.Builder builder = LocaleMatcher.builder();
+ for (ULocale locale : availableLocales) {
+ builder.addSupportedULocale(locale);
+ }
+ LocaleMatcher matcher = builder.build();
+ LocaleMatcher.Result result;
+ if (acceptLanguageList.length == 1) {
+ result = matcher.getBestMatchResult(acceptLanguageList[0]);
+ } else {
+ result = matcher.getBestMatchResult(Arrays.asList(acceptLanguageList));
+ }
+ if (result.getDesiredIndex() >= 0) {
+ if (fallback != null && result.getDesiredULocale().equals(result.getSupportedULocale())) {
+ fallback[0] = false;
+ }
+ return result.getSupportedULocale();
}
return null;
}
@@ -1954,11 +2088,16 @@ public final class ULocale implements Serializable, Comparable<ULocale> {
* availableLocales matched). No ULocale array element should be null; behavior is
* undefined if this is the case. This function will choose a locale from the
* ULocale.getAvailableLocales() list as available.
+ *
+ * <p>This is a thin wrapper over {@link LocalePriorityList} + {@link LocaleMatcher}.
+ *
* @param acceptLanguageList list in HTTP "Accept-Language:" format of acceptable locales
* @param fallback if non-null, a 1-element array containing a boolean to be set with
* the fallback status
* @return one of the locales from the ULocale.getAvailableLocales() list, or null if
* none match
+ * @see LocaleMatcher
+ * @see LocalePriorityList
*/
public static ULocale acceptLanguage(String acceptLanguageList, boolean[] fallback) {
return acceptLanguage(acceptLanguageList, ULocale.getAvailableLocales(),
@@ -1975,274 +2114,20 @@ public final class ULocale implements Serializable, Comparable<ULocale> {
* availableLocales matched). No ULocale array element should be null; behavior is
* undefined if this is the case. This function will choose a locale from the
* ULocale.getAvailableLocales() list as available.
+ *
+ * <p>This is a thin wrapper over {@link LocaleMatcher}.
+ *
* @param acceptLanguageList ordered array of acceptable locales (preferred are listed first)
* @param fallback if non-null, a 1-element array containing a boolean to be set with
* the fallback status
* @return one of the locales from the ULocale.getAvailableLocales() list, or null if none match
+ * @see LocaleMatcher
*/
public static ULocale acceptLanguage(ULocale[] acceptLanguageList, boolean[] fallback) {
return acceptLanguage(acceptLanguageList, ULocale.getAvailableLocales(),
fallback);
}
- /**
- * Package local method used for parsing Accept-Language string
- */
- static ULocale[] parseAcceptLanguage(String acceptLanguage, boolean isLenient)
- throws ParseException {
- class ULocaleAcceptLanguageQ implements Comparable<ULocaleAcceptLanguageQ> {
- private double q;
- private double serial;
- public ULocaleAcceptLanguageQ(double theq, int theserial) {
- q = theq;
- serial = theserial;
- }
- @Override
- public int compareTo(ULocaleAcceptLanguageQ other) {
- if (q > other.q) { // reverse - to sort in descending order
- return -1;
- } else if (q < other.q) {
- return 1;
- }
- if (serial < other.serial) {
- return -1;
- } else if (serial > other.serial) {
- return 1;
- } else {
- return 0; // same object
- }
- }
- }
-
- // parse out the acceptLanguage into an array
- TreeMap<ULocaleAcceptLanguageQ, ULocale> map =
- new TreeMap<>();
- StringBuilder languageRangeBuf = new StringBuilder();
- StringBuilder qvalBuf = new StringBuilder();
- int state = 0;
- acceptLanguage += ","; // append comma to simplify the parsing code
- int n;
- boolean subTag = false;
- boolean q1 = false;
- for (n = 0; n < acceptLanguage.length(); n++) {
- boolean gotLanguageQ = false;
- char c = acceptLanguage.charAt(n);
- switch (state) {
- case 0: // before language-range start
- if (('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z')) {
- // in language-range
- languageRangeBuf.append(c);
- state = 1;
- subTag = false;
- } else if (c == '*') {
- languageRangeBuf.append(c);
- state = 2;
- } else if (c != ' ' && c != '\t') {
- // invalid character
- state = -1;
- }
- break;
- case 1: // in language-range
- if (('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z')) {
- languageRangeBuf.append(c);
- } else if (c == '-') {
- subTag = true;
- languageRangeBuf.append(c);
- } else if (c == '_') {
- if (isLenient) {
- subTag = true;
- languageRangeBuf.append(c);
- } else {
- state = -1;
- }
- } else if ('0' <= c && c <= '9') {
- if (subTag) {
- languageRangeBuf.append(c);
- } else {
- // DIGIT is allowed only in language sub tag
- state = -1;
- }
- } else if (c == ',') {
- // language-q end
- gotLanguageQ = true;
- } else if (c == ' ' || c == '\t') {
- // language-range end
- state = 3;
- } else if (c == ';') {
- // before q
- state = 4;
- } else {
- // invalid character for language-range
- state = -1;
- }
- break;
- case 2: // saw wild card range
- if (c == ',') {
- // language-q end
- gotLanguageQ = true;
- } else if (c == ' ' || c == '\t') {
- // language-range end
- state = 3;
- } else if (c == ';') {
- // before q
- state = 4;
- } else {
- // invalid
- state = -1;
- }
- break;
- case 3: // language-range end
- if (c == ',') {
- // language-q end
- gotLanguageQ = true;
- } else if (c == ';') {
- // before q
- state =4;
- } else if (c != ' ' && c != '\t') {
- // invalid
- state = -1;
- }
- break;
- case 4: // before q
- if (c == 'q') {
- // before equal
- state = 5;
- } else if (c != ' ' && c != '\t') {
- // invalid
- state = -1;
- }
- break;
- case 5: // before equal
- if (c == '=') {
- // before q value
- state = 6;
- } else if (c != ' ' && c != '\t') {
- // invalid
- state = -1;
- }
- break;
- case 6: // before q value
- if (c == '0') {
- // q value start with 0
- q1 = false;
- qvalBuf.append(c);
- state = 7;
- } else if (c == '1') {
- // q value start with 1
- qvalBuf.append(c);
- state = 7;
- } else if (c == '.') {
- if (isLenient) {
- qvalBuf.append(c);
- state = 8;
- } else {
- state = -1;
- }
- } else if (c != ' ' && c != '\t') {
- // invalid
- state = -1;
- }
- break;
- case 7: // q value start
- if (c == '.') {
- // before q value fraction part
- qvalBuf.append(c);
- state = 8;
- } else if (c == ',') {
- // language-q end
- gotLanguageQ = true;
- } else if (c == ' ' || c == '\t') {
- // after q value
- state = 10;
- } else {
- // invalid
- state = -1;
- }
- break;
- case 8: // before q value fraction part
- if ('0' <= c && c <= '9') {
- if (q1 && c != '0' && !isLenient) {
- // if q value starts with 1, the fraction part must be 0
- state = -1;
- } else {
- // in q value fraction part
- qvalBuf.append(c);
- state = 9;
- }
- } else {
- // invalid
- state = -1;
- }
- break;
- case 9: // in q value fraction part
- if ('0' <= c && c <= '9') {
- if (q1 && c != '0') {
- // if q value starts with 1, the fraction part must be 0
- state = -1;
- } else {
- qvalBuf.append(c);
- }
- } else if (c == ',') {
- // language-q end
- gotLanguageQ = true;
- } else if (c == ' ' || c == '\t') {
- // after q value
- state = 10;
- } else {
- // invalid
- state = -1;
- }
- break;
- case 10: // after q value
- if (c == ',') {
- // language-q end
- gotLanguageQ = true;
- } else if (c != ' ' && c != '\t') {
- // invalid
- state = -1;
- }
- break;
- }
- if (state == -1) {
- // error state
- throw new ParseException("Invalid Accept-Language", n);
- }
- if (gotLanguageQ) {
- double q = 1.0;
- if (qvalBuf.length() != 0) {
- try {
- q = Double.parseDouble(qvalBuf.toString());
- } catch (NumberFormatException nfe) {
- // Already validated, so it should never happen
- q = 1.0;
- }
- if (q > 1.0) {
- q = 1.0;
- }
- }
- if (languageRangeBuf.charAt(0) != '*') {
- int serial = map.size();
- ULocaleAcceptLanguageQ entry = new ULocaleAcceptLanguageQ(q, serial);
- // sort in reverse order.. 1.0, 0.9, 0.8 .. etc
- map.put(entry, new ULocale(canonicalize(languageRangeBuf.toString())));
- }
-
- // reset buffer and parse state
- languageRangeBuf.setLength(0);
- qvalBuf.setLength(0);
- state = 0;
- }
- }
- if (state != 0) {
- // Well, the parser should handle all cases. So just in case.
- throw new ParseException("Invalid AcceptlLanguage", n);
- }
-
- // pull out the map
- ULocale acceptList[] = map.values().toArray(new ULocale[map.size()]);
- return acceptList;
- }
-
private static final String UNDEFINED_LANGUAGE = "und";
private static final String UNDEFINED_SCRIPT = "Zzzz";
private static final String UNDEFINED_REGION = "ZZ";
@@ -3107,7 +2992,10 @@ public final class ULocale implements Serializable, Comparable<ULocale> {
}
List<String>subtags = tag.getVariants();
- for (String s : subtags) {
+ // ICU-20478: Sort variants per UTS35.
+ ArrayList<String> variants = new ArrayList<>(subtags);
+ Collections.sort(variants);
+ for (String s : variants) {
buf.append(LanguageTag.SEP);
buf.append(LanguageTag.canonicalizeVariant(s));
}
diff --git a/android_icu4j/src/main/java/android/icu/util/VersionInfo.java b/android_icu4j/src/main/java/android/icu/util/VersionInfo.java
index 02fb446f0..730187048 100644
--- a/android_icu4j/src/main/java/android/icu/util/VersionInfo.java
+++ b/android_icu4j/src/main/java/android/icu/util/VersionInfo.java
@@ -180,7 +180,7 @@ public final class VersionInfo implements Comparable<VersionInfo>
* @hide draft / provisional / internal are hidden on Android
*/
@Deprecated
- public static final String ICU_DATA_VERSION_PATH = "66b";
+ public static final String ICU_DATA_VERSION_PATH = "67b";
/**
* Data version in ICU4J.
@@ -551,7 +551,7 @@ public final class VersionInfo implements Comparable<VersionInfo>
UNICODE_12_1 = getInstance(12, 1, 0, 0);
UNICODE_13_0 = getInstance(13, 0, 0, 0);
- ICU_VERSION = getInstance(66, 1, 0, 0);
+ ICU_VERSION = getInstance(67, 1, 0, 0);
ICU_DATA_VERSION = ICU_VERSION;
UNICODE_VERSION = UNICODE_13_0;
diff --git a/android_icu4j/src/main/tests/android/icu/dev/data/collationtest.txt b/android_icu4j/src/main/tests/android/icu/dev/data/collationtest.txt
index 366362db2..abda337e5 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/data/collationtest.txt
+++ b/android_icu4j/src/main/tests/android/icu/dev/data/collationtest.txt
@@ -1,7 +1,5 @@
# Copyright (C) 2016 and later: Unicode, Inc. and others.
-# License & terms of use: http://www.unicode.org/copyright.html#License
-#
-# Corporation and others. All Rights Reserved.
+# License & terms of use: http://www.unicode.org/copyright.html
# Copyright (c) 2012-2015 International Business Machines
# Corporation and others. All Rights Reserved.
#
@@ -2542,3 +2540,46 @@
# Before ICU 55, the following reordered together with Gothic.
<1 𐌈 # Old Italic
<1 𐑐 # Shavian
+
+# Check for presence of certain chars 乛冂刂卜又小彑艹日月爫牛辶 in
+# zh pinyin and stroke, ICU-13790
+# (bracket pinyin test with 卬..作, stroke test with 一..乾)
+
+** test: DataDrivenCollationTest/VerifyCertainCharsInPinyin
+@ locale zh-u-co-pinyin
+* compare
+< 卬
+< 卜
+< 艹
+< 辶
+< 刂
+< 彑
+< 冂
+< 牛
+< 日
+< 小
+< 乛
+< 又
+< 月
+< 爫
+< 作
+
+** test: DataDrivenCollationTest/VerifyCertainCharsInStroke
+@ locale zh-u-co-stroke
+* compare
+< 一
+< 乛
+< 冂
+< 刂
+< 卜
+< 又
+< 小
+< 彑
+< 艹
+< 日
+< 月
+< 爫
+< 牛
+< 辶
+< 乾
+
diff --git a/android_icu4j/src/main/tests/android/icu/dev/data/numberpermutationtest.txt b/android_icu4j/src/main/tests/android/icu/dev/data/numberpermutationtest.txt
index 25f7da8b7..24136ad10 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/data/numberpermutationtest.txt
+++ b/android_icu4j/src/main/tests/android/icu/dev/data/numberpermutationtest.txt
@@ -1,4 +1,4 @@
-# © 2017 and later: Unicode, Inc. and others.
+# © 2019 and later: Unicode, Inc. and others.
# License & terms of use: http://www.unicode.org/copyright.html
compact-short percent unit-width-narrow
@@ -951,7 +951,7 @@ compact-short currency/EUR sign-accounting-except-zero
bn-BD
০€
+৯২ হা€
- (০.২২ €)
+ (০.২২€)
compact-short measure-unit/length-furlong sign-accounting-except-zero
es-MX
@@ -993,7 +993,7 @@ scientific/+ee/sign-always currency/EUR sign-accounting-except-zero
bn-BD
০.০০E+০০€
+৯.১৮E+০৪€
- (২.২২E-০১ €)
+ (২.২২E-০১€)
scientific/+ee/sign-always measure-unit/length-furlong sign-accounting-except-zero
es-MX
@@ -2273,15 +2273,15 @@ compact-short precision-integer sign-accounting-except-zero
es-MX
0
+92 k
- -0
+ 0
zh-TW
0
+9萬
- -0
+ 0
bn-BD
+৯২ হা
- -০
+ ০
compact-short .000 sign-accounting-except-zero
es-MX
@@ -3877,7 +3877,7 @@ currency/EUR unit-width-narrow sign-accounting-except-zero
bn-BD
০.০০€
+৯১,৮২৭.৩৬€
- (০.২২ €)
+ (০.২২€)
currency/EUR unit-width-full-name sign-accounting-except-zero
es-MX
@@ -4849,15 +4849,15 @@ percent precision-integer sign-accounting-except-zero
es-MX
0 %
+91,827 %
- -0 %
+ 0 %
zh-TW
0%
+91,827%
- -0%
+ 0%
bn-BD
০%
+৯১,৮২৭%
- -০%
+ ০%
percent .000 sign-accounting-except-zero
es-MX
@@ -4905,15 +4905,15 @@ currency/EUR precision-integer sign-accounting-except-zero
es-MX
EUR 0
+EUR 91,827
- -EUR 0
+ EUR 0
zh-TW
€0
+€91,827
- (€0)
+ €0
bn-BD
০€
+৯১,৮২৭€
- (০ €)
+ ০€
currency/EUR .000 sign-accounting-except-zero
es-MX
@@ -4927,7 +4927,7 @@ currency/EUR .000 sign-accounting-except-zero
bn-BD
০.০০০€
+৯১,৮২৭.৩৬৪€
- (০.২২২ €)
+ (০.২২২€)
currency/EUR .##/@@@+ sign-accounting-except-zero
es-MX
@@ -4941,7 +4941,7 @@ currency/EUR .##/@@@+ sign-accounting-except-zero
bn-BD
০€
+৯১,৮২৭.৩৬€
- (০.২২২ €)
+ (০.২২২€)
currency/EUR @@ sign-accounting-except-zero
es-MX
@@ -4955,21 +4955,21 @@ currency/EUR @@ sign-accounting-except-zero
bn-BD
০.০€
+৯২,০০০€
- (০.২২ €)
+ (০.২২€)
measure-unit/length-furlong precision-integer sign-accounting-except-zero
es-MX
0 fur
+91,827 fur
- -0 fur
+ 0 fur
zh-TW
0 化朗
+91,827 化朗
- -0 化朗
+ 0 化朗
bn-BD
০ ফার্লং
+৯১,৮২৭ ফার্লং
- -০ ফার্লং
+ ০ ফার্লং
measure-unit/length-furlong .000 sign-accounting-except-zero
es-MX
@@ -5375,7 +5375,7 @@ currency/EUR rounding-mode-floor sign-accounting-except-zero
bn-BD
০.০০€
+৯১,৮২৭.৩৬€
- (০.২৩ €)
+ (০.২৩€)
measure-unit/length-furlong rounding-mode-floor sign-accounting-except-zero
es-MX
@@ -5585,7 +5585,7 @@ currency/EUR integer-width/##00 sign-accounting-except-zero
bn-BD
০০.০০€
+১,৮২৭.৩৬€
- (০০.২২ €)
+ (০০.২২€)
measure-unit/length-furlong integer-width/##00 sign-accounting-except-zero
es-MX
@@ -5753,7 +5753,7 @@ currency/EUR scale/0.5 sign-accounting-except-zero
bn-BD
০.০০€
+৪৫,৯১৩.৬৮€
- (০.১১ €)
+ (০.১১€)
measure-unit/length-furlong scale/0.5 sign-accounting-except-zero
es-MX
@@ -5879,7 +5879,7 @@ currency/EUR group-on-aligned sign-accounting-except-zero
bn-BD
০.০০€
+৯১,৮২৭.৩৬€
- (০.২২ €)
+ (০.২২€)
measure-unit/length-furlong group-on-aligned sign-accounting-except-zero
es-MX
@@ -5963,7 +5963,7 @@ currency/EUR latin sign-accounting-except-zero
bn-BD
0.00€
+91,827.36€
- (0.22 €)
+ (0.22€)
measure-unit/length-furlong latin sign-accounting-except-zero
es-MX
@@ -6047,7 +6047,7 @@ currency/EUR sign-accounting-except-zero decimal-always
bn-BD
০.০০€
+৯১,৮২৭.৩৬€
- (০.২২ €)
+ (০.২২€)
measure-unit/length-furlong sign-accounting-except-zero decimal-always
es-MX
@@ -6627,15 +6627,15 @@ unit-width-narrow precision-integer sign-accounting-except-zero
es-MX
0
+91,827
- -0
+ 0
zh-TW
0
+91,827
- -0
+ 0
bn-BD
+৯১,৮২৭
- -০
+ ০
unit-width-narrow .000 sign-accounting-except-zero
es-MX
@@ -6683,15 +6683,15 @@ unit-width-full-name precision-integer sign-accounting-except-zero
es-MX
0
+91,827
- -0
+ 0
zh-TW
0
+91,827
- -0
+ 0
bn-BD
+৯১,৮২৭
- -০
+ ০
unit-width-full-name .000 sign-accounting-except-zero
es-MX
@@ -7943,15 +7943,15 @@ precision-integer integer-width/##00 sign-accounting-except-zero
es-MX
00
+1827
- -00
+ 00
zh-TW
00
+1,827
- -00
+ 00
bn-BD
০০
+১,৮২৭
- -০০
+ ০০
.000 integer-width/##00 sign-accounting-except-zero
es-MX
@@ -8167,15 +8167,15 @@ precision-integer scale/0.5 sign-accounting-except-zero
es-MX
0
+45,914
- -0
+ 0
zh-TW
0
+45,914
- -0
+ 0
bn-BD
+৪৫,৯১৪
- -০
+ ০
.000 scale/0.5 sign-accounting-except-zero
es-MX
@@ -8335,15 +8335,15 @@ precision-integer group-on-aligned sign-accounting-except-zero
es-MX
0
+91,827
- -0
+ 0
zh-TW
0
+91,827
- -0
+ 0
bn-BD
+৯১,৮২৭
- -০
+ ০
.000 group-on-aligned sign-accounting-except-zero
es-MX
@@ -8447,15 +8447,15 @@ precision-integer latin sign-accounting-except-zero
es-MX
0
+91,827
- -0
+ 0
zh-TW
0
+91,827
- -0
+ 0
bn-BD
0
+91,827
- -0
+ 0
.000 latin sign-accounting-except-zero
es-MX
@@ -8559,15 +8559,15 @@ precision-integer sign-accounting-except-zero decimal-always
es-MX
0.
+91,827.
- -0.
+ 0.
zh-TW
0.
+91,827.
- -0.
+ 0.
bn-BD
০.
+৯১,৮২৭.
- -০.
+ ০.
.000 sign-accounting-except-zero decimal-always
es-MX
diff --git a/android_icu4j/src/main/tests/android/icu/dev/data/testdata/ibm9027.cnv b/android_icu4j/src/main/tests/android/icu/dev/data/testdata/ibm9027.cnv
index 7c0659e85..8a53bfeb1 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/data/testdata/ibm9027.cnv
+++ b/android_icu4j/src/main/tests/android/icu/dev/data/testdata/ibm9027.cnv
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/data/testdata/root.res b/android_icu4j/src/main/tests/android/icu/dev/data/testdata/root.res
index 4497c2d11..3d30e163c 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/data/testdata/root.res
+++ b/android_icu4j/src/main/tests/android/icu/dev/data/testdata/root.res
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/data/testdata/structLocale.res b/android_icu4j/src/main/tests/android/icu/dev/data/testdata/structLocale.res
index b41279f89..086ded758 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/data/testdata/structLocale.res
+++ b/android_icu4j/src/main/tests/android/icu/dev/data/testdata/structLocale.res
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/data/testdata/test1.cnv b/android_icu4j/src/main/tests/android/icu/dev/data/testdata/test1.cnv
index 3c6a7d0d6..499ee2ea4 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/data/testdata/test1.cnv
+++ b/android_icu4j/src/main/tests/android/icu/dev/data/testdata/test1.cnv
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/data/testdata/test1bmp.cnv b/android_icu4j/src/main/tests/android/icu/dev/data/testdata/test1bmp.cnv
index b4be8904b..b81ee1068 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/data/testdata/test1bmp.cnv
+++ b/android_icu4j/src/main/tests/android/icu/dev/data/testdata/test1bmp.cnv
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/data/testdata/test2.cnv b/android_icu4j/src/main/tests/android/icu/dev/data/testdata/test2.cnv
index 91c0eca00..f853304af 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/data/testdata/test2.cnv
+++ b/android_icu4j/src/main/tests/android/icu/dev/data/testdata/test2.cnv
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/data/testdata/test3.cnv b/android_icu4j/src/main/tests/android/icu/dev/data/testdata/test3.cnv
index 1372e9d76..327529aca 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/data/testdata/test3.cnv
+++ b/android_icu4j/src/main/tests/android/icu/dev/data/testdata/test3.cnv
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/data/testdata/test4.cnv b/android_icu4j/src/main/tests/android/icu/dev/data/testdata/test4.cnv
index 1f430c428..557221cdc 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/data/testdata/test4.cnv
+++ b/android_icu4j/src/main/tests/android/icu/dev/data/testdata/test4.cnv
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/data/testdata/test4x.cnv b/android_icu4j/src/main/tests/android/icu/dev/data/testdata/test4x.cnv
index b14218f8f..cab1387bc 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/data/testdata/test4x.cnv
+++ b/android_icu4j/src/main/tests/android/icu/dev/data/testdata/test4x.cnv
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/data/testdata/test5.cnv b/android_icu4j/src/main/tests/android/icu/dev/data/testdata/test5.cnv
index c67620995..758e3bd2b 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/data/testdata/test5.cnv
+++ b/android_icu4j/src/main/tests/android/icu/dev/data/testdata/test5.cnv
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/impl/number/DecimalQuantity_64BitBCD.java b/android_icu4j/src/main/tests/android/icu/dev/impl/number/DecimalQuantity_64BitBCD.java
index c4cd54775..4d35af871 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/impl/number/DecimalQuantity_64BitBCD.java
+++ b/android_icu4j/src/main/tests/android/icu/dev/impl/number/DecimalQuantity_64BitBCD.java
@@ -98,6 +98,7 @@ public final class DecimalQuantity_64BitBCD extends DecimalQuantity_AbstractBCD
isApproximate = false;
origDouble = 0;
origDelta = 0;
+ exponent = 0;
}
@Override
diff --git a/android_icu4j/src/main/tests/android/icu/dev/impl/number/DecimalQuantity_ByteArrayBCD.java b/android_icu4j/src/main/tests/android/icu/dev/impl/number/DecimalQuantity_ByteArrayBCD.java
index 516e9af13..7e281cb56 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/impl/number/DecimalQuantity_ByteArrayBCD.java
+++ b/android_icu4j/src/main/tests/android/icu/dev/impl/number/DecimalQuantity_ByteArrayBCD.java
@@ -115,6 +115,7 @@ public final class DecimalQuantity_ByteArrayBCD extends DecimalQuantity_Abstract
isApproximate = false;
origDouble = 0;
origDelta = 0;
+ exponent = 0;
}
@Override
diff --git a/android_icu4j/src/main/tests/android/icu/dev/impl/number/DecimalQuantity_SimpleStorage.java b/android_icu4j/src/main/tests/android/icu/dev/impl/number/DecimalQuantity_SimpleStorage.java
index 41118b7ac..394a2b22b 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/impl/number/DecimalQuantity_SimpleStorage.java
+++ b/android_icu4j/src/main/tests/android/icu/dev/impl/number/DecimalQuantity_SimpleStorage.java
@@ -10,6 +10,7 @@ import java.text.FieldPosition;
import android.icu.impl.StandardPlural;
import android.icu.impl.number.DecimalQuantity;
+import android.icu.impl.number.Modifier.Signum;
import android.icu.text.PluralRules;
import android.icu.text.PluralRules.Operand;
import android.icu.text.UFieldPosition;
@@ -97,6 +98,8 @@ public class DecimalQuantity_SimpleStorage implements DecimalQuantity {
1000000000000000000L
};
+ private int origPrimaryScale;
+
@Override
public int maxRepresentableDigits() {
return Integer.MAX_VALUE;
@@ -112,6 +115,7 @@ public class DecimalQuantity_SimpleStorage implements DecimalQuantity {
primaryScale = 0;
primaryPrecision = computePrecision(primary);
fallback = null;
+ origPrimaryScale = primaryScale;
}
/**
@@ -191,6 +195,8 @@ public class DecimalQuantity_SimpleStorage implements DecimalQuantity {
primary = -1;
fallback = new BigDecimal(temp);
}
+
+ origPrimaryScale = primaryScale;
}
static final double LOG_2_OF_TEN = 3.32192809489;
@@ -281,6 +287,7 @@ public class DecimalQuantity_SimpleStorage implements DecimalQuantity {
primaryPrecision = _other.primaryPrecision;
fallback = _other.fallback;
flags = _other.flags;
+ origPrimaryScale = _other.origPrimaryScale;
}
@Override
@@ -520,8 +527,18 @@ public class DecimalQuantity_SimpleStorage implements DecimalQuantity {
}
@Override
- public int signum() {
- return isNegative() ? -1 : isZeroish() ? 0 : 1;
+ public Signum signum() {
+ boolean isZero = (isZeroish() && !isInfinite());
+ boolean isNeg = isNegative();
+ if (isZero && isNeg) {
+ return Signum.NEG_ZERO;
+ } else if (isZero) {
+ return Signum.POS_ZERO;
+ } else if (isNeg) {
+ return Signum.NEG;
+ } else {
+ return Signum.POS;
+ }
}
private void setNegative(boolean isNegative) {
@@ -884,9 +901,17 @@ public class DecimalQuantity_SimpleStorage implements DecimalQuantity {
if (isNegative()) {
sb.append('-');
}
- for (int m = getUpperDisplayMagnitude(); m >= getLowerDisplayMagnitude(); m--) {
- sb.append(getDigit(m));
- if (m == 0) sb.append('.');
+ int upper = getUpperDisplayMagnitude();
+ int lower = getLowerDisplayMagnitude();
+ int p = upper;
+ for (; p >= 0; p--) {
+ sb.append((char) ('0' + getDigit(p)));
+ }
+ if (lower < 0) {
+ sb.append('.');
+ }
+ for(; p >= lower; p--) {
+ sb.append((char) ('0' + getDigit(p)));
}
return sb.toString();
}
@@ -908,4 +933,14 @@ public class DecimalQuantity_SimpleStorage implements DecimalQuantity {
.setFractionDigits((int) getPluralOperand(Operand.v), (long) getPluralOperand(Operand.f));
}
}
+
+ @Override
+ public int getExponent() {
+ return origPrimaryScale;
+ }
+
+ @Override
+ public void adjustExponent(int delta) {
+ origPrimaryScale = origPrimaryScale + delta;
+ }
}
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/calendar/CalendarRegressionTest.java b/android_icu4j/src/main/tests/android/icu/dev/test/calendar/CalendarRegressionTest.java
index dce734594..7e4cdd6dd 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/calendar/CalendarRegressionTest.java
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/calendar/CalendarRegressionTest.java
@@ -2294,7 +2294,7 @@ public class CalendarRegressionTest extends android.icu.dev.test.TestFmwk {
gc.setFirstDayOfWeek(Calendar.MONDAY);
gc.setMinimalDaysInFirstWeek(4);
- // Force the calender to resolve the fields once.
+ // Force the calendar to resolve the fields once.
// The maximum week number in 2011 is 52.
gc.set(Calendar.YEAR, 2011);
gc.get(Calendar.YEAR);
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/format/DateFormatTest.java b/android_icu4j/src/main/tests/android/icu/dev/test/format/DateFormatTest.java
index 8c5c6206f..1704bcb3d 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/format/DateFormatTest.java
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/format/DateFormatTest.java
@@ -50,6 +50,7 @@ import android.icu.text.ChineseDateFormatSymbols;
import android.icu.text.DateFormat;
import android.icu.text.DateFormat.BooleanAttribute;
import android.icu.text.DateFormatSymbols;
+import android.icu.text.DateTimePatternGenerator;
import android.icu.text.DisplayContext;
import android.icu.text.NumberFormat;
import android.icu.text.SimpleDateFormat;
@@ -5434,4 +5435,82 @@ public class DateFormatTest extends TestFmwk {
dfmt.parse(inDate, pos);
assertEquals("Error index", inDate.length(), pos.getErrorIndex());
}
+
+ @Test
+ public void test20739_MillisecondsWithoutSeconds() {
+ String[][] cases = new String[][]{
+ // Ticket #20739
+ // minutes+milliseconds, seconds missing, should be repaired
+ {"SSSSm", "mm:ss.SSSS"},
+ {"mSSSS", "mm:ss.SSSS"},
+ {"SSSm", "mm:ss.SSS"},
+ {"mSSS", "mm:ss.SSS"},
+ {"SSm", "mm:ss.SS"},
+ {"mSS", "mm:ss.SS"},
+ {"Sm", "mm:ss.S"},
+ {"mS", "mm:ss.S"},
+ // only milliseconds, untouched, no repairs
+ {"S", "S"},
+ {"SS", "SS"},
+ {"SSS", "SSS"},
+ {"SSSS", "SSSS"},
+ // hour:minute+seconds+milliseconds, correct, no repairs, proper pattern
+ {"jmsSSS", "h:mm:ss.SSS a"},
+ {"jmSSS", "h:mm:ss.SSS a"},
+ // Ticket #20738
+ // seconds+milliseconds, correct, no repairs, proper pattern
+ {"sS", "s.S"},
+ {"sSS", "s.SS"},
+ {"sSSS", "s.SSS"},
+ {"sSSSS", "s.SSSS"},
+ // minutes+seconds+milliseconds, correct, no repairs, proper pattern
+ {"msS", "mm:ss.S"},
+ {"msSS", "mm:ss.SS"},
+ {"msSSS", "mm:ss.SSS"},
+ {"msSSSS", "mm:ss.SSSS"}
+ };
+
+ ULocale locale = ULocale.ENGLISH;
+ for (String[] cas : cases) {
+ DateFormat fmt = DateFormat.getInstanceForSkeleton( cas[0], locale);
+ String pattern = ((SimpleDateFormat) fmt).toPattern();
+ assertEquals("Format pattern", cas[1], pattern);
+ }
+ }
+
+ @Test
+ public void test20741_ABFields() {
+ String [] skeletons = {"EEEEEBBBBB", "EEEEEbbbbb"};
+ ULocale[] locales = ULocale.getAvailableLocales();
+ for (String skeleton : skeletons) {
+ for (int i = 0; i < locales.length; i++) {
+ ULocale locale = locales[i];
+ if (isQuick() && (i % 17 != 0)) continue;
+
+ DateTimePatternGenerator gen = DateTimePatternGenerator.getInstance(locale);
+ String pattern = gen.getBestPattern(skeleton);
+ SimpleDateFormat dateFormat = new SimpleDateFormat(pattern, locale);
+ Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("PST8PDT"));
+ calendar.setTime(new Date(0));
+
+ FieldPosition pos_c = new FieldPosition(DateFormat.Field.DAY_OF_WEEK);
+ dateFormat.format(calendar, new StringBuffer(""), pos_c);
+ assertFalse("'Day of week' field was not found", pos_c.getBeginIndex() == 0 && pos_c.getEndIndex() == 0);
+
+ if (skeleton.equals("EEEEEBBBBB")) {
+ FieldPosition pos_B = new FieldPosition(DateFormat.Field.FLEXIBLE_DAY_PERIOD);
+ dateFormat.format(calendar, new StringBuffer(""), pos_B);
+ assertFalse("'Flexible day period' field was not found", pos_B.getBeginIndex() == 0 && pos_B.getEndIndex() == 0);
+ } else {
+ FieldPosition pos_b = new FieldPosition(DateFormat.Field.AM_PM_MIDNIGHT_NOON);
+ dateFormat.format(calendar, new StringBuffer(""), pos_b);
+ assertFalse("'AM/PM/Midnight/Noon' field was not found", pos_b.getBeginIndex() == 0 && pos_b.getEndIndex() == 0);
+ }
+
+ FieldPosition pos_a = new FieldPosition(DateFormat.Field.AM_PM);
+ dateFormat.format(calendar, new StringBuffer(""), pos_a);
+ assertTrue("'AM/PM' field was found", pos_a.getBeginIndex() == 0 && pos_a.getEndIndex() == 0);
+ }
+ }
+ }
}
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/format/DateIntervalFormatTest.java b/android_icu4j/src/main/tests/android/icu/dev/test/format/DateIntervalFormatTest.java
index ff05e18ac..0e3737e52 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/format/DateIntervalFormatTest.java
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/format/DateIntervalFormatTest.java
@@ -1263,10 +1263,10 @@ public class DateIntervalFormatTest extends TestFmwk {
@Test
public void TestGetIntervalPattern(){
// Tests when "if ( field > MINIMUM_SUPPORTED_CALENDAR_FIELD )" is true
- // MINIMUM_SUPPORTED_CALENDAR_FIELD = Calendar.SECOND;
+ // MINIMUM_SUPPORTED_CALENDAR_FIELD = Calendar.MILLISECOND;
DateIntervalInfo dii = new DateIntervalInfo();
try{
- dii.getIntervalPattern("", Calendar.SECOND+1);
+ dii.getIntervalPattern("", Calendar.MILLISECOND+1);
errln("DateIntervalInfo.getIntervalPattern(String,int) was suppose " +
"to return an exception for the 'int field' parameter " +
"when it exceeds MINIMUM_SUPPORTED_CALENDAR_FIELD.");
@@ -1289,10 +1289,10 @@ public class DateIntervalFormatTest extends TestFmwk {
} catch(Exception e){}
// Tests when "if ( lrgDiffCalUnit > MINIMUM_SUPPORTED_CALENDAR_FIELD )" is true
- // MINIMUM_SUPPORTED_CALENDAR_FIELD = Calendar.SECOND;
+ // MINIMUM_SUPPORTED_CALENDAR_FIELD = Calendar.MILLISECOND;
try{
dii = dii.cloneAsThawed();
- dii.setIntervalPattern("", Calendar.SECOND+1, "");
+ dii.setIntervalPattern("", Calendar.MILLISECOND+1, "");
errln("DateIntervalInfo.setIntervalPattern(String,int,String) " +
"was suppose to return an exception when the " +
"variable 'lrgDiffCalUnit' is greater than " +
@@ -2060,4 +2060,100 @@ public class DateIntervalFormatTest extends TestFmwk {
}
}
}
+
+ @Test
+ public void testTicket20707() {
+ TimeZone tz = TimeZone.getTimeZone("UTC");
+ Locale locales[] = {
+ new Locale("en-u-hc-h24"),
+ new Locale("en-u-hc-h23"),
+ new Locale("en-u-hc-h12"),
+ new Locale("en-u-hc-h11"),
+ new Locale("en"),
+ new Locale("en-u-hc-h25"),
+ new Locale("hi-IN-u-hc-h11")
+ };
+
+ // Clomuns: hh, HH, kk, KK, jj, JJs, CC
+ String expected[][] = {
+ // Hour-cycle: k
+ {"12 AM", "24", "24", "12 AM", "24", "0 (hour: 24)", "12 AM"},
+ // Hour-cycle: H
+ {"12 AM", "00", "00", "12 AM", "00", "0 (hour: 00)", "12 AM"},
+ // Hour-cycle: h
+ {"12 AM", "00", "00", "12 AM", "12 AM", "0 (hour: 12)", "12 AM"},
+ // Hour-cycle: K
+ {"0 AM", "00", "00", "0 AM", "0 AM", "0 (hour: 00)", "0 AM"},
+ {"12 AM", "00", "00", "12 AM", "12 AM", "0 (hour: 12)", "12 AM"},
+ {"12 AM", "00", "00", "12 AM", "12 AM", "0 (hour: 12)", "12 AM"},
+ {"0 am", "00", "00", "0 am", "0 am", "0 (\u0918\u0902\u091F\u093E: 00)", "\u0930\u093E\u0924 0"}
+ };
+
+ int i = 0;
+ for (Locale locale : locales) {
+ int j = 0;
+ String skeletons[] = {"hh", "HH", "kk", "KK", "jj", "JJs", "CC"};
+ for (String skeleton : skeletons) {
+ DateIntervalFormat dateFormat = DateIntervalFormat.getInstance(skeleton, locale);
+ Calendar calendar = Calendar.getInstance(tz);
+ calendar.setTime(new Date(1563235200000L));
+ StringBuffer resultBuffer = dateFormat.format(calendar, calendar, new StringBuffer(""), new FieldPosition(0));
+
+ assertEquals("Formatted result for " + skeleton + " locale: " + locale.getDisplayName(), expected[i][j++], resultBuffer.toString());
+ }
+ i++;
+ }
+ }
+
+ @Test
+ public void testFormatMillisecond() {
+ Object[][] kTestCases = {
+ { new Date(2019, 2, 10, 1, 23, 45), 321, new Date(2019, 2, 10, 1, 23, 45), 321, "ms", "23:45"},
+ { new Date(2019, 2, 10, 1, 23, 45), 321, new Date(2019, 2, 10, 1, 23, 45), 321, "msS", "23:45.3"},
+ { new Date(2019, 2, 10, 1, 23, 45), 321, new Date(2019, 2, 10, 1, 23, 45), 321, "msSS", "23:45.32"},
+ { new Date(2019, 2, 10, 1, 23, 45), 321, new Date(2019, 2, 10, 1, 23, 45), 321, "msSSS", "23:45.321"},
+ { new Date(2019, 2, 10, 1, 23, 45), 321, new Date(2019, 2, 10, 1, 23, 45), 987, "ms", "23:45"},
+ { new Date(2019, 2, 10, 1, 23, 45), 321, new Date(2019, 2, 10, 1, 23, 45), 987, "msS", "23:45.3 – 23:45.9"},
+ { new Date(2019, 2, 10, 1, 23, 45), 321, new Date(2019, 2, 10, 1, 23, 45), 987, "msSS", "23:45.32 – 23:45.98"},
+ { new Date(2019, 2, 10, 1, 23, 45), 321, new Date(2019, 2, 10, 1, 23, 45), 987, "msSSS", "23:45.321 – 23:45.987"},
+ { new Date(2019, 2, 10, 1, 23, 45), 321, new Date(2019, 2, 10, 1, 23, 46), 987, "ms", "23:45 – 23:46"},
+ { new Date(2019, 2, 10, 1, 23, 45), 321, new Date(2019, 2, 10, 1, 23, 46), 987, "msS", "23:45.3 – 23:46.9"},
+ { new Date(2019, 2, 10, 1, 23, 45), 321, new Date(2019, 2, 10, 1, 23, 46), 987, "msSS", "23:45.32 – 23:46.98"},
+ { new Date(2019, 2, 10, 1, 23, 45), 321, new Date(2019, 2, 10, 1, 23, 46), 987, "msSSS", "23:45.321 – 23:46.987"},
+ { new Date(2019, 2, 10, 1, 23, 45), 321, new Date(2019, 2, 10, 2, 24, 45), 987, "ms", "23:45 – 24:45"},
+ { new Date(2019, 2, 10, 1, 23, 45), 321, new Date(2019, 2, 10, 2, 24, 45), 987, "msS", "23:45.3 – 24:45.9"},
+ { new Date(2019, 2, 10, 1, 23, 45), 321, new Date(2019, 2, 10, 2, 24, 45), 987, "msSS", "23:45.32 – 24:45.98"},
+ { new Date(2019, 2, 10, 1, 23, 45), 321, new Date(2019, 2, 10, 2, 24, 45), 987, "msSSS", "23:45.321 – 24:45.987"},
+ { new Date(2019, 2, 10, 1, 23, 45), 321, new Date(2019, 2, 10, 1, 23, 45), 321, "s", "45"},
+ { new Date(2019, 2, 10, 1, 23, 45), 321, new Date(2019, 2, 10, 1, 23, 45), 321, "sS", "45.3"},
+ { new Date(2019, 2, 10, 1, 23, 45), 321, new Date(2019, 2, 10, 1, 23, 45), 321, "sSS", "45.32"},
+ { new Date(2019, 2, 10, 1, 23, 45), 321, new Date(2019, 2, 10, 1, 23, 45), 321, "sSSS", "45.321"},
+ { new Date(2019, 2, 10, 1, 23, 45), 321, new Date(2019, 2, 10, 1, 23, 45), 987, "s", "45"},
+ { new Date(2019, 2, 10, 1, 23, 45), 321, new Date(2019, 2, 10, 1, 23, 45), 987, "sS", "45.3 – 45.9"},
+ { new Date(2019, 2, 10, 1, 23, 45), 321, new Date(2019, 2, 10, 1, 23, 45), 987, "sSS", "45.32 – 45.98"},
+ { new Date(2019, 2, 10, 1, 23, 45), 321, new Date(2019, 2, 10, 1, 23, 45), 987, "sSSS", "45.321 – 45.987"},
+ { new Date(2019, 2, 10, 1, 23, 45), 321, new Date(2019, 2, 10, 1, 23, 46), 987, "s", "45 – 46"},
+ { new Date(2019, 2, 10, 1, 23, 45), 321, new Date(2019, 2, 10, 1, 23, 46), 987, "sS", "45.3 – 46.9"},
+ { new Date(2019, 2, 10, 1, 23, 45), 321, new Date(2019, 2, 10, 1, 23, 46), 987, "sSS", "45.32 – 46.98"},
+ { new Date(2019, 2, 10, 1, 23, 45), 321, new Date(2019, 2, 10, 1, 23, 46), 987, "sSSS", "45.321 – 46.987"},
+ { new Date(2019, 2, 10, 1, 23, 45), 321, new Date(2019, 2, 10, 2, 24, 45), 987, "s", "45 – 45"},
+ { new Date(2019, 2, 10, 1, 23, 45), 321, new Date(2019, 2, 10, 2, 24, 45), 987, "sS", "45.3 – 45.9"},
+ { new Date(2019, 2, 10, 1, 23, 45), 321, new Date(2019, 2, 10, 2, 24, 45), 987, "sSS", "45.32 – 45.98"},
+ { new Date(2019, 2, 10, 1, 23, 45), 321, new Date(2019, 2, 10, 2, 24, 45), 987, "sSSS", "45.321 – 45.987"},
+ };
+
+ Locale enLocale = Locale.ENGLISH;
+
+ for (Object[] testCase : kTestCases) {
+ DateIntervalFormat fmt = DateIntervalFormat.getInstance((String)testCase[4], enLocale);
+
+ Date fromDate = (Date)testCase[0];
+ long from = fromDate.getTime() + (Integer)testCase[1];
+ Date toDate = (Date)testCase[2];
+ long to = toDate.getTime() + (Integer)testCase[3];
+
+ FormattedDateInterval res = fmt.formatToValue(new DateInterval(from, to));
+ assertEquals("Formate for " + testCase[4], testCase[5], res.toString());
+ }
+ }
}
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/format/DateTimeGeneratorTest.java b/android_icu4j/src/main/tests/android/icu/dev/test/format/DateTimeGeneratorTest.java
index 87c4bc35a..b3a93ad2b 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/format/DateTimeGeneratorTest.java
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/format/DateTimeGeneratorTest.java
@@ -1742,14 +1742,14 @@ public class DateTimeGeneratorTest extends TestFmwk {
String[][] cases = new String[][]{
// ars is interesting because it does not have a region, but it aliases
// to ar_SA, which has a region.
- {"ars", "h a", "h:mm a"},
+ {"ars", "h a", "h:mm a", "HOUR_CYCLE_12"},
// en_NH is interesting because NH is a depregated region code.
- {"en_NH", "h a", "h:mm a"},
+ {"en_NH", "h a", "h:mm a", "HOUR_CYCLE_12"},
// ch_ZH is a typo (should be zh_CN), but we should fail gracefully.
// {"cn_ZH", "HH", "H:mm"}, // TODO(ICU-20653): Desired behavior
- {"cn_ZH", "HH", "h:mm a"}, // Actual behavior
+ {"cn_ZH", "HH", "h:mm a", "HOUR_CYCLE_23"}, // Actual behavior
// a non-BCP47 locale without a country code should not fail
- {"ja_TRADITIONAL", "H時", "H:mm"},
+ {"ja_TRADITIONAL", "H時", "H:mm", "HOUR_CYCLE_23"},
};
for (String[] cas : cases) {
@@ -1764,6 +1764,8 @@ public class DateTimeGeneratorTest extends TestFmwk {
cas[1], dtpgPattern);
assertEquals("timePattern " + cas[1],
cas[2], timePattern);
+ assertEquals("default hour cycle " + cas[3],
+ cas[3], dtpg.getDefaultHourCycle().toString());
}
}
}
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/format/FormattedStringBuilderTest.java b/android_icu4j/src/main/tests/android/icu/dev/test/format/FormattedStringBuilderTest.java
index 07e1a6c3e..d2651df95 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/format/FormattedStringBuilderTest.java
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/format/FormattedStringBuilderTest.java
@@ -9,7 +9,6 @@ import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import java.text.FieldPosition;
-import java.text.Format.Field;
import org.junit.Test;
@@ -173,7 +172,7 @@ public class FormattedStringBuilderTest {
FormattedStringBuilder sb = new FormattedStringBuilder();
sb.append(str, null);
sb.append(str, NumberFormat.Field.CURRENCY);
- Field[] fields = sb.toFieldArray();
+ Object[] fields = sb.toFieldArray();
assertEquals(str.length() * 2, fields.length);
for (int i = 0; i < str.length(); i++) {
assertEquals(null, fields[i]);
@@ -201,7 +200,7 @@ public class FormattedStringBuilderTest {
int numNull = 0;
int numCurr = 0;
int numInt = 0;
- Field[] oldFields = fields;
+ Object[] oldFields = fields;
fields = sb.toFieldArray();
for (int i = 0; i < sb.length(); i++) {
assertEquals(oldFields[i % oldFields.length], fields[i]);
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/format/GlobalizationPreferencesTest.java b/android_icu4j/src/main/tests/android/icu/dev/test/format/GlobalizationPreferencesTest.java
index f1b49efd2..ee0fa33d0 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/format/GlobalizationPreferencesTest.java
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/format/GlobalizationPreferencesTest.java
@@ -350,10 +350,19 @@ public class GlobalizationPreferencesTest extends TestFmwk {
gp.reset();
gp.setLocales(acceptLanguage);
- List resultLocales = gp.getLocales();
+ List<ULocale> resultLocales = gp.getLocales();
+ List<ULocale> expectedLocales = new ArrayList<>(RESULTS_LOCALEIDS[i].length);
+ for (String exp : RESULTS_LOCALEIDS[i]) {
+ expectedLocales.add(new ULocale(exp));
+ }
+ assertEquals("#" + i, expectedLocales.toString(), resultLocales.toString());
if (resultLocales.size() != RESULTS_LOCALEIDS[i].length) {
+ StringBuilder res = new StringBuilder();
+ for (ULocale l : resultLocales) {
+ res.append(l.toString()).append(",");
+ }
errln("FAIL: Number of locales mismatch - GP:" + resultLocales.size()
- + " Expected:" + RESULTS_LOCALEIDS[i].length);
+ + " Expected:" + RESULTS_LOCALEIDS[i].length + " index: " + i + " " + res.toString());
} else {
for (int j = 0; j < RESULTS_LOCALEIDS[i].length; j++) {
@@ -376,22 +385,23 @@ public class GlobalizationPreferencesTest extends TestFmwk {
}
// Invalid accept-language
- logln("Set locale - ko_KR");
- gp.setLocale(new ULocale("ko_KR"));
- boolean bException = false;
- try {
- logln("Set invlaid accept-language - ko=100");
- gp.setLocales("ko=100");
- } catch (IllegalArgumentException iae) {
- logln("IllegalArgumentException was thrown");
- bException = true;
- }
- if (!bException) {
- errln("FAIL: IllegalArgumentException was not thrown for illegal accept-language - ko=100");
- }
- if (!gp.getLocale(0).toString().equals("ko_KR")) {
- errln("FAIL: Previous valid locale list had gone");
- }
+ // ICU-20700 changed the parser to using LocalePriorityList which is more lenient.
+// logln("Set locale - ko_KR");
+// gp.setLocale(new ULocale("ko_KR"));
+// boolean bException = false;
+// try {
+// logln("Set invlaid accept-language - ko=100");
+// gp.setLocales("ko=100");
+// } catch (IllegalArgumentException iae) {
+// logln("IllegalArgumentException was thrown");
+// bException = true;
+// }
+// if (!bException) {
+// errln("FAIL: IllegalArgumentException was not thrown for illegal accept-language - ko=100");
+// }
+// if (!gp.getLocale(0).toString().equals("ko_KR")) {
+// errln("FAIL: Previous valid locale list had gone");
+// }
}
@Test
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/format/IntlTestNumberFormat.java b/android_icu4j/src/main/tests/android/icu/dev/test/format/IntlTestNumberFormat.java
index 5f7a2869d..f958435ab 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/format/IntlTestNumberFormat.java
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/format/IntlTestNumberFormat.java
@@ -14,6 +14,7 @@
**/
package android.icu.dev.test.format;
+
import java.util.Locale;
import java.util.Random;
@@ -24,6 +25,7 @@ import org.junit.runners.JUnit4;
import android.icu.dev.test.TestFmwk;
import android.icu.text.DecimalFormat;
import android.icu.text.NumberFormat;
+import android.icu.util.ULocale;
import android.icu.testsharding.MainTestShard;
/**
@@ -170,6 +172,8 @@ public class IntlTestNumberFormat extends TestFmwk {
boolean dump = false;
int i;
+ String message = "Locale: " + fNumberFormat.getLocale(ULocale.VALID_LOCALE);
+
for (i = 0; i < DEPTH; i++) {
if (i == 0) {
number[i] = aNumber;
@@ -177,7 +181,7 @@ public class IntlTestNumberFormat extends TestFmwk {
try {
number[i - 1] = fNumberFormat.parse(string[i - 1]).doubleValue();
} catch(java.text.ParseException pe) {
- errln("**** FAIL: Parse of " + string[i-1] + " failed.");
+ errln("**** FAIL: Parse of " + string[i-1] + " failed: " + message);
dump = true;
break;
}
@@ -190,7 +194,7 @@ public class IntlTestNumberFormat extends TestFmwk {
numberMatch = i;
else if (numberMatch > 0 && number[i] != number[i-1])
{
- errln("**** FAIL: Numeric mismatch after match.");
+ errln("**** FAIL: Numeric mismatch after match: " + message);
dump = true;
break;
}
@@ -198,7 +202,7 @@ public class IntlTestNumberFormat extends TestFmwk {
stringMatch = i;
else if (stringMatch > 0 && string[i] != string[i-1])
{
- errln("**** FAIL: String mismatch after match.");
+ errln("**** FAIL: String mismatch after match: " + message);
dump = true;
break;
}
@@ -211,7 +215,7 @@ public class IntlTestNumberFormat extends TestFmwk {
if (stringMatch > 2 || numberMatch > 2)
{
- errln("**** FAIL: No string and/or number match within 2 iterations.");
+ errln("**** FAIL: No string and/or number match within 2 iterations: " + message);
dump = true;
}
@@ -232,16 +236,19 @@ public class IntlTestNumberFormat extends TestFmwk {
public void tryIt(int aNumber) {
long number;
+ String message = "Locale: " + fNumberFormat.getLocale(ULocale.VALID_LOCALE);
+
String stringNum = fNumberFormat.format(aNumber);
try {
number = fNumberFormat.parse(stringNum).longValue();
} catch (java.text.ParseException pe) {
- errln("**** FAIL: Parse of " + stringNum + " failed.");
+ errln("**** FAIL: Parse of " + stringNum + " failed: " + message);
return;
}
if (number != aNumber) {
- errln("**** FAIL: Parse of " + stringNum + " failed. Got:" + number
+ errln("**** FAIL: Parse of " + stringNum + " failed: " + message
+ + " Got:" + number
+ " Expected:" + aNumber);
}
@@ -282,9 +289,9 @@ public class IntlTestNumberFormat extends TestFmwk {
count = locales.length;
if (count != 0)
{
- if (TestFmwk.getExhaustiveness() < 10 && count > 6) {
- count = 6;
- locales = new Locale[6];
+ if (TestFmwk.getExhaustiveness() < 10 && count > 7) {
+ count = 7;
+ locales = new Locale[count];
locales[0] = allLocales[0];
locales[1] = allLocales[1];
locales[2] = allLocales[2];
@@ -294,6 +301,7 @@ public class IntlTestNumberFormat extends TestFmwk {
locales[3] = new Locale("ar", "AE", "");
locales[4] = new Locale("cs", "CZ", "");
locales[5] = new Locale("en", "IN", "");
+ locales[6] = new Locale("su", "", "");
}
for (int i=0; i<count; ++i)
{
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/format/ListFormatterTest.java b/android_icu4j/src/main/tests/android/icu/dev/test/format/ListFormatterTest.java
index 958447ede..7e1a7b5c1 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/format/ListFormatterTest.java
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/format/ListFormatterTest.java
@@ -10,6 +10,8 @@
package android.icu.dev.test.format;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
import java.util.Locale;
import org.junit.Test;
@@ -18,6 +20,9 @@ import org.junit.runners.JUnit4;
import android.icu.dev.test.TestFmwk;
import android.icu.text.ListFormatter;
+import android.icu.text.ListFormatter.FormattedList;
+import android.icu.text.ListFormatter.Type;
+import android.icu.text.ListFormatter.Width;
import android.icu.util.ULocale;
import android.icu.testsharding.MainTestShard;
@@ -212,4 +217,144 @@ public class ListFormatterTest extends TestFmwk {
ULocale defaultLocale = ULocale.getDefault(ULocale.Category.FORMAT);
return defaultLocale.equals(ULocale.ENGLISH) || defaultLocale.equals(ULocale.US);
}
+
+ @Test
+ public void TestFormattedValue() {
+ ListFormatter fmt = ListFormatter.getInstance(ULocale.ENGLISH);
+
+ {
+ String message = "Field position test 1";
+ String expectedString = "hello, wonderful, and world";
+ String[] inputs = {
+ "hello",
+ "wonderful",
+ "world"
+ };
+ FormattedList result = fmt.formatToValue(Arrays.asList(inputs));
+ Object[][] expectedFieldPositions = new Object[][] {
+ // field, begin index, end index
+ {ListFormatter.SpanField.LIST_SPAN, 0, 5, 0},
+ {ListFormatter.Field.ELEMENT, 0, 5},
+ {ListFormatter.Field.LITERAL, 5, 7},
+ {ListFormatter.SpanField.LIST_SPAN, 7, 16, 1},
+ {ListFormatter.Field.ELEMENT, 7, 16},
+ {ListFormatter.Field.LITERAL, 16, 22},
+ {ListFormatter.SpanField.LIST_SPAN, 22, 27, 2},
+ {ListFormatter.Field.ELEMENT, 22, 27}};
+ FormattedValueTest.checkFormattedValue(
+ message,
+ result,
+ expectedString,
+ expectedFieldPositions);
+ }
+ }
+
+ @Test
+ public void TestCreateStyled() {
+ // Locale en has interesting data
+ Object[][] cases = {
+ { "pt", Type.AND, Width.WIDE, "A, B e C" },
+ { "pt", Type.AND, Width.SHORT, "A, B e C" },
+ { "pt", Type.AND, Width.NARROW, "A, B, C" },
+ { "pt", Type.OR, Width.WIDE, "A, B ou C" },
+ { "pt", Type.OR, Width.SHORT, "A, B ou C" },
+ { "pt", Type.OR, Width.NARROW, "A, B ou C" },
+ { "pt", Type.UNITS, Width.WIDE, "A, B e C" },
+ { "pt", Type.UNITS, Width.SHORT, "A, B e C" },
+ { "pt", Type.UNITS, Width.NARROW, "A B C" },
+ { "en", Type.AND, Width.WIDE, "A, B, and C" },
+ { "en", Type.AND, Width.SHORT, "A, B, & C" },
+ { "en", Type.AND, Width.NARROW, "A, B, C" },
+ { "en", Type.OR, Width.WIDE, "A, B, or C" },
+ { "en", Type.OR, Width.SHORT, "A, B, or C" },
+ { "en", Type.OR, Width.NARROW, "A, B, or C" },
+ { "en", Type.UNITS, Width.WIDE, "A, B, C" },
+ { "en", Type.UNITS, Width.SHORT, "A, B, C" },
+ { "en", Type.UNITS, Width.NARROW, "A B C" },
+ };
+ for (Object[] cas : cases) {
+ Locale loc = new Locale((String) cas[0]);
+ ULocale uloc = new ULocale((String) cas[0]);
+ Type type = (Type) cas[1];
+ Width width = (Width) cas[2];
+ String expected = (String) cas[3];
+ ListFormatter fmt1 = ListFormatter.getInstance(loc, type, width);
+ ListFormatter fmt2 = ListFormatter.getInstance(uloc, type, width);
+ String message = "TestCreateStyled loc="
+ + loc + " type="
+ + type + " width="
+ + width;
+ String[] inputs = {
+ "A",
+ "B",
+ "C"
+ };
+ String result = fmt1.format(Arrays.asList(inputs));
+ assertEquals(message, expected, result);
+ // Coverage for the other factory method overload:
+ result = fmt2.format(Arrays.asList(inputs));
+ assertEquals(message, expected, result);
+ }
+ }
+
+ @Test
+ public void TestContextual() {
+ String [] es = { "es", "es_419", "es_PY", "es_DO" };
+ String [] he = { "he", "he_IL", "iw", "iw_IL" };
+ Width[] widths = {Width.WIDE, Width.SHORT, Width.NARROW};
+ Object[][] cases = {
+ { es, Type.AND, "fascinante e incre\u00EDblemente", "fascinante", "incre\u00EDblemente"},
+ { es, Type.AND, "Comunicaciones Industriales e IIoT", "Comunicaciones Industriales", "IIoT"},
+ { es, Type.AND, "Espa\u00F1a e Italia", "Espa\u00F1a", "Italia"},
+ { es, Type.AND, "hijas intr\u00E9pidas e hijos solidarios", "hijas intr\u00E9pidas", "hijos solidarios"},
+ { es, Type.AND, "a un hombre e hirieron a otro", "a un hombre", "hirieron a otro"},
+ { es, Type.AND, "hija e hijo", "hija", "hijo"},
+ { es, Type.AND, "esposa, hija e hijo", "esposa", "hija", "hijo"},
+ // For 'y' exception
+ { es, Type.AND, "oro y hierro", "oro", "hierro"},
+ { es, Type.AND, "agua y hielo", "agua", "hielo"},
+ { es, Type.AND, "col\u00E1geno y hialur\u00F3nico", "col\u00E1geno", "hialur\u00F3nico"},
+
+ { es, Type.OR, "desierto u oasis", "desierto", "oasis"},
+ { es, Type.OR, "oasis, desierto u océano", "oasis", "desierto", "océano"},
+ { es, Type.OR, "7 u 8", "7", "8"},
+ { es, Type.OR, "7 u 80", "7", "80"},
+ { es, Type.OR, "7 u 800", "7", "800"},
+ { es, Type.OR, "6, 7 u 8", "6", "7", "8"},
+ { es, Type.OR, "10 u 11", "10", "11"},
+ { es, Type.OR, "10 o 111", "10", "111"},
+ { es, Type.OR, "10 o 11.2", "10", "11.2"},
+ { es, Type.OR, "9, 10 u 11", "9", "10", "11"},
+
+ { he, Type.AND, "a, b \u05D5-c", "a", "b", "c" },
+ { he, Type.AND, "a \u05D5-b", "a", "b" },
+ { he, Type.AND, "1, 2 \u05D5-3", "1", "2", "3" },
+ { he, Type.AND, "1 \u05D5-2", "1", "2" },
+ { he, Type.AND, "\u05D0\u05D4\u05D1\u05D4 \u05D5\u05DE\u05E7\u05D5\u05D5\u05D4",
+ "\u05D0\u05D4\u05D1\u05D4", "\u05DE\u05E7\u05D5\u05D5\u05D4" },
+ { he, Type.AND, "\u05D0\u05D4\u05D1\u05D4, \u05DE\u05E7\u05D5\u05D5\u05D4 \u05D5\u05D0\u05DE\u05D5\u05E0\u05D4",
+ "\u05D0\u05D4\u05D1\u05D4", "\u05DE\u05E7\u05D5\u05D5\u05D4", "\u05D0\u05DE\u05D5\u05E0\u05D4" },
+ };
+ for (Width width : widths) {
+ for (Object[] cas : cases) {
+ String [] locales = (String[]) cas[0];
+ Type type = (Type) cas[1];
+ String expected = (String) cas[2];
+ for (String locale : locales) {
+ ULocale uloc = new ULocale(locale);
+ List inputs = Arrays.asList(cas).subList(3, cas.length);
+ ListFormatter fmt = ListFormatter.getInstance(uloc, type, width);
+ String message = "TestContextual uloc="
+ + uloc + " type="
+ + type + " width="
+ + width + "data=";
+ for (Object i : inputs) {
+ message += i + ",";
+ }
+ String result = fmt.format(inputs);
+ assertEquals(message, expected, result);
+ }
+ }
+ }
+ }
}
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/format/MeasureUnitTest.java b/android_icu4j/src/main/tests/android/icu/dev/test/format/MeasureUnitTest.java
index 1094e2a49..a475414e2 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/format/MeasureUnitTest.java
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/format/MeasureUnitTest.java
@@ -87,7 +87,7 @@ public class MeasureUnitTest extends TestFmwk {
}
}
- private static final String[] DRAFT_VERSIONS = {"64", "65"};
+ private static final String[] DRAFT_VERSIONS = {"64", "65", "66", "67"};
private static final HashSet<String> DRAFT_VERSION_SET = new HashSet<>();
@@ -270,6 +270,10 @@ public class MeasureUnitTest extends TestFmwk {
private static final HashMap<String, String> JAVA_VERSION_MAP = new HashMap<>();
+ // modify certain CLDR unit names before generating functions
+ // that create/get the corresponding MeasureUnit objects
+ private static final Map<String,String> CLDR_NAME_REMAP = new HashMap();
+
static {
TIME_CODES.add("year");
TIME_CODES.add("month");
@@ -284,6 +288,20 @@ public class MeasureUnitTest extends TestFmwk {
for (String[] funcNameAndVersion : JAVA_VERSIONS) {
JAVA_VERSION_MAP.put(funcNameAndVersion[0], funcNameAndVersion[1]);
}
+
+ // CLDR_NAME_REMAP entries
+ // The first two fix overly-generic CLDR unit names
+ CLDR_NAME_REMAP.put("revolution", "revolution-angle");
+ CLDR_NAME_REMAP.put("generic", "generic-temperature");
+ // The next seven map updated CLDR 37 names back to their
+ // old form in order to preserve the old function names
+ CLDR_NAME_REMAP.put("meter-per-square-second", "meter-per-second-squared");
+ CLDR_NAME_REMAP.put("permillion", "part-per-million");
+ CLDR_NAME_REMAP.put("liter-per-100-kilometer", "liter-per-100kilometers");
+ CLDR_NAME_REMAP.put("inch-ofhg", "inch-hg");
+ CLDR_NAME_REMAP.put("millimeter-ofhg", "millimeter-of-mercury");
+ CLDR_NAME_REMAP.put("pound-force-per-square-inch", "pound-per-square-inch");
+ CLDR_NAME_REMAP.put("pound-force-foot", "pound-foot");
}
@Test
@@ -291,12 +309,12 @@ public class MeasureUnitTest extends TestFmwk {
// various generateXXX calls go here, see
// http://site.icu-project.org/design/formatting/measureformat/updating-measure-unit
// use this test to run each of the ollowing in succession
- //generateConstants("65"); // for MeasureUnit.java, update generated MeasureUnit constants
- //generateBackwardCompatibilityTest("65"); // for MeasureUnitTest.java, create TestCompatible65
- //generateCXXHConstants("65"); // for measunit.h, update generated createXXX methods
+ //generateConstants("67"); // for MeasureUnit.java, update generated MeasureUnit constants
+ //generateBackwardCompatibilityTest("67"); // for MeasureUnitTest.java, create TestCompatible65
+ //generateCXXHConstants("67"); // for measunit.h, update generated createXXX methods
//generateCXXConstants(); // for measunit.cpp, update generated code
- //generateCXXBackwardCompatibilityTest("65"); // for measfmttest.cpp, create TestCompatible65
- //updateJAVAVersions("65"); // for MeasureUnitTest.java, JAVA_VERSIONS
+ //generateCXXBackwardCompatibilityTest("67"); // for measfmttest.cpp, create TestCompatible65
+ //updateJAVAVersions("67"); // for MeasureUnitTest.java, JAVA_VERSIONS
}
@Test
@@ -2926,12 +2944,12 @@ public class MeasureUnitTest extends TestFmwk {
StringBuilder result = new StringBuilder();
boolean caps = true;
String code = unit.getSubtype();
- if (code.equals("revolution")) {
- code = code + "-angle";
- }
- if (code.equals("generic")) {
- code = code + "-temperature";
+
+ String replacement = CLDR_NAME_REMAP.get(code);
+ if (replacement != null) {
+ code = replacement;
}
+
int len = code.length();
for (int i = 0; i < len; i++) {
char ch = code.charAt(i);
@@ -3007,19 +3025,17 @@ public class MeasureUnitTest extends TestFmwk {
static String toJAVAName(MeasureUnit unit) {
String code = unit.getSubtype();
String type = unit.getType();
+
+ String replacement = CLDR_NAME_REMAP.get(code);
+ if (replacement != null) {
+ code = replacement;
+ }
+
String name = code.toUpperCase(Locale.ENGLISH).replace("-", "_");
if (type.equals("angle")) {
if (code.equals("minute") || code.equals("second")) {
name = "ARC_" + name;
}
- if (code.equals("revolution")) {
- name = name + "_ANGLE";
- }
- }
- if (type.equals("temperature")) {
- if (code.equals("generic")) {
- name = name + "_TEMPERATURE";
- }
}
return name;
}
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/format/NumberFormatTest.java b/android_icu4j/src/main/tests/android/icu/dev/test/format/NumberFormatTest.java
index 1b0f6bb2a..76f483c66 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/format/NumberFormatTest.java
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/format/NumberFormatTest.java
@@ -4171,6 +4171,62 @@ public class NumberFormatTest extends TestFmwk {
}
@Test
+ public void TestMinIntMinFracZero() {
+ class TestMinIntMinFracItem {
+ double value;;
+ String expDecFmt;
+ String expCurFmt;
+ // Simple constructor
+ public TestMinIntMinFracItem(double valueIn, String expDecFmtIn, String expCurFmtIn) {
+ value = valueIn;
+ expDecFmt = expDecFmtIn;
+ expCurFmt = expCurFmtIn;
+ }
+ };
+
+ final TestMinIntMinFracItem[] items = {
+ // decFmt curFmt
+ new TestMinIntMinFracItem( 10.0, "10", "$10" ),
+ new TestMinIntMinFracItem( 0.9, ".9", "$.9" ),
+ new TestMinIntMinFracItem( 0.0, "0", "$0" ),
+ };
+ int minInt, minFrac;
+
+ NumberFormat decFormat = NumberFormat.getInstance(ULocale.US, NumberFormat.NUMBERSTYLE);
+ decFormat.setMinimumIntegerDigits(0);
+ decFormat.setMinimumFractionDigits(0);
+ minInt = decFormat.getMinimumIntegerDigits();
+ minFrac = decFormat.getMinimumFractionDigits();
+ if (minInt != 0 || minFrac != 0) {
+ errln("after setting DECIMAL minInt=minFrac=0, get minInt " + minInt + ", minFrac " + minFrac);
+ }
+ String decPattern = ((DecimalFormat)decFormat).toPattern();
+ if (decPattern.length() < 3 || decPattern.indexOf("#.#")< 0) {
+ errln("after setting DECIMAL minInt=minFrac=0, expect pattern to contain \"#.#\", but get " + decPattern);
+ }
+
+ NumberFormat curFormat = NumberFormat.getInstance(ULocale.US, NumberFormat.CURRENCYSTYLE);
+ curFormat.setMinimumIntegerDigits(0);
+ curFormat.setMinimumFractionDigits(0);
+ minInt = curFormat.getMinimumIntegerDigits();
+ minFrac = curFormat.getMinimumFractionDigits();
+ if (minInt != 0 || minFrac != 0) {
+ errln("after setting CURRENCY minInt=minFrac=0, get minInt " + minInt + ", minFrac " + minFrac);
+ }
+
+ for (TestMinIntMinFracItem item: items) {
+ String decString = decFormat.format(item.value);
+ if (!decString.equals(item.expDecFmt)) {
+ errln("format DECIMAL value " + item.value + ", expected \"" + item.expDecFmt + "\", got \"" + decString + "\"");
+ }
+ String curString = curFormat.format(item.value);
+ if (!curString.equals(item.expCurFmt)) {
+ errln("format CURRENCY value " + item.value + ", expected \"" + item.expCurFmt + "\", got \"" + curString + "\"");
+ }
+ }
+ }
+
+ @Test
public void TestBug9936() {
DecimalFormat numberFormat =
(DecimalFormat) NumberFormat.getInstance(ULocale.US);
@@ -6735,4 +6791,10 @@ public class NumberFormatTest extends TestFmwk {
assertEquals("result: ", null, result);
}
}
+
+ @Test
+ public void test20961_CurrencyPluralPattern() {
+ DecimalFormat decimalFormat = (DecimalFormat) NumberFormat.getInstance(ULocale.US, NumberFormat.PLURALCURRENCYSTYLE);
+ assertEquals("Currency pattern", "#,##0.00 ¤¤¤", decimalFormat.toPattern());
+ }
}
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/format/PluralRulesTest.java b/android_icu4j/src/main/tests/android/icu/dev/test/format/PluralRulesTest.java
index 0da015d3b..cf23155fb 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/format/PluralRulesTest.java
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/format/PluralRulesTest.java
@@ -43,6 +43,7 @@ import android.icu.dev.util.CollectionUtilities;
import android.icu.impl.Relation;
import android.icu.impl.Utility;
import android.icu.number.FormattedNumber;
+import android.icu.number.LocalizedNumberFormatter;
import android.icu.number.NumberFormatter;
import android.icu.number.Precision;
import android.icu.number.UnlocalizedNumberFormatter;
@@ -932,6 +933,66 @@ public class PluralRulesTest extends TestFmwk {
}
}
+
+
+ @Test
+ public void testCompactDecimalPluralKeyword() {
+ PluralRules rules = PluralRules.createRules("one: i = 0,1 @integer 0, 1 @decimal 0.0~1.5; many: e = 0 and i % 1000000 = 0 and v = 0 or " +
+ "e != 0 .. 5; other: @integer 2~17, 100, 1000, 10000, 100000, 1000000, @decimal 2.0~3.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …");
+ ULocale locale = new ULocale("fr-FR");
+
+ Object[][] casesData = {
+ // unlocalized formatter skeleton, input, string output, plural rule keyword
+ {"", 0, "0", "one"},
+ {"compact-long", 0, "0", "one"},
+
+ {"", 1, "1", "one"},
+ {"compact-long", 1, "1", "one"},
+
+ {"", 2, "2", "other"},
+ {"compact-long", 2, "2", "other"},
+
+ {"", 1000000, "1 000 000", "many"},
+ {"compact-long", 1000000, "1 million", "many"},
+
+ {"", 1000001, "1 000 001", "other"},
+ {"compact-long", 1000001, "1 million", "many"},
+
+ {"", 120000, "1 200 000", "other"},
+ {"compact-long", 1200000, "1,2 millions", "many"},
+
+ {"", 1200001, "1 200 001", "other"},
+ {"compact-long", 1200001, "1,2 millions", "many"},
+
+ {"", 2000000, "2 000 000", "many"},
+ {"compact-long", 2000000, "2 millions", "many"},
+ };
+
+ for (Object[] caseDatum : casesData) {
+ String skeleton = (String) caseDatum[0];
+ int input = (int) caseDatum[1];
+ String expectedString = (String) caseDatum[2];
+ String expectPluralRuleKeyword = (String) caseDatum[3];
+
+ String actualPluralRuleKeyword =
+ getPluralKeyword(rules, locale, input, skeleton);
+
+ assertEquals(
+ String.format("PluralRules select %s: %d", skeleton, input),
+ expectPluralRuleKeyword,
+ actualPluralRuleKeyword);
+ }
+ }
+
+ private String getPluralKeyword(PluralRules rules, ULocale locale, double number, String skeleton) {
+ LocalizedNumberFormatter formatter =
+ NumberFormatter.forSkeleton(skeleton)
+ .locale(locale);
+ FormattedNumber fn = formatter.format(number);
+ String pluralKeyword = rules.select(fn);
+ return pluralKeyword;
+ }
+
enum StandardPluralCategories {
zero, one, two, few, many, other;
/**
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/number/DecimalQuantityTest.java b/android_icu4j/src/main/tests/android/icu/dev/test/number/DecimalQuantityTest.java
index 4038b0b0c..2453d3e4d 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/number/DecimalQuantityTest.java
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/number/DecimalQuantityTest.java
@@ -25,10 +25,15 @@ import android.icu.impl.number.DecimalFormatProperties;
import android.icu.impl.number.DecimalQuantity;
import android.icu.impl.number.DecimalQuantity_DualStorageBCD;
import android.icu.impl.number.RoundingUtils;
+import android.icu.number.FormattedNumber;
import android.icu.number.LocalizedNumberFormatter;
+import android.icu.number.Notation;
import android.icu.number.NumberFormatter;
+import android.icu.number.Precision;
+import android.icu.number.Scale;
import android.icu.text.CompactDecimalFormat.CompactStyle;
import android.icu.text.DecimalFormatSymbols;
+import android.icu.text.PluralRules.Operand;
import android.icu.util.ULocale;
import android.icu.testsharding.MainTestShard;
@@ -533,13 +538,13 @@ public class DecimalQuantityTest extends TestFmwk {
@Test
public void testNickelRounding() {
Object[][] cases = new Object[][] {
- {1.000, -2, RoundingMode.HALF_EVEN, "1."},
- {1.001, -2, RoundingMode.HALF_EVEN, "1."},
- {1.010, -2, RoundingMode.HALF_EVEN, "1."},
- {1.020, -2, RoundingMode.HALF_EVEN, "1."},
- {1.024, -2, RoundingMode.HALF_EVEN, "1."},
- {1.025, -2, RoundingMode.HALF_EVEN, "1."},
- {1.025, -2, RoundingMode.HALF_DOWN, "1."},
+ {1.000, -2, RoundingMode.HALF_EVEN, "1"},
+ {1.001, -2, RoundingMode.HALF_EVEN, "1"},
+ {1.010, -2, RoundingMode.HALF_EVEN, "1"},
+ {1.020, -2, RoundingMode.HALF_EVEN, "1"},
+ {1.024, -2, RoundingMode.HALF_EVEN, "1"},
+ {1.025, -2, RoundingMode.HALF_EVEN, "1"},
+ {1.025, -2, RoundingMode.HALF_DOWN, "1"},
{1.025, -2, RoundingMode.HALF_UP, "1.05"},
{1.026, -2, RoundingMode.HALF_EVEN, "1.05"},
{1.030, -2, RoundingMode.HALF_EVEN, "1.05"},
@@ -555,28 +560,28 @@ public class DecimalQuantityTest extends TestFmwk {
{1.080, -2, RoundingMode.HALF_EVEN, "1.1"},
{1.090, -2, RoundingMode.HALF_EVEN, "1.1"},
{1.099, -2, RoundingMode.HALF_EVEN, "1.1"},
- {1.999, -2, RoundingMode.HALF_EVEN, "2."},
- {2.25, -1, RoundingMode.HALF_EVEN, "2."},
+ {1.999, -2, RoundingMode.HALF_EVEN, "2"},
+ {2.25, -1, RoundingMode.HALF_EVEN, "2"},
{2.25, -1, RoundingMode.HALF_UP, "2.5"},
{2.75, -1, RoundingMode.HALF_DOWN, "2.5"},
- {2.75, -1, RoundingMode.HALF_EVEN, "3."},
- {3.00, -1, RoundingMode.CEILING, "3."},
+ {2.75, -1, RoundingMode.HALF_EVEN, "3"},
+ {3.00, -1, RoundingMode.CEILING, "3"},
{3.25, -1, RoundingMode.CEILING, "3.5"},
{3.50, -1, RoundingMode.CEILING, "3.5"},
- {3.75, -1, RoundingMode.CEILING, "4."},
- {4.00, -1, RoundingMode.FLOOR, "4."},
- {4.25, -1, RoundingMode.FLOOR, "4."},
+ {3.75, -1, RoundingMode.CEILING, "4"},
+ {4.00, -1, RoundingMode.FLOOR, "4"},
+ {4.25, -1, RoundingMode.FLOOR, "4"},
{4.50, -1, RoundingMode.FLOOR, "4.5"},
{4.75, -1, RoundingMode.FLOOR, "4.5"},
- {5.00, -1, RoundingMode.UP, "5."},
+ {5.00, -1, RoundingMode.UP, "5"},
{5.25, -1, RoundingMode.UP, "5.5"},
{5.50, -1, RoundingMode.UP, "5.5"},
- {5.75, -1, RoundingMode.UP, "6."},
- {6.00, -1, RoundingMode.DOWN, "6."},
- {6.25, -1, RoundingMode.DOWN, "6."},
+ {5.75, -1, RoundingMode.UP, "6"},
+ {6.00, -1, RoundingMode.DOWN, "6"},
+ {6.25, -1, RoundingMode.DOWN, "6"},
{6.50, -1, RoundingMode.DOWN, "6.5"},
{6.75, -1, RoundingMode.DOWN, "6.5"},
- {7.00, -1, RoundingMode.UNNECESSARY, "7."},
+ {7.00, -1, RoundingMode.UNNECESSARY, "7"},
{7.50, -1, RoundingMode.UNNECESSARY, "7.5"},
};
for (Object[] cas : cases) {
@@ -606,8 +611,272 @@ public class DecimalQuantityTest extends TestFmwk {
}
}
+ @Test
+ public void testCompactDecimalSuppressedExponent() {
+ ULocale locale = new ULocale("fr-FR");
+
+ Object[][] casesData = {
+ // unlocalized formatter skeleton, input, string output, long output, double output, BigDecimal output, plain string, suppressed exponent
+ {"", 123456789, "123 456 789", 123456789L, 123456789.0, new BigDecimal("123456789"), "123456789", 0},
+ {"compact-long", 123456789, "123 millions", 123000000L, 123000000.0, new BigDecimal("123000000"), "123000000", 6},
+ {"compact-short", 123456789, "123 M", 123000000L, 123000000.0, new BigDecimal("123000000"), "123000000", 6},
+ {"scientific", 123456789, "1,234568E8", 123456800L, 123456800.0, new BigDecimal("123456800"), "123456800", 8},
+
+ {"", 1234567, "1 234 567", 1234567L, 1234567.0, new BigDecimal("1234567"), "1234567", 0},
+ {"compact-long", 1234567, "1,2 million", 1200000L, 1200000.0, new BigDecimal("1200000"), "1200000", 6},
+ {"compact-short", 1234567, "1,2 M", 1200000L, 1200000.0, new BigDecimal("1200000"), "1200000", 6},
+ {"scientific", 1234567, "1,234567E6", 1234567L, 1234567.0, new BigDecimal("1234567"), "1234567", 6},
+
+ {"", 123456, "123 456", 123456L, 123456.0, new BigDecimal("123456"), "123456", 0},
+ {"compact-long", 123456, "123 mille", 123000L, 123000.0, new BigDecimal("123000"), "123000", 3},
+ {"compact-short", 123456, "123 k", 123000L, 123000.0, new BigDecimal("123000"), "123000", 3},
+ {"scientific", 123456, "1,23456E5", 123456L, 123456.0, new BigDecimal("123456"), "123456", 5},
+
+ {"", 123, "123", 123L, 123.0, new BigDecimal("123"), "123", 0},
+ {"compact-long", 123, "123", 123L, 123.0, new BigDecimal("123"), "123", 0},
+ {"compact-short", 123, "123", 123L, 123.0, new BigDecimal("123"), "123", 0},
+ {"scientific", 123, "1,23E2", 123L, 123.0, new BigDecimal("123"), "123", 2},
+
+ {"", 1.2, "1,2", 1L, 1.2, new BigDecimal("1.2"), "1.2", 0},
+ {"compact-long", 1.2, "1,2", 1L, 1.2, new BigDecimal("1.2"), "1.2", 0},
+ {"compact-short", 1.2, "1,2", 1L, 1.2, new BigDecimal("1.2"), "1.2", 0},
+ {"scientific", 1.2, "1,2E0", 1L, 1.2, new BigDecimal("1.2"), "1.2", 0},
+
+ {"", 0.12, "0,12", 0L, 0.12, new BigDecimal("0.12"), "0.12", 0},
+ {"compact-long", 0.12, "0,12", 0L, 0.12, new BigDecimal("0.12"), "0.12", 0},
+ {"compact-short", 0.12, "0,12", 0L, 0.12, new BigDecimal("0.12"), "0.12", 0},
+ {"scientific", 0.12, "1,2E-1", 0L, 0.12, new BigDecimal("0.12"), "0.12", -1},
+
+ {"", 0.012, "0,012", 0L, 0.012, new BigDecimal("0.012"), "0.012", 0},
+ {"compact-long", 0.012, "0,012", 0L, 0.012, new BigDecimal("0.012"), "0.012", 0},
+ {"compact-short", 0.012, "0,012", 0L, 0.012, new BigDecimal("0.012"), "0.012", 0},
+ {"scientific", 0.012, "1,2E-2", 0L, 0.012, new BigDecimal("0.012"), "0.012", -2},
+
+ {"", 999.9, "999,9", 999L, 999.9, new BigDecimal("999.9"), "999.9", 0},
+ {"compact-long", 999.9, "1 millier", 1000L, 1000.0, new BigDecimal("1000"), "1000", 3},
+ {"compact-short", 999.9, "1 k", 1000L, 1000.0, new BigDecimal("1000"), "1000", 3},
+ {"scientific", 999.9, "9,999E2", 999L, 999.9, new BigDecimal("999.9"), "999.9", 2},
+
+ {"", 1000.0, "1 000", 1000L, 1000.0, new BigDecimal("1000"), "1000", 0},
+ {"compact-long", 1000.0, "1 millier", 1000L, 1000.0, new BigDecimal("1000"), "1000", 3},
+ {"compact-short", 1000.0, "1 k", 1000L, 1000.0, new BigDecimal("1000"), "1000", 3},
+ {"scientific", 1000.0, "1E3", 1000L, 1000.0, new BigDecimal("1000"), "1000", 3},
+ };
+
+ for (Object[] caseDatum : casesData) {
+ // test the helper methods used to compute plural operand values
+
+ String skeleton = (String) caseDatum[0];
+ LocalizedNumberFormatter formatter =
+ NumberFormatter.forSkeleton(skeleton)
+ .locale(locale);
+ double input = ((Number) caseDatum[1]).doubleValue();
+ String expectedString = (String) caseDatum[2];
+ long expectedLong = (long) caseDatum[3];
+ double expectedDouble = (double) caseDatum[4];
+ BigDecimal expectedBigDecimal = (BigDecimal) caseDatum[5];
+ String expectedPlainString = (String) caseDatum[6];
+ int expectedSuppressedExponent = (int) caseDatum[7];
+
+ FormattedNumber fn = formatter.format(input);
+ DecimalQuantity_DualStorageBCD dq = (DecimalQuantity_DualStorageBCD)
+ fn.getFixedDecimal();
+ String actualString = fn.toString();
+ long actualLong = dq.toLong(true);
+ double actualDouble = dq.toDouble();
+ BigDecimal actualBigDecimal = dq.toBigDecimal();
+ String actualPlainString = dq.toPlainString();
+ int actualSuppressedExponent = dq.getExponent();
+
+ assertEquals(
+ String.format("formatted number %s toString: %f", skeleton, input),
+ expectedString,
+ actualString);
+ assertEquals(
+ String.format("compact decimal %s toLong: %f", skeleton, input),
+ expectedLong,
+ actualLong);
+ assertDoubleEquals(
+ String.format("compact decimal %s toDouble: %f", skeleton, input),
+ expectedDouble,
+ actualDouble);
+ assertBigDecimalEquals(
+ String.format("compact decimal %s toBigDecimal: %f", skeleton, input),
+ expectedBigDecimal,
+ actualBigDecimal);
+ assertEquals(
+ String.format("formatted number %s toPlainString: %f", skeleton, input),
+ expectedPlainString,
+ actualPlainString);
+ assertEquals(
+ String.format("compact decimal %s suppressed exponent: %f", skeleton, input),
+ expectedSuppressedExponent,
+ actualSuppressedExponent);
+
+ // test the actual computed values of the plural operands
+
+ double expectedNOperand = expectedDouble;
+ double expectedIOperand = expectedLong;
+ double expectedEOperand = expectedSuppressedExponent;
+ double actualNOperand = dq.getPluralOperand(Operand.n);
+ double actualIOperand = dq.getPluralOperand(Operand.i);
+ double actualEOperand = dq.getPluralOperand(Operand.e);
+
+ assertEquals(
+ String.format("formatted number %s toString: %s", skeleton, input),
+ expectedString,
+ actualString);
+ assertDoubleEquals(
+ String.format("compact decimal %s n operand: %f", skeleton, input),
+ expectedNOperand,
+ actualNOperand);
+ assertDoubleEquals(
+ String.format("compact decimal %s i operand: %f", skeleton, input),
+ expectedIOperand,
+ actualIOperand);
+ assertDoubleEquals(
+ String.format("compact decimal %s e operand: %f", skeleton, input),
+ expectedEOperand,
+ actualEOperand);
+ }
+ }
+
+
+ @Test
+ public void testCompactNotationFractionPluralOperands() {
+ ULocale locale = new ULocale("fr-FR");
+ LocalizedNumberFormatter formatter =
+ NumberFormatter.withLocale(locale)
+ .notation(Notation.compactLong())
+ .precision(Precision.fixedFraction(5))
+ .scale(Scale.powerOfTen(-1));
+ double formatterInput = 12345;
+ double inputVal = 1234.5;
+ FormattedNumber fn = formatter.format(formatterInput);
+ DecimalQuantity_DualStorageBCD dq = (DecimalQuantity_DualStorageBCD)
+ fn.getFixedDecimal();
+
+ double expectedNOperand = 1234.5;
+ double expectedIOperand = 1234;
+ double expectedFOperand = 50;
+ double expectedTOperand = 5;
+ double expectedVOperand = 2;
+ double expectedWOperand = 1;
+ double expectedEOperand = 3;
+ String expectedString = "1,23450 millier";
+ double actualNOperand = dq.getPluralOperand(Operand.n);
+ double actualIOperand = dq.getPluralOperand(Operand.i);
+ double actualFOperand = dq.getPluralOperand(Operand.f);
+ double actualTOperand = dq.getPluralOperand(Operand.t);
+ double actualVOperand = dq.getPluralOperand(Operand.v);
+ double actualWOperand = dq.getPluralOperand(Operand.w);
+ double actualEOperand = dq.getPluralOperand(Operand.e);
+ String actualString = fn.toString();
+
+ assertDoubleEquals(
+ String.format("compact decimal fraction n operand: %f", inputVal),
+ expectedNOperand,
+ actualNOperand);
+ assertDoubleEquals(
+ String.format("compact decimal fraction i operand: %f", inputVal),
+ expectedIOperand,
+ actualIOperand);
+ assertDoubleEquals(
+ String.format("compact decimal fraction f operand: %f", inputVal),
+ expectedFOperand,
+ actualFOperand);
+ assertDoubleEquals(
+ String.format("compact decimal fraction t operand: %f", inputVal),
+ expectedTOperand,
+ actualTOperand);
+ assertDoubleEquals(
+ String.format("compact decimal fraction v operand: %f", inputVal),
+ expectedVOperand,
+ actualVOperand);
+ assertDoubleEquals(
+ String.format("compact decimal fraction w operand: %f", inputVal),
+ expectedWOperand,
+ actualWOperand);
+ assertDoubleEquals(
+ String.format("compact decimal fraction e operand: %f", inputVal),
+ expectedEOperand,
+ actualEOperand);
+ assertEquals(
+ String.format("compact decimal fraction toString: %f", inputVal),
+ expectedString,
+ actualString);
+ }
+
+ @Test
+ public void testSuppressedExponentUnchangedByInitialScaling() {
+ ULocale locale = new ULocale("fr-FR");
+ LocalizedNumberFormatter withLocale = NumberFormatter.withLocale(locale);
+ LocalizedNumberFormatter compactLong =
+ withLocale.notation(Notation.compactLong());
+ LocalizedNumberFormatter compactScaled =
+ compactLong.scale(Scale.powerOfTen(3));
+
+ Object[][] casesData = {
+ // input, compact long string output,
+ // compact n operand, compact i operand, compact e operand
+ {123456789, "123 millions", 123000000.0, 123000000.0, 6.0},
+ {1234567, "1,2 million", 1200000.0, 1200000.0, 6.0},
+ {123456, "123 mille", 123000.0, 123000.0, 3.0},
+ {123, "123", 123.0, 123.0, 0.0},
+ };
+
+ for (Object[] caseDatum : casesData) {
+ int input = (int) caseDatum[0];
+ String expectedString = (String) caseDatum[1];
+ double expectedNOperand = (double) caseDatum[2];
+ double expectedIOperand = (double) caseDatum[3];
+ double expectedEOperand = (double) caseDatum[4];
+
+ FormattedNumber fnCompactScaled = compactScaled.format(input);
+ DecimalQuantity_DualStorageBCD dqCompactScaled =
+ (DecimalQuantity_DualStorageBCD) fnCompactScaled.getFixedDecimal();
+ double compactScaledEOperand = dqCompactScaled.getPluralOperand(Operand.e);
+
+ FormattedNumber fnCompact = compactLong.format(input);
+ DecimalQuantity_DualStorageBCD dqCompact =
+ (DecimalQuantity_DualStorageBCD) fnCompact.getFixedDecimal();
+ String actualString = fnCompact.toString();
+ double compactNOperand = dqCompact.getPluralOperand(Operand.n);
+ double compactIOperand = dqCompact.getPluralOperand(Operand.i);
+ double compactEOperand = dqCompact.getPluralOperand(Operand.e);
+ assertEquals(
+ String.format("formatted number compactLong toString: %s", input),
+ expectedString,
+ actualString);
+ assertDoubleEquals(
+ String.format("compact decimal %d, n operand vs. expected", input),
+ expectedNOperand,
+ compactNOperand);
+ assertDoubleEquals(
+ String.format("compact decimal %d, i operand vs. expected", input),
+ expectedIOperand,
+ compactIOperand);
+ assertDoubleEquals(
+ String.format("compact decimal %d, e operand vs. expected", input),
+ expectedEOperand,
+ compactEOperand);
+
+ // By scaling by 10^3 in a locale that has words / compact notation
+ // based on powers of 10^3, we guarantee that the suppressed
+ // exponent will differ by 3.
+ assertDoubleEquals(
+ String.format("decimal %d, e operand for compact vs. compact scaled", input),
+ compactEOperand + 3,
+ compactScaledEOperand);
+ }
+ }
+
+ static boolean doubleEquals(double d1, double d2) {
+ return (Math.abs(d1 - d2) < 1e-6) || (Math.abs((d1 - d2) / d1) < 1e-6);
+ }
+
static void assertDoubleEquals(String message, double d1, double d2) {
- boolean equal = (Math.abs(d1 - d2) < 1e-6) || (Math.abs((d1 - d2) / d1) < 1e-6);
+ boolean equal = doubleEquals(d1, d2);
handleAssert(equal, message, d1, d2, null, false);
}
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/number/ExhaustiveNumberTest.java b/android_icu4j/src/main/tests/android/icu/dev/test/number/ExhaustiveNumberTest.java
index 5edd9579e..a15c038ac 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/number/ExhaustiveNumberTest.java
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/number/ExhaustiveNumberTest.java
@@ -126,8 +126,6 @@ public class ExhaustiveNumberTest extends TestFmwk {
// based on https://github.com/google/double-conversion/issues/28
double[] hardDoubles = {
1651087494906221570.0,
- -5074790912492772E-327,
- 83602530019752571E-327,
2.207817077636718750000000000000,
1.818351745605468750000000000000,
3.941719055175781250000000000000,
@@ -152,9 +150,11 @@ public class ExhaustiveNumberTest extends TestFmwk {
1.305290222167968750000000000000,
3.834922790527343750000000000000, };
- double[] integerDoubles = {
+ double[] exactDoubles = {
51423,
51423e10,
+ -5074790912492772E-327,
+ 83602530019752571E-327,
4.503599627370496E15,
6.789512076111555E15,
9.007199254740991E15,
@@ -164,7 +164,7 @@ public class ExhaustiveNumberTest extends TestFmwk {
checkDoubleBehavior(d, true, "");
}
- for (double d : integerDoubles) {
+ for (double d : exactDoubles) {
checkDoubleBehavior(d, false, "");
}
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/number/MutablePatternModifierTest.java b/android_icu4j/src/main/tests/android/icu/dev/test/number/MutablePatternModifierTest.java
index 32a50cc31..27d41de58 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/number/MutablePatternModifierTest.java
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/number/MutablePatternModifierTest.java
@@ -13,6 +13,7 @@ import android.icu.impl.FormattedStringBuilder;
import android.icu.impl.number.DecimalQuantity;
import android.icu.impl.number.DecimalQuantity_DualStorageBCD;
import android.icu.impl.number.MicroProps;
+import android.icu.impl.number.Modifier.Signum;
import android.icu.impl.number.MutablePatternModifier;
import android.icu.impl.number.PatternStringParser;
import android.icu.number.NumberFormatter.SignDisplay;
@@ -35,19 +36,22 @@ public class MutablePatternModifierTest {
UnitWidth.SHORT,
null);
- mod.setNumberProperties(1, null);
+ mod.setNumberProperties(Signum.POS, null);
assertEquals("a", getPrefix(mod));
assertEquals("b", getSuffix(mod));
mod.setPatternAttributes(SignDisplay.ALWAYS, false);
assertEquals("+a", getPrefix(mod));
assertEquals("b", getSuffix(mod));
- mod.setNumberProperties(0, null);
+ mod.setNumberProperties(Signum.POS_ZERO, null);
assertEquals("+a", getPrefix(mod));
assertEquals("b", getSuffix(mod));
+ mod.setNumberProperties(Signum.NEG_ZERO, null);
+ assertEquals("-a", getPrefix(mod));
+ assertEquals("b", getSuffix(mod));
mod.setPatternAttributes(SignDisplay.EXCEPT_ZERO, false);
assertEquals("a", getPrefix(mod));
assertEquals("b", getSuffix(mod));
- mod.setNumberProperties(-1, null);
+ mod.setNumberProperties(Signum.NEG, null);
assertEquals("-a", getPrefix(mod));
assertEquals("b", getSuffix(mod));
mod.setPatternAttributes(SignDisplay.NEVER, false);
@@ -56,24 +60,27 @@ public class MutablePatternModifierTest {
mod.setPatternInfo(PatternStringParser.parseToPatternInfo("a0b;c-0d"), null);
mod.setPatternAttributes(SignDisplay.AUTO, false);
- mod.setNumberProperties(1, null);
+ mod.setNumberProperties(Signum.POS, null);
assertEquals("a", getPrefix(mod));
assertEquals("b", getSuffix(mod));
mod.setPatternAttributes(SignDisplay.ALWAYS, false);
assertEquals("c+", getPrefix(mod));
assertEquals("d", getSuffix(mod));
- mod.setNumberProperties(0, null);
+ mod.setNumberProperties(Signum.POS_ZERO, null);
assertEquals("c+", getPrefix(mod));
assertEquals("d", getSuffix(mod));
+ mod.setNumberProperties(Signum.NEG_ZERO, null);
+ assertEquals("c-", getPrefix(mod));
+ assertEquals("d", getSuffix(mod));
mod.setPatternAttributes(SignDisplay.EXCEPT_ZERO, false);
assertEquals("a", getPrefix(mod));
assertEquals("b", getSuffix(mod));
- mod.setNumberProperties(-1, null);
+ mod.setNumberProperties(Signum.NEG, null);
assertEquals("c-", getPrefix(mod));
assertEquals("d", getSuffix(mod));
mod.setPatternAttributes(SignDisplay.NEVER, false);
- assertEquals("c-", getPrefix(mod)); // TODO: What should this behavior be?
- assertEquals("d", getSuffix(mod));
+ assertEquals("a", getPrefix(mod));
+ assertEquals("b", getSuffix(mod));
}
@Test
@@ -115,7 +122,7 @@ public class MutablePatternModifierTest {
Currency.getInstance("USD"),
UnitWidth.SHORT,
null);
- mod.setNumberProperties(1, null);
+ mod.setNumberProperties(Signum.POS_ZERO, null);
// Unsafe Code Path
FormattedStringBuilder nsb = new FormattedStringBuilder();
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/number/NumberFormatterApiTest.java b/android_icu4j/src/main/tests/android/icu/dev/test/number/NumberFormatterApiTest.java
index d89d10571..cb72b5ae8 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/number/NumberFormatterApiTest.java
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/number/NumberFormatterApiTest.java
@@ -47,6 +47,7 @@ import android.icu.number.Precision;
import android.icu.number.Scale;
import android.icu.number.ScientificNotation;
import android.icu.number.UnlocalizedNumberFormatter;
+import android.icu.text.ConstrainedFieldPosition;
import android.icu.text.DecimalFormatSymbols;
import android.icu.text.NumberFormat;
import android.icu.text.NumberingSystem;
@@ -69,12 +70,16 @@ public class NumberFormatterApiTest {
private static final Currency ESP = Currency.getInstance("ESP");
private static final Currency PTE = Currency.getInstance("PTE");
private static final Currency RON = Currency.getInstance("RON");
+ private static final Currency TWD = Currency.getInstance("TWD");
+ private static final Currency TRY = Currency.getInstance("TRY");
+ private static final Currency CNY = Currency.getInstance("CNY");
@Test
public void notationSimple() {
assertFormatDescending(
"Basic",
"",
+ "",
NumberFormatter.with(),
ULocale.ENGLISH,
"87,650",
@@ -90,6 +95,7 @@ public class NumberFormatterApiTest {
assertFormatDescendingBig(
"Big Simple",
"notation-simple",
+ "",
NumberFormatter.with().notation(Notation.simple()),
ULocale.ENGLISH,
"87,650,000",
@@ -105,6 +111,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Basic with Negative Sign",
"",
+ "",
NumberFormatter.with(),
ULocale.ENGLISH,
-9876543.21,
@@ -116,6 +123,7 @@ public class NumberFormatterApiTest {
assertFormatDescending(
"Scientific",
"scientific",
+ "E0",
NumberFormatter.with().notation(Notation.scientific()),
ULocale.ENGLISH,
"8.765E4",
@@ -131,6 +139,7 @@ public class NumberFormatterApiTest {
assertFormatDescending(
"Engineering",
"engineering",
+ "EE0",
NumberFormatter.with().notation(Notation.engineering()),
ULocale.ENGLISH,
"87.65E3",
@@ -146,6 +155,7 @@ public class NumberFormatterApiTest {
assertFormatDescending(
"Scientific sign always shown",
"scientific/sign-always",
+ "E+!0",
NumberFormatter.with().notation(Notation.scientific().withExponentSignDisplay(SignDisplay.ALWAYS)),
ULocale.ENGLISH,
"8.765E+4",
@@ -160,7 +170,8 @@ public class NumberFormatterApiTest {
assertFormatDescending(
"Scientific min exponent digits",
- "scientific/+ee",
+ "scientific/*ee",
+ "E00",
NumberFormatter.with().notation(Notation.scientific().withMinExponentDigits(2)),
ULocale.ENGLISH,
"8.765E04",
@@ -176,6 +187,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Scientific Negative",
"scientific",
+ "E0",
NumberFormatter.with().notation(Notation.scientific()),
ULocale.ENGLISH,
-1000000,
@@ -184,6 +196,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Scientific Infinity",
"scientific",
+ "E0",
NumberFormatter.with().notation(Notation.scientific()),
ULocale.ENGLISH,
Double.NEGATIVE_INFINITY,
@@ -192,6 +205,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Scientific NaN",
"scientific",
+ "E0",
NumberFormatter.with().notation(Notation.scientific()),
ULocale.ENGLISH,
Double.NaN,
@@ -203,6 +217,7 @@ public class NumberFormatterApiTest {
assertFormatDescendingBig(
"Compact Short",
"compact-short",
+ "K",
NumberFormatter.with().notation(Notation.compactShort()),
ULocale.ENGLISH,
"88M",
@@ -218,6 +233,7 @@ public class NumberFormatterApiTest {
assertFormatDescendingBig(
"Compact Long",
"compact-long",
+ "KK",
NumberFormatter.with().notation(Notation.compactLong()),
ULocale.ENGLISH,
"88 million",
@@ -233,6 +249,7 @@ public class NumberFormatterApiTest {
assertFormatDescending(
"Compact Short Currency",
"compact-short currency/USD",
+ "K currency/USD",
NumberFormatter.with().notation(Notation.compactShort()).unit(USD),
ULocale.ENGLISH,
"$88K",
@@ -248,6 +265,7 @@ public class NumberFormatterApiTest {
assertFormatDescending(
"Compact Short with ISO Currency",
"compact-short currency/USD unit-width-iso-code",
+ "K currency/USD unit-width-iso-code",
NumberFormatter.with().notation(Notation.compactShort()).unit(USD).unitWidth(UnitWidth.ISO_CODE),
ULocale.ENGLISH,
"USD 88K",
@@ -263,6 +281,7 @@ public class NumberFormatterApiTest {
assertFormatDescending(
"Compact Short with Long Name Currency",
"compact-short currency/USD unit-width-full-name",
+ "K currency/USD unit-width-full-name",
NumberFormatter.with().notation(Notation.compactShort()).unit(USD).unitWidth(UnitWidth.FULL_NAME),
ULocale.ENGLISH,
"88K US dollars",
@@ -280,6 +299,7 @@ public class NumberFormatterApiTest {
assertFormatDescending(
"Compact Long Currency",
"compact-long currency/USD",
+ "KK currency/USD",
NumberFormatter.with().notation(Notation.compactLong()).unit(USD),
ULocale.ENGLISH,
"$88K", // should be something like "$88 thousand"
@@ -297,6 +317,7 @@ public class NumberFormatterApiTest {
assertFormatDescending(
"Compact Long with ISO Currency",
"compact-long currency/USD unit-width-iso-code",
+ "KK currency/USD unit-width-iso-code",
NumberFormatter.with().notation(Notation.compactLong()).unit(USD).unitWidth(UnitWidth.ISO_CODE),
ULocale.ENGLISH,
"USD 88K", // should be something like "USD 88 thousand"
@@ -313,6 +334,7 @@ public class NumberFormatterApiTest {
assertFormatDescending(
"Compact Long with Long Name Currency",
"compact-long currency/USD unit-width-full-name",
+ "KK currency/USD unit-width-full-name",
NumberFormatter.with().notation(Notation.compactLong()).unit(USD).unitWidth(UnitWidth.FULL_NAME),
ULocale.ENGLISH,
"88 thousand US dollars",
@@ -328,14 +350,25 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Compact Plural One",
"compact-long",
+ "KK",
NumberFormatter.with().notation(Notation.compactLong()),
ULocale.forLanguageTag("es"),
1000000,
"1 millón");
assertFormatSingle(
+ "Compact Plural One with rounding",
+ "compact-long precision-integer",
+ "KK precision-integer",
+ NumberFormatter.with().notation(Notation.compactLong()).precision(Precision.integer()),
+ ULocale.forLanguageTag("es"),
+ 1222222,
+ "1 millón");
+
+ assertFormatSingle(
"Compact Plural Other",
"compact-long",
+ "KK",
NumberFormatter.with().notation(Notation.compactLong()),
ULocale.forLanguageTag("es"),
2000000,
@@ -344,6 +377,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Compact with Negative Sign",
"compact-short",
+ "K",
NumberFormatter.with().notation(Notation.compactShort()),
ULocale.ENGLISH,
-9876543.21,
@@ -352,6 +386,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Compact Rounding",
"compact-short",
+ "K",
NumberFormatter.with().notation(Notation.compactShort()),
ULocale.ENGLISH,
990000,
@@ -360,6 +395,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Compact Rounding",
"compact-short",
+ "K",
NumberFormatter.with().notation(Notation.compactShort()),
ULocale.ENGLISH,
999000,
@@ -368,6 +404,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Compact Rounding",
"compact-short",
+ "K",
NumberFormatter.with().notation(Notation.compactShort()),
ULocale.ENGLISH,
999900,
@@ -376,6 +413,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Compact Rounding",
"compact-short",
+ "K",
NumberFormatter.with().notation(Notation.compactShort()),
ULocale.ENGLISH,
9900000,
@@ -384,6 +422,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Compact Rounding",
"compact-short",
+ "K",
NumberFormatter.with().notation(Notation.compactShort()),
ULocale.ENGLISH,
9990000,
@@ -392,6 +431,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Compact in zh-Hant-HK",
"compact-short",
+ "K",
NumberFormatter.with().notation(Notation.compactShort()),
new ULocale("zh-Hant-HK"),
1e7,
@@ -400,6 +440,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Compact in zh-Hant",
"compact-short",
+ "K",
NumberFormatter.with().notation(Notation.compactShort()),
new ULocale("zh-Hant"),
1e7,
@@ -408,6 +449,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Compact Infinity",
"compact-short",
+ "K",
NumberFormatter.with().notation(Notation.compactShort()),
ULocale.ENGLISH,
Double.NEGATIVE_INFINITY,
@@ -416,6 +458,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Compact NaN",
"compact-short",
+ "K",
NumberFormatter.with().notation(Notation.compactShort()),
ULocale.ENGLISH,
Double.NaN,
@@ -429,6 +472,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Compact Somali No Figure",
null, // feature not supported in skeleton
+ null,
NumberFormatter.with().notation(CompactNotation.forCustomData(compactCustomData)),
ULocale.ENGLISH,
1000,
@@ -440,6 +484,7 @@ public class NumberFormatterApiTest {
assertFormatDescending(
"Meters Short",
"measure-unit/length-meter",
+ "unit/meter",
NumberFormatter.with().unit(MeasureUnit.METER),
ULocale.ENGLISH,
"87,650 m",
@@ -455,6 +500,7 @@ public class NumberFormatterApiTest {
assertFormatDescending(
"Meters Long",
"measure-unit/length-meter unit-width-full-name",
+ "unit/meter unit-width-full-name",
NumberFormatter.with().unit(MeasureUnit.METER).unitWidth(UnitWidth.FULL_NAME),
ULocale.ENGLISH,
"87,650 meters",
@@ -470,6 +516,7 @@ public class NumberFormatterApiTest {
assertFormatDescending(
"Compact Meters Long",
"compact-long measure-unit/length-meter unit-width-full-name",
+ "KK unit/meter unit-width-full-name",
NumberFormatter.with().notation(Notation.compactLong()).unit(MeasureUnit.METER)
.unitWidth(UnitWidth.FULL_NAME),
ULocale.ENGLISH,
@@ -486,6 +533,7 @@ public class NumberFormatterApiTest {
assertFormatSingleMeasure(
"Meters with Measure Input",
"unit-width-full-name",
+ "unit-width-full-name",
NumberFormatter.with().unitWidth(UnitWidth.FULL_NAME),
ULocale.ENGLISH,
new Measure(5.43, MeasureUnit.METER),
@@ -494,6 +542,7 @@ public class NumberFormatterApiTest {
assertFormatSingleMeasure(
"Measure format method takes precedence over fluent chain",
"measure-unit/length-meter",
+ "unit/meter",
NumberFormatter.with().unit(MeasureUnit.METER),
ULocale.ENGLISH,
new Measure(5.43, USD),
@@ -502,6 +551,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Meters with Negative Sign",
"measure-unit/length-meter",
+ "unit/meter",
NumberFormatter.with().unit(MeasureUnit.METER),
ULocale.ENGLISH,
-9876543.21,
@@ -511,6 +561,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Interesting Data Fallback 1",
"measure-unit/duration-day unit-width-full-name",
+ "unit/day unit-width-full-name",
NumberFormatter.with().unit(MeasureUnit.DAY).unitWidth(UnitWidth.FULL_NAME),
ULocale.forLanguageTag("brx"),
5.43,
@@ -520,6 +571,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Interesting Data Fallback 2",
"measure-unit/duration-day unit-width-narrow",
+ "unit/day unit-width-narrow",
NumberFormatter.with().unit(MeasureUnit.DAY).unitWidth(UnitWidth.NARROW),
ULocale.forLanguageTag("brx"),
5.43,
@@ -530,15 +582,17 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Interesting Data Fallback 3",
"measure-unit/area-square-meter unit-width-narrow",
+ "unit/square-meter unit-width-narrow",
NumberFormatter.with().unit(MeasureUnit.SQUARE_METER).unitWidth(UnitWidth.NARROW),
ULocale.forLanguageTag("en-GB"),
5.43,
- "5.43 m²");
+ "5.43m²");
// Try accessing a narrow unit directly from root.
assertFormatSingle(
"Interesting Data Fallback 4",
"measure-unit/area-square-meter unit-width-narrow",
+ "unit/square-meter unit-width-narrow",
NumberFormatter.with().unit(MeasureUnit.SQUARE_METER).unitWidth(UnitWidth.NARROW),
ULocale.forLanguageTag("root"),
5.43,
@@ -549,6 +603,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"MeasureUnit Difference between Narrow and Short (Narrow Version)",
"measure-unit/temperature-fahrenheit unit-width-narrow",
+ "unit/fahrenheit unit-width-narrow",
NumberFormatter.with().unit(MeasureUnit.FAHRENHEIT).unitWidth(UnitWidth.NARROW),
ULocale.forLanguageTag("es-US"),
5.43,
@@ -557,6 +612,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"MeasureUnit Difference between Narrow and Short (Short Version)",
"measure-unit/temperature-fahrenheit unit-width-short",
+ "unit/fahrenheit unit-width-short",
NumberFormatter.with().unit(MeasureUnit.FAHRENHEIT).unitWidth(UnitWidth.SHORT),
ULocale.forLanguageTag("es-US"),
5.43,
@@ -565,6 +621,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"MeasureUnit form without {0} in CLDR pattern",
"measure-unit/temperature-kelvin unit-width-full-name",
+ "unit/kelvin unit-width-full-name",
NumberFormatter.with().unit(MeasureUnit.KELVIN).unitWidth(UnitWidth.FULL_NAME),
ULocale.forLanguageTag("es-MX"),
1,
@@ -573,6 +630,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"MeasureUnit form without {0} in CLDR pattern and wide base form",
"measure-unit/temperature-kelvin .00000000000000000000 unit-width-full-name",
+ "unit/kelvin .00000000000000000000 unit-width-full-name",
NumberFormatter.with()
.precision(Precision.fixedFraction(20))
.unit(MeasureUnit.KELVIN)
@@ -587,6 +645,7 @@ public class NumberFormatterApiTest {
assertFormatDescending(
"Meters Per Second Short (unit that simplifies) and perUnit method",
"measure-unit/length-meter per-measure-unit/duration-second",
+ "~unit/meter-per-second", // does not round-trip to the full skeleton above
NumberFormatter.with().unit(MeasureUnit.METER).perUnit(MeasureUnit.SECOND),
ULocale.ENGLISH,
"87,650 m/s",
@@ -602,6 +661,7 @@ public class NumberFormatterApiTest {
assertFormatDescending(
"Pounds Per Square Mile Short (secondary unit has per-format)",
"measure-unit/mass-pound per-measure-unit/area-square-mile",
+ "unit/pound-per-square-mile",
NumberFormatter.with().unit(MeasureUnit.POUND).perUnit(MeasureUnit.SQUARE_MILE),
ULocale.ENGLISH,
"87,650 lb/mi²",
@@ -617,6 +677,7 @@ public class NumberFormatterApiTest {
assertFormatDescending(
"Joules Per Furlong Short (unit with no simplifications or special patterns)",
"measure-unit/energy-joule per-measure-unit/length-furlong",
+ "unit/joule-per-furlong",
NumberFormatter.with().unit(MeasureUnit.JOULE).perUnit(MeasureUnit.FURLONG),
ULocale.ENGLISH,
"87,650 J/fur",
@@ -635,6 +696,7 @@ public class NumberFormatterApiTest {
assertFormatDescending(
"Currency",
"currency/GBP",
+ "currency/GBP",
NumberFormatter.with().unit(GBP),
ULocale.ENGLISH,
"£87,650.00",
@@ -650,6 +712,7 @@ public class NumberFormatterApiTest {
assertFormatDescending(
"Currency ISO",
"currency/GBP unit-width-iso-code",
+ "currency/GBP unit-width-iso-code",
NumberFormatter.with().unit(GBP).unitWidth(UnitWidth.ISO_CODE),
ULocale.ENGLISH,
"GBP 87,650.00",
@@ -665,6 +728,7 @@ public class NumberFormatterApiTest {
assertFormatDescending(
"Currency Long Name",
"currency/GBP unit-width-full-name",
+ "currency/GBP unit-width-full-name",
NumberFormatter.with().unit(GBP).unitWidth(UnitWidth.FULL_NAME),
ULocale.ENGLISH,
"87,650.00 British pounds",
@@ -680,6 +744,7 @@ public class NumberFormatterApiTest {
assertFormatDescending(
"Currency Hidden",
"currency/GBP unit-width-hidden",
+ "currency/GBP unit-width-hidden",
NumberFormatter.with().unit(GBP).unitWidth(UnitWidth.HIDDEN),
ULocale.ENGLISH,
"87,650.00",
@@ -695,6 +760,7 @@ public class NumberFormatterApiTest {
assertFormatSingleMeasure(
"Currency with CurrencyAmount Input",
"",
+ "",
NumberFormatter.with(),
ULocale.ENGLISH,
new CurrencyAmount(5.43, GBP),
@@ -703,6 +769,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Currency Long Name from Pattern Syntax",
null,
+ null,
NumberFormatter.fromDecimalFormat(
PatternStringParser.parseToProperties("0 ¤¤¤"),
DecimalFormatSymbols.getInstance(ULocale.ENGLISH),
@@ -714,6 +781,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Currency with Negative Sign",
"currency/GBP",
+ "currency/GBP",
NumberFormatter.with().unit(GBP),
ULocale.ENGLISH,
-9876543.21,
@@ -724,6 +792,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Currency Difference between Narrow and Short (Narrow Version)",
"currency/USD unit-width-narrow",
+ "currency/USD unit-width-narrow",
NumberFormatter.with().unit(USD).unitWidth(UnitWidth.NARROW),
ULocale.forLanguageTag("en-CA"),
5.43,
@@ -732,14 +801,52 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Currency Difference between Narrow and Short (Short Version)",
"currency/USD unit-width-short",
+ "currency/USD unit-width-short",
NumberFormatter.with().unit(USD).unitWidth(UnitWidth.SHORT),
ULocale.forLanguageTag("en-CA"),
5.43,
"US$5.43");
assertFormatSingle(
+ "Currency Difference between Formal and Short (Formal Version)",
+ "currency/TWD unit-width-formal",
+ "currency/TWD unit-width-formal",
+ NumberFormatter.with().unit(TWD).unitWidth(UnitWidth.FORMAL),
+ ULocale.forLanguageTag("zh-TW"),
+ 5.43,
+ "NT$5.43");
+
+ assertFormatSingle(
+ "Currency Difference between Formal and Short (Short Version)",
+ "currency/TWD unit-width-short",
+ "currency/TWD unit-width-short",
+ NumberFormatter.with().unit(TWD).unitWidth(UnitWidth.SHORT),
+ ULocale.forLanguageTag("zh-TW"),
+ 5.43,
+ "$5.43");
+
+ assertFormatSingle(
+ "Currency Difference between Variant and Short (Formal Version)",
+ "currency/TRY unit-width-variant",
+ "currency/TRY unit-width-variant",
+ NumberFormatter.with().unit(TRY).unitWidth(UnitWidth.VARIANT),
+ ULocale.forLanguageTag("tr-TR"),
+ 5.43,
+ "TL\u00A05,43");
+
+ assertFormatSingle(
+ "Currency Difference between Variant and Short (Short Version)",
+ "currency/TRY unit-width-short",
+ "currency/TRY unit-width-short",
+ NumberFormatter.with().unit(TRY).unitWidth(UnitWidth.SHORT),
+ ULocale.forLanguageTag("tr-TR"),
+ 5.43,
+ "₺5,43");
+
+ assertFormatSingle(
"Currency-dependent format (Control)",
"currency/USD unit-width-short",
+ "currency/USD unit-width-short",
NumberFormatter.with().unit(USD).unitWidth(UnitWidth.SHORT),
ULocale.forLanguageTag("ca"),
444444.55,
@@ -748,6 +855,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Currency-dependent format (Test)",
"currency/ESP unit-width-short",
+ "currency/ESP unit-width-short",
NumberFormatter.with().unit(ESP).unitWidth(UnitWidth.SHORT),
ULocale.forLanguageTag("ca"),
444444.55,
@@ -756,6 +864,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Currency-dependent symbols (Control)",
"currency/USD unit-width-short",
+ "currency/USD unit-width-short",
NumberFormatter.with().unit(USD).unitWidth(UnitWidth.SHORT),
ULocale.forLanguageTag("pt-PT"),
444444.55,
@@ -766,6 +875,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Currency-dependent symbols (Test Short)",
"currency/PTE unit-width-short",
+ "currency/PTE unit-width-short",
NumberFormatter.with().unit(PTE).unitWidth(UnitWidth.SHORT),
ULocale.forLanguageTag("pt-PT"),
444444.55,
@@ -774,6 +884,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Currency-dependent symbols (Test Narrow)",
"currency/PTE unit-width-narrow",
+ "currency/PTE unit-width-narrow",
NumberFormatter.with().unit(PTE).unitWidth(UnitWidth.NARROW),
ULocale.forLanguageTag("pt-PT"),
444444.55,
@@ -782,6 +893,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Currency-dependent symbols (Test ISO Code)",
"currency/PTE unit-width-iso-code",
+ "currency/PTE unit-width-iso-code",
NumberFormatter.with().unit(PTE).unitWidth(UnitWidth.ISO_CODE),
ULocale.forLanguageTag("pt-PT"),
444444.55,
@@ -790,10 +902,20 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Plural form depending on visible digits (ICU-20499)",
"currency/RON unit-width-full-name",
+ "currency/RON unit-width-full-name",
NumberFormatter.with().unit(RON).unitWidth(UnitWidth.FULL_NAME),
ULocale.forLanguageTag("ro-RO"),
24,
"24,00 lei românești");
+
+ assertFormatSingle(
+ "Currency spacing in suffix (ICU-20954)",
+ "currency/CNY",
+ "currency/CNY",
+ NumberFormatter.with().unit(CNY),
+ ULocale.forLanguageTag("lu"),
+ 123.12,
+ "123,12 CN¥");
}
@Test
@@ -801,6 +923,7 @@ public class NumberFormatterApiTest {
assertFormatDescending(
"Percent",
"percent",
+ "%",
NumberFormatter.with().unit(NoUnit.PERCENT),
ULocale.ENGLISH,
"87,650%",
@@ -816,6 +939,7 @@ public class NumberFormatterApiTest {
assertFormatDescending(
"Permille",
"permille",
+ "permille",
NumberFormatter.with().unit(NoUnit.PERMILLE),
ULocale.ENGLISH,
"87,650‰",
@@ -831,6 +955,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"NoUnit Base",
"base-unit",
+ "",
NumberFormatter.with().unit(NoUnit.BASE),
ULocale.ENGLISH,
51423,
@@ -839,6 +964,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Percent with Negative Sign",
"percent",
+ "%",
NumberFormatter.with().unit(NoUnit.PERCENT),
ULocale.ENGLISH,
-98.7654321,
@@ -850,6 +976,7 @@ public class NumberFormatterApiTest {
assertFormatDescending(
"Integer",
"precision-integer",
+ ".",
NumberFormatter.with().precision(Precision.integer()),
ULocale.ENGLISH,
"87,650",
@@ -865,6 +992,7 @@ public class NumberFormatterApiTest {
assertFormatDescending(
"Fixed Fraction",
".000",
+ ".000",
NumberFormatter.with().precision(Precision.fixedFraction(3)),
ULocale.ENGLISH,
"87,650.000",
@@ -879,6 +1007,7 @@ public class NumberFormatterApiTest {
assertFormatDescending(
"Min Fraction",
+ ".0*",
".0+",
NumberFormatter.with().precision(Precision.minFraction(1)),
ULocale.ENGLISH,
@@ -895,6 +1024,7 @@ public class NumberFormatterApiTest {
assertFormatDescending(
"Max Fraction",
".#",
+ ".#",
NumberFormatter.with().precision(Precision.maxFraction(1)),
ULocale.ENGLISH,
"87,650",
@@ -910,6 +1040,7 @@ public class NumberFormatterApiTest {
assertFormatDescending(
"Min/Max Fraction",
".0##",
+ ".0##",
NumberFormatter.with().precision(Precision.minMaxFraction(1, 3)),
ULocale.ENGLISH,
"87,650.0",
@@ -928,6 +1059,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Fixed Significant",
"@@@",
+ "@@@",
NumberFormatter.with().precision(Precision.fixedSignificantDigits(3)),
ULocale.ENGLISH,
-98,
@@ -936,6 +1068,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Fixed Significant Rounding",
"@@@",
+ "@@@",
NumberFormatter.with().precision(Precision.fixedSignificantDigits(3)),
ULocale.ENGLISH,
-98.7654321,
@@ -944,6 +1077,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Fixed Significant Zero",
"@@@",
+ "@@@",
NumberFormatter.with().precision(Precision.fixedSignificantDigits(3)),
ULocale.ENGLISH,
0,
@@ -951,6 +1085,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Min Significant",
+ "@@*",
"@@+",
NumberFormatter.with().precision(Precision.minSignificantDigits(2)),
ULocale.ENGLISH,
@@ -960,6 +1095,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Max Significant",
"@###",
+ "@###",
NumberFormatter.with().precision(Precision.maxSignificantDigits(4)),
ULocale.ENGLISH,
98.7654321,
@@ -968,6 +1104,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Min/Max Significant",
"@@@#",
+ "@@@#",
NumberFormatter.with().precision(Precision.minMaxSignificantDigits(3, 4)),
ULocale.ENGLISH,
9.99999,
@@ -975,6 +1112,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Fixed Significant on zero with zero integer width",
+ "@ integer-width/*",
"@ integer-width/+",
NumberFormatter.with().precision(Precision.fixedSignificantDigits(1)).integerWidth(IntegerWidth.zeroFillTo(0)),
ULocale.ENGLISH,
@@ -984,6 +1122,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Fixed Significant on zero with lots of integer width",
"@ integer-width/+000",
+ "@ 000",
NumberFormatter.with().precision(Precision.fixedSignificantDigits(1)).integerWidth(IntegerWidth.zeroFillTo(3)),
ULocale.ENGLISH,
0,
@@ -995,6 +1134,7 @@ public class NumberFormatterApiTest {
assertFormatDescending(
"Basic Significant", // for comparison
"@#",
+ "@#",
NumberFormatter.with().precision(Precision.maxSignificantDigits(2)),
ULocale.ENGLISH,
"88,000",
@@ -1009,6 +1149,7 @@ public class NumberFormatterApiTest {
assertFormatDescending(
"FracSig minMaxFrac minSig",
+ ".0#/@@@*",
".0#/@@@+",
NumberFormatter.with().precision(Precision.minMaxFraction(1, 2).withMinDigits(3)),
ULocale.ENGLISH,
@@ -1025,6 +1166,7 @@ public class NumberFormatterApiTest {
assertFormatDescending(
"FracSig minMaxFrac maxSig A",
".0##/@#",
+ ".0##/@#",
NumberFormatter.with().precision(Precision.minMaxFraction(1, 3).withMaxDigits(2)),
ULocale.ENGLISH,
"88,000.0", // maxSig beats maxFrac
@@ -1040,6 +1182,7 @@ public class NumberFormatterApiTest {
assertFormatDescending(
"FracSig minMaxFrac maxSig B",
".00/@#",
+ ".00/@#",
NumberFormatter.with().precision(Precision.fixedFraction(2).withMaxDigits(2)),
ULocale.ENGLISH,
"88,000.00", // maxSig beats maxFrac
@@ -1055,6 +1198,7 @@ public class NumberFormatterApiTest {
assertFormatDescending(
"FracSig minFrac maxSig",
".0+/@#",
+ ".0+/@#",
NumberFormatter.with().precision(Precision.minFraction(1).withMaxDigits(2)),
ULocale.ENGLISH,
"88,000.0",
@@ -1069,6 +1213,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"FracSig with trailing zeros A",
+ ".00/@@@*",
".00/@@@+",
NumberFormatter.with().precision(Precision.fixedFraction(2).withMinDigits(3)),
ULocale.ENGLISH,
@@ -1077,6 +1222,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"FracSig with trailing zeros B",
+ ".00/@@@*",
".00/@@@+",
NumberFormatter.with().precision(Precision.fixedFraction(2).withMinDigits(3)),
ULocale.ENGLISH,
@@ -1089,6 +1235,7 @@ public class NumberFormatterApiTest {
assertFormatDescending(
"Rounding None",
"precision-unlimited",
+ ".+",
NumberFormatter.with().precision(Precision.unlimited()),
ULocale.ENGLISH,
"87,650",
@@ -1104,6 +1251,7 @@ public class NumberFormatterApiTest {
assertFormatDescending(
"Increment",
"precision-increment/0.5",
+ "precision-increment/0.5",
NumberFormatter.with().precision(Precision.increment(BigDecimal.valueOf(0.5))),
ULocale.ENGLISH,
"87,650.0",
@@ -1119,6 +1267,7 @@ public class NumberFormatterApiTest {
assertFormatDescending(
"Increment with Min Fraction",
"precision-increment/0.50",
+ "precision-increment/0.50",
NumberFormatter.with().precision(Precision.increment(new BigDecimal("0.50"))),
ULocale.ENGLISH,
"87,650.00",
@@ -1134,6 +1283,7 @@ public class NumberFormatterApiTest {
assertFormatDescending(
"Strange Increment",
"precision-increment/3.140",
+ "precision-increment/3.140",
NumberFormatter.with().precision(Precision.increment(new BigDecimal("3.140"))),
ULocale.ENGLISH,
"87,649.960",
@@ -1149,6 +1299,7 @@ public class NumberFormatterApiTest {
assertFormatDescending(
"Increment Resolving to Power of 10",
"precision-increment/0.010",
+ "precision-increment/0.010",
NumberFormatter.with().precision(Precision.increment(new BigDecimal("0.010"))),
ULocale.ENGLISH,
"87,650.000",
@@ -1164,6 +1315,7 @@ public class NumberFormatterApiTest {
assertFormatDescending(
"Currency Standard",
"currency/CZK precision-currency-standard",
+ "currency/CZK precision-currency-standard",
NumberFormatter.with().precision(Precision.currency(CurrencyUsage.STANDARD)).unit(CZK),
ULocale.ENGLISH,
"CZK 87,650.00",
@@ -1179,6 +1331,7 @@ public class NumberFormatterApiTest {
assertFormatDescending(
"Currency Cash",
"currency/CZK precision-currency-cash",
+ "currency/CZK precision-currency-cash",
NumberFormatter.with().precision(Precision.currency(CurrencyUsage.CASH)).unit(CZK),
ULocale.ENGLISH,
"CZK 87,650",
@@ -1194,6 +1347,7 @@ public class NumberFormatterApiTest {
assertFormatDescending(
"Currency Cash with Nickel Rounding",
"currency/CAD precision-currency-cash",
+ "currency/CAD precision-currency-cash",
NumberFormatter.with().precision(Precision.currency(CurrencyUsage.CASH)).unit(CAD),
ULocale.ENGLISH,
"CA$87,650.00",
@@ -1209,6 +1363,7 @@ public class NumberFormatterApiTest {
assertFormatDescending(
"Currency not in top-level fluent chain",
"precision-integer", // calling .withCurrency() applies currency rounding rules immediately
+ ".",
NumberFormatter.with().precision(Precision.currency(CurrencyUsage.CASH).withCurrency(CZK)),
ULocale.ENGLISH,
"87,650",
@@ -1225,6 +1380,7 @@ public class NumberFormatterApiTest {
assertFormatDescending(
"Rounding Mode CEILING",
"precision-integer rounding-mode-ceiling",
+ ". rounding-mode-ceiling",
NumberFormatter.with().precision(Precision.integer()).roundingMode(RoundingMode.CEILING),
ULocale.ENGLISH,
"87,650",
@@ -1236,6 +1392,24 @@ public class NumberFormatterApiTest {
"1",
"1",
"0");
+
+ assertFormatSingle(
+ "ICU-20974 Double.MIN_NORMAL",
+ "scientific",
+ "E0",
+ NumberFormatter.with().notation(Notation.scientific()),
+ ULocale.ENGLISH,
+ Double.MIN_NORMAL,
+ "2.225074E-308");
+
+ assertFormatSingle(
+ "ICU-20974 Double.MIN_VALUE",
+ "scientific",
+ "E0",
+ NumberFormatter.with().notation(Notation.scientific()),
+ ULocale.ENGLISH,
+ Double.MIN_VALUE,
+ "4.9E-324");
}
@Test
@@ -1243,6 +1417,7 @@ public class NumberFormatterApiTest {
assertFormatDescendingBig(
"Western Grouping",
"group-auto",
+ "",
NumberFormatter.with().grouping(GroupingStrategy.AUTO),
ULocale.ENGLISH,
"87,650,000",
@@ -1258,6 +1433,7 @@ public class NumberFormatterApiTest {
assertFormatDescendingBig(
"Indic Grouping",
"group-auto",
+ "",
NumberFormatter.with().grouping(GroupingStrategy.AUTO),
new ULocale("en-IN"),
"8,76,50,000",
@@ -1273,6 +1449,7 @@ public class NumberFormatterApiTest {
assertFormatDescendingBig(
"Western Grouping, Min 2",
"group-min2",
+ ",?",
NumberFormatter.with().grouping(GroupingStrategy.MIN2),
ULocale.ENGLISH,
"87,650,000",
@@ -1288,6 +1465,7 @@ public class NumberFormatterApiTest {
assertFormatDescendingBig(
"Indic Grouping, Min 2",
"group-min2",
+ ",?",
NumberFormatter.with().grouping(GroupingStrategy.MIN2),
new ULocale("en-IN"),
"8,76,50,000",
@@ -1303,6 +1481,7 @@ public class NumberFormatterApiTest {
assertFormatDescendingBig(
"No Grouping",
"group-off",
+ ",_",
NumberFormatter.with().grouping(GroupingStrategy.OFF),
new ULocale("en-IN"),
"87650000",
@@ -1318,6 +1497,7 @@ public class NumberFormatterApiTest {
assertFormatDescendingBig(
"Indic locale with THOUSANDS grouping",
"group-thousands",
+ "group-thousands",
NumberFormatter.with().grouping(GroupingStrategy.THOUSANDS),
new ULocale("en-IN"),
"87,650,000",
@@ -1336,6 +1516,7 @@ public class NumberFormatterApiTest {
assertFormatDescendingBig(
"Polish Grouping",
"group-auto",
+ "",
NumberFormatter.with().grouping(GroupingStrategy.AUTO),
new ULocale("pl"),
"87 650 000",
@@ -1351,6 +1532,7 @@ public class NumberFormatterApiTest {
assertFormatDescendingBig(
"Polish Grouping, Min 2",
"group-min2",
+ ",?",
NumberFormatter.with().grouping(GroupingStrategy.MIN2),
new ULocale("pl"),
"87 650 000",
@@ -1366,6 +1548,7 @@ public class NumberFormatterApiTest {
assertFormatDescendingBig(
"Polish Grouping, Always",
"group-on-aligned",
+ ",!",
NumberFormatter.with().grouping(GroupingStrategy.ON_ALIGNED),
new ULocale("pl"),
"87 650 000",
@@ -1383,6 +1566,7 @@ public class NumberFormatterApiTest {
assertFormatDescendingBig(
"Bulgarian Currency Grouping",
"currency/USD group-auto",
+ "currency/USD",
NumberFormatter.with().grouping(GroupingStrategy.AUTO).unit(USD),
new ULocale("bg"),
"87650000,00 щ.д.",
@@ -1398,6 +1582,7 @@ public class NumberFormatterApiTest {
assertFormatDescendingBig(
"Bulgarian Currency Grouping, Always",
"currency/USD group-on-aligned",
+ "currency/USD ,!",
NumberFormatter.with().grouping(GroupingStrategy.ON_ALIGNED).unit(USD),
new ULocale("bg"),
"87 650 000,00 щ.д.",
@@ -1415,6 +1600,7 @@ public class NumberFormatterApiTest {
assertFormatDescendingBig(
"Custom Grouping via Internal API",
null,
+ null,
NumberFormatter.with().macros(macros),
ULocale.ENGLISH,
"8,7,6,5,0000",
@@ -1433,6 +1619,7 @@ public class NumberFormatterApiTest {
assertFormatDescending(
"Padding",
null,
+ null,
NumberFormatter.with().padding(Padder.none()),
ULocale.ENGLISH,
"87,650",
@@ -1448,6 +1635,7 @@ public class NumberFormatterApiTest {
assertFormatDescending(
"Padding",
null,
+ null,
NumberFormatter.with().padding(Padder.codePoints('*', 8, PadPosition.AFTER_PREFIX)),
ULocale.ENGLISH,
"**87,650",
@@ -1463,6 +1651,7 @@ public class NumberFormatterApiTest {
assertFormatDescending(
"Padding with code points",
null,
+ null,
NumberFormatter.with().padding(Padder.codePoints(0x101E4, 8, PadPosition.AFTER_PREFIX)),
ULocale.ENGLISH,
"𐇤𐇤87,650",
@@ -1478,6 +1667,7 @@ public class NumberFormatterApiTest {
assertFormatDescending(
"Padding with wide digits",
null,
+ null,
NumberFormatter.with().padding(Padder.codePoints('*', 8, PadPosition.AFTER_PREFIX))
.symbols(NumberingSystem.getInstanceByName("mathsanb")),
ULocale.ENGLISH,
@@ -1494,6 +1684,7 @@ public class NumberFormatterApiTest {
assertFormatDescending(
"Padding with currency spacing",
null,
+ null,
NumberFormatter.with().padding(Padder.codePoints('*', 10, PadPosition.AFTER_PREFIX)).unit(GBP)
.unitWidth(UnitWidth.ISO_CODE),
ULocale.ENGLISH,
@@ -1510,6 +1701,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Pad Before Prefix",
null,
+ null,
NumberFormatter.with().padding(Padder.codePoints('*', 8, PadPosition.BEFORE_PREFIX)),
ULocale.ENGLISH,
-88.88,
@@ -1518,6 +1710,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Pad After Prefix",
null,
+ null,
NumberFormatter.with().padding(Padder.codePoints('*', 8, PadPosition.AFTER_PREFIX)),
ULocale.ENGLISH,
-88.88,
@@ -1526,6 +1719,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Pad Before Suffix",
null,
+ null,
NumberFormatter.with().padding(Padder.codePoints('*', 8, PadPosition.BEFORE_SUFFIX))
.unit(NoUnit.PERCENT),
ULocale.ENGLISH,
@@ -1535,6 +1729,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Pad After Suffix",
null,
+ null,
NumberFormatter.with().padding(Padder.codePoints('*', 8, PadPosition.AFTER_SUFFIX))
.unit(NoUnit.PERCENT),
ULocale.ENGLISH,
@@ -1544,6 +1739,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Currency Spacing with Zero Digit Padding Broken",
null,
+ null,
NumberFormatter.with().padding(Padder.codePoints('0', 12, PadPosition.AFTER_PREFIX)).unit(GBP)
.unitWidth(UnitWidth.ISO_CODE),
ULocale.ENGLISH,
@@ -1556,6 +1752,7 @@ public class NumberFormatterApiTest {
assertFormatDescending(
"Integer Width Default",
"integer-width/+0",
+ "0",
NumberFormatter.with().integerWidth(IntegerWidth.zeroFillTo(1)),
ULocale.ENGLISH,
"87,650",
@@ -1570,6 +1767,7 @@ public class NumberFormatterApiTest {
assertFormatDescending(
"Integer Width Zero Fill 0",
+ "integer-width/*",
"integer-width/+",
NumberFormatter.with().integerWidth(IntegerWidth.zeroFillTo(0)),
ULocale.ENGLISH,
@@ -1581,11 +1779,12 @@ public class NumberFormatterApiTest {
".8765",
".08765",
".008765",
- ""); // TODO: Avoid the empty string here?
+ "0"); // see ICU-20844
assertFormatDescending(
"Integer Width Zero Fill 3",
"integer-width/+000",
+ "000",
NumberFormatter.with().integerWidth(IntegerWidth.zeroFillTo(3)),
ULocale.ENGLISH,
"87,650",
@@ -1601,6 +1800,7 @@ public class NumberFormatterApiTest {
assertFormatDescending(
"Integer Width Max 3",
"integer-width/##0",
+ "integer-width/##0",
NumberFormatter.with().integerWidth(IntegerWidth.zeroFillTo(1).truncateAt(3)),
ULocale.ENGLISH,
"650",
@@ -1616,6 +1816,7 @@ public class NumberFormatterApiTest {
assertFormatDescending(
"Integer Width Fixed 2",
"integer-width/00",
+ "integer-width/00",
NumberFormatter.with().integerWidth(IntegerWidth.zeroFillTo(2).truncateAt(2)),
ULocale.ENGLISH,
"50",
@@ -1628,9 +1829,64 @@ public class NumberFormatterApiTest {
"00.008765",
"00");
+ assertFormatDescending(
+ "Integer Width Compact",
+ "compact-short integer-width/000",
+ "K integer-width/000",
+ NumberFormatter.with()
+ .notation(Notation.compactShort())
+ .integerWidth(IntegerWidth.zeroFillTo(3).truncateAt(3)),
+ ULocale.ENGLISH,
+ "088K",
+ "008.8K",
+ "876",
+ "088",
+ "008.8",
+ "000.88",
+ "000.088",
+ "000.0088",
+ "000");
+
+ assertFormatDescending(
+ "Integer Width Scientific",
+ "scientific integer-width/000",
+ "E0 integer-width/000",
+ NumberFormatter.with()
+ .notation(Notation.scientific())
+ .integerWidth(IntegerWidth.zeroFillTo(3).truncateAt(3)),
+ ULocale.ENGLISH,
+ "008.765E4",
+ "008.765E3",
+ "008.765E2",
+ "008.765E1",
+ "008.765E0",
+ "008.765E-1",
+ "008.765E-2",
+ "008.765E-3",
+ "000E0");
+
+ assertFormatDescending(
+ "Integer Width Engineering",
+ "engineering integer-width/000",
+ "EE0 integer-width/000",
+ NumberFormatter.with()
+ .notation(Notation.engineering())
+ .integerWidth(IntegerWidth.zeroFillTo(3).truncateAt(3)),
+ ULocale.ENGLISH,
+ "087.65E3",
+ "008.765E3",
+ "876.5E0",
+ "087.65E0",
+ "008.765E0",
+ "876.5E-3",
+ "087.65E-3",
+ "008.765E-3",
+ "000E0");
+
assertFormatSingle(
"Integer Width Remove All A",
"integer-width/00",
+ "integer-width/00",
NumberFormatter.with().integerWidth(IntegerWidth.zeroFillTo(2).truncateAt(2)),
ULocale.ENGLISH,
2500,
@@ -1639,6 +1895,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Integer Width Remove All B",
"integer-width/00",
+ "integer-width/00",
NumberFormatter.with().integerWidth(IntegerWidth.zeroFillTo(2).truncateAt(2)),
ULocale.ENGLISH,
25000,
@@ -1647,6 +1904,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Integer Width Remove All B, Bytes Mode",
"integer-width/00",
+ "integer-width/00",
NumberFormatter.with().integerWidth(IntegerWidth.zeroFillTo(2).truncateAt(2)),
ULocale.ENGLISH,
// Note: this double produces all 17 significant digits
@@ -1659,6 +1917,7 @@ public class NumberFormatterApiTest {
assertFormatDescending(
"French Symbols with Japanese Data 1",
null,
+ null,
NumberFormatter.with().symbols(DecimalFormatSymbols.getInstance(ULocale.FRENCH)),
ULocale.JAPAN,
"87\u202F650",
@@ -1674,6 +1933,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"French Symbols with Japanese Data 2",
null,
+ null,
NumberFormatter.with().notation(Notation.compactShort())
.symbols(DecimalFormatSymbols.getInstance(ULocale.FRENCH)),
ULocale.JAPAN,
@@ -1683,6 +1943,7 @@ public class NumberFormatterApiTest {
assertFormatDescending(
"Latin Numbering System with Arabic Data",
"currency/USD latin",
+ "currency/USD latin",
NumberFormatter.with().symbols(NumberingSystem.LATIN).unit(USD),
new ULocale("ar"),
"US$ 87,650.00",
@@ -1698,6 +1959,7 @@ public class NumberFormatterApiTest {
assertFormatDescending(
"Math Numbering System with French Data",
"numbering-system/mathsanb",
+ "numbering-system/mathsanb",
NumberFormatter.with().symbols(NumberingSystem.getInstanceByName("mathsanb")),
ULocale.FRENCH,
"𝟴𝟳\u202f𝟲𝟱𝟬",
@@ -1713,6 +1975,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Swiss Symbols (used in documentation)",
null,
+ null,
NumberFormatter.with().symbols(DecimalFormatSymbols.getInstance(new ULocale("de-CH"))),
ULocale.ENGLISH,
12345.67,
@@ -1721,6 +1984,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Myanmar Symbols (used in documentation)",
null,
+ null,
NumberFormatter.with().symbols(DecimalFormatSymbols.getInstance(new ULocale("my_MY"))),
ULocale.ENGLISH,
12345.67,
@@ -1731,6 +1995,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Currency symbol should precede number in ar with NS latn",
"currency/USD latin",
+ "currency/USD latin",
NumberFormatter.with().symbols(NumberingSystem.LATIN).unit(USD),
new ULocale("ar"),
12345.67,
@@ -1739,6 +2004,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Currency symbol should precede number in ar@numbers=latn",
"currency/USD",
+ "currency/USD",
NumberFormatter.with().unit(USD),
new ULocale("ar@numbers=latn"),
12345.67,
@@ -1747,6 +2013,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Currency symbol should follow number in ar-EG with NS arab",
"currency/USD",
+ "currency/USD",
NumberFormatter.with().unit(USD),
new ULocale("ar-EG"),
12345.67,
@@ -1755,6 +2022,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Currency symbol should follow number in ar@numbers=arab",
"currency/USD",
+ "currency/USD",
NumberFormatter.with().unit(USD),
new ULocale("ar@numbers=arab"),
12345.67,
@@ -1763,6 +2031,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"NumberingSystem in API should win over @numbers keyword",
"currency/USD latin",
+ "currency/USD latin",
NumberFormatter.with().symbols(NumberingSystem.LATIN).unit(USD),
new ULocale("ar@numbers=arab"),
12345.67,
@@ -1782,6 +2051,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Symbols object should be copied",
null,
+ null,
f,
ULocale.ENGLISH,
12345.67,
@@ -1790,6 +2060,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"The last symbols setter wins",
"latin",
+ "latin",
NumberFormatter.with().symbols(symbols).symbols(NumberingSystem.LATIN),
ULocale.ENGLISH,
12345.67,
@@ -1798,6 +2069,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"The last symbols setter wins",
null,
+ null,
NumberFormatter.with().symbols(NumberingSystem.LATIN).symbols(symbols),
ULocale.ENGLISH,
12345.67,
@@ -1813,6 +2085,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Custom Short Currency Symbol",
"$XXX",
+ "$XXX",
NumberFormatter.with().unit(Currency.getInstance("XXX")).symbols(dfs),
ULocale.ENGLISH,
12.3,
@@ -1824,6 +2097,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Sign Auto Positive",
"sign-auto",
+ "",
NumberFormatter.with().sign(SignDisplay.AUTO),
ULocale.ENGLISH,
444444,
@@ -1832,6 +2106,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Sign Auto Negative",
"sign-auto",
+ "",
NumberFormatter.with().sign(SignDisplay.AUTO),
ULocale.ENGLISH,
-444444,
@@ -1840,6 +2115,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Sign Auto Zero",
"sign-auto",
+ "",
NumberFormatter.with().sign(SignDisplay.AUTO),
ULocale.ENGLISH,
0,
@@ -1848,6 +2124,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Sign Always Positive",
"sign-always",
+ "+!",
NumberFormatter.with().sign(SignDisplay.ALWAYS),
ULocale.ENGLISH,
444444,
@@ -1856,6 +2133,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Sign Always Negative",
"sign-always",
+ "+!",
NumberFormatter.with().sign(SignDisplay.ALWAYS),
ULocale.ENGLISH,
-444444,
@@ -1864,6 +2142,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Sign Always Zero",
"sign-always",
+ "+!",
NumberFormatter.with().sign(SignDisplay.ALWAYS),
ULocale.ENGLISH,
0,
@@ -1872,6 +2151,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Sign Never Positive",
"sign-never",
+ "+_",
NumberFormatter.with().sign(SignDisplay.NEVER),
ULocale.ENGLISH,
444444,
@@ -1880,6 +2160,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Sign Never Negative",
"sign-never",
+ "+_",
NumberFormatter.with().sign(SignDisplay.NEVER),
ULocale.ENGLISH,
-444444,
@@ -1888,6 +2169,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Sign Never Zero",
"sign-never",
+ "+_",
NumberFormatter.with().sign(SignDisplay.NEVER),
ULocale.ENGLISH,
0,
@@ -1896,6 +2178,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Sign Accounting Positive",
"currency/USD sign-accounting",
+ "currency/USD ()",
NumberFormatter.with().sign(SignDisplay.ACCOUNTING).unit(USD),
ULocale.ENGLISH,
444444,
@@ -1904,6 +2187,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Sign Accounting Negative",
"currency/USD sign-accounting",
+ "currency/USD ()",
NumberFormatter.with().sign(SignDisplay.ACCOUNTING).unit(USD),
ULocale.ENGLISH,
-444444,
@@ -1912,6 +2196,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Sign Accounting Zero",
"currency/USD sign-accounting",
+ "currency/USD ()",
NumberFormatter.with().sign(SignDisplay.ACCOUNTING).unit(USD),
ULocale.ENGLISH,
0,
@@ -1920,6 +2205,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Sign Accounting-Always Positive",
"currency/USD sign-accounting-always",
+ "currency/USD ()!",
NumberFormatter.with().sign(SignDisplay.ACCOUNTING_ALWAYS).unit(USD),
ULocale.ENGLISH,
444444,
@@ -1928,6 +2214,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Sign Accounting-Always Negative",
"currency/USD sign-accounting-always",
+ "currency/USD ()!",
NumberFormatter.with().sign(SignDisplay.ACCOUNTING_ALWAYS).unit(USD),
ULocale.ENGLISH,
-444444,
@@ -1936,6 +2223,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Sign Accounting-Always Zero",
"currency/USD sign-accounting-always",
+ "currency/USD ()!",
NumberFormatter.with().sign(SignDisplay.ACCOUNTING_ALWAYS).unit(USD),
ULocale.ENGLISH,
0,
@@ -1944,6 +2232,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Sign Except-Zero Positive",
"sign-except-zero",
+ "+?",
NumberFormatter.with().sign(SignDisplay.EXCEPT_ZERO),
ULocale.ENGLISH,
444444,
@@ -1952,6 +2241,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Sign Except-Zero Negative",
"sign-except-zero",
+ "+?",
NumberFormatter.with().sign(SignDisplay.EXCEPT_ZERO),
ULocale.ENGLISH,
-444444,
@@ -1960,6 +2250,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Sign Except-Zero Zero",
"sign-except-zero",
+ "+?",
NumberFormatter.with().sign(SignDisplay.EXCEPT_ZERO),
ULocale.ENGLISH,
0,
@@ -1968,6 +2259,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Sign Accounting-Except-Zero Positive",
"currency/USD sign-accounting-except-zero",
+ "currency/USD ()?",
NumberFormatter.with().sign(SignDisplay.ACCOUNTING_EXCEPT_ZERO).unit(USD),
ULocale.ENGLISH,
444444,
@@ -1976,6 +2268,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Sign Accounting-Except-Zero Negative",
"currency/USD sign-accounting-except-zero",
+ "currency/USD ()?",
NumberFormatter.with().sign(SignDisplay.ACCOUNTING_EXCEPT_ZERO).unit(USD),
ULocale.ENGLISH,
-444444,
@@ -1984,6 +2277,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Sign Accounting-Except-Zero Zero",
"currency/USD sign-accounting-except-zero",
+ "currency/USD ()?",
NumberFormatter.with().sign(SignDisplay.ACCOUNTING_EXCEPT_ZERO).unit(USD),
ULocale.ENGLISH,
0,
@@ -1992,6 +2286,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Sign Accounting Negative Hidden",
"currency/USD unit-width-hidden sign-accounting",
+ "currency/USD unit-width-hidden ()",
NumberFormatter.with().sign(SignDisplay.ACCOUNTING).unit(USD).unitWidth(UnitWidth.HIDDEN),
ULocale.ENGLISH,
-444444,
@@ -2000,6 +2295,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Sign Accounting Negative Narrow",
"currency/USD unit-width-narrow sign-accounting",
+ "currency/USD unit-width-narrow ()",
NumberFormatter.with().sign(SignDisplay.ACCOUNTING).unit(USD).unitWidth(UnitWidth.NARROW),
ULocale.CANADA,
-444444,
@@ -2008,6 +2304,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Sign Accounting Negative Short",
"currency/USD sign-accounting",
+ "currency/USD ()",
NumberFormatter.with().sign(SignDisplay.ACCOUNTING).unit(USD).unitWidth(UnitWidth.SHORT),
ULocale.CANADA,
-444444,
@@ -2016,6 +2313,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Sign Accounting Negative Iso Code",
"currency/USD unit-width-iso-code sign-accounting",
+ "currency/USD unit-width-iso-code ()",
NumberFormatter.with().sign(SignDisplay.ACCOUNTING).unit(USD).unitWidth(UnitWidth.ISO_CODE),
ULocale.CANADA,
-444444,
@@ -2026,6 +2324,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Sign Accounting Negative Full Name",
"currency/USD unit-width-full-name sign-accounting",
+ "currency/USD unit-width-full-name ()",
NumberFormatter.with().sign(SignDisplay.ACCOUNTING).unit(USD).unitWidth(UnitWidth.FULL_NAME),
ULocale.CANADA,
-444444,
@@ -2033,13 +2332,52 @@ public class NumberFormatterApiTest {
}
@Test
+ public void signNearZero() {
+ // https://unicode-org.atlassian.net/browse/ICU-20709
+ Object[][] cases = {
+ { SignDisplay.AUTO, 1.1, "1" },
+ { SignDisplay.AUTO, 0.9, "1" },
+ { SignDisplay.AUTO, 0.1, "0" },
+ { SignDisplay.AUTO, -0.1, "-0" }, // interesting case
+ { SignDisplay.AUTO, -0.9, "-1" },
+ { SignDisplay.AUTO, -1.1, "-1" },
+ { SignDisplay.ALWAYS, 1.1, "+1" },
+ { SignDisplay.ALWAYS, 0.9, "+1" },
+ { SignDisplay.ALWAYS, 0.1, "+0" },
+ { SignDisplay.ALWAYS, -0.1, "-0" },
+ { SignDisplay.ALWAYS, -0.9, "-1" },
+ { SignDisplay.ALWAYS, -1.1, "-1" },
+ { SignDisplay.EXCEPT_ZERO, 1.1, "+1" },
+ { SignDisplay.EXCEPT_ZERO, 0.9, "+1" },
+ { SignDisplay.EXCEPT_ZERO, 0.1, "0" }, // interesting case
+ { SignDisplay.EXCEPT_ZERO, -0.1, "0" }, // interesting case
+ { SignDisplay.EXCEPT_ZERO, -0.9, "-1" },
+ { SignDisplay.EXCEPT_ZERO, -1.1, "-1" },
+ };
+ for (Object[] cas : cases) {
+ SignDisplay sign = (SignDisplay) cas[0];
+ double input = (Double) cas[1];
+ String expected = (String) cas[2];
+ String actual = NumberFormatter.with()
+ .sign(sign)
+ .precision(Precision.integer())
+ .locale(Locale.US)
+ .format(input)
+ .toString();
+ assertEquals(
+ input + " @ SignDisplay " + sign,
+ expected, actual);
+ }
+ }
+
+ @Test
public void signCoverage() {
// https://unicode-org.atlassian.net/browse/ICU-20708
Object[][][] cases = new Object[][][] {
{ {SignDisplay.AUTO}, { "-∞", "-1", "-0", "0", "1", "∞", "NaN", "-NaN" } },
{ {SignDisplay.ALWAYS}, { "-∞", "-1", "-0", "+0", "+1", "+∞", "+NaN", "-NaN" } },
{ {SignDisplay.NEVER}, { "∞", "1", "0", "0", "1", "∞", "NaN", "NaN" } },
- { {SignDisplay.EXCEPT_ZERO}, { "-∞", "-1", "-0", "0", "+1", "+∞", "NaN", "-NaN" } },
+ { {SignDisplay.EXCEPT_ZERO}, { "-∞", "-1", "0", "0", "+1", "+∞", "NaN", "NaN" } },
};
double negNaN = Math.copySign(Double.NaN, -0.0);
double inputs[] = new double[] {
@@ -2067,6 +2405,7 @@ public class NumberFormatterApiTest {
assertFormatDescending(
"Decimal Default",
"decimal-auto",
+ "",
NumberFormatter.with().decimal(DecimalSeparatorDisplay.AUTO),
ULocale.ENGLISH,
"87,650",
@@ -2082,6 +2421,7 @@ public class NumberFormatterApiTest {
assertFormatDescending(
"Decimal Always Shown",
"decimal-always",
+ "decimal-always",
NumberFormatter.with().decimal(DecimalSeparatorDisplay.ALWAYS),
ULocale.ENGLISH,
"87,650.",
@@ -2100,6 +2440,7 @@ public class NumberFormatterApiTest {
assertFormatDescending(
"Multiplier None",
"scale/1",
+ "",
NumberFormatter.with().scale(Scale.none()),
ULocale.ENGLISH,
"87,650",
@@ -2115,6 +2456,7 @@ public class NumberFormatterApiTest {
assertFormatDescending(
"Multiplier Power of Ten",
"scale/1000000",
+ "scale/1000000",
NumberFormatter.with().scale(Scale.powerOfTen(6)),
ULocale.ENGLISH,
"87,650,000,000",
@@ -2130,6 +2472,7 @@ public class NumberFormatterApiTest {
assertFormatDescending(
"Multiplier Arbitrary Double",
"scale/5.2",
+ "scale/5.2",
NumberFormatter.with().scale(Scale.byDouble(5.2)),
ULocale.ENGLISH,
"455,780",
@@ -2145,6 +2488,7 @@ public class NumberFormatterApiTest {
assertFormatDescending(
"Multiplier Arbitrary BigDecimal",
"scale/5.2",
+ "scale/5.2",
NumberFormatter.with().scale(Scale.byBigDecimal(new BigDecimal("5.2"))),
ULocale.ENGLISH,
"455,780",
@@ -2160,6 +2504,7 @@ public class NumberFormatterApiTest {
assertFormatDescending(
"Multiplier Arbitrary Double And Power Of Ten",
"scale/5200",
+ "scale/5200",
NumberFormatter.with().scale(Scale.byDoubleAndPowerOfTen(5.2, 3)),
ULocale.ENGLISH,
"455,780,000",
@@ -2175,6 +2520,7 @@ public class NumberFormatterApiTest {
assertFormatDescending(
"Multiplier Zero",
"scale/0",
+ "scale/0",
NumberFormatter.with().scale(Scale.byDouble(0)),
ULocale.ENGLISH,
"0",
@@ -2190,6 +2536,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Multiplier Skeleton Scientific Notation and Percent",
"percent scale/1E2",
+ "%x100",
NumberFormatter.with().unit(NoUnit.PERCENT).scale(Scale.powerOfTen(2)),
ULocale.ENGLISH,
0.5,
@@ -2198,6 +2545,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Negative Multiplier",
"scale/-5.2",
+ "scale/-5.2",
NumberFormatter.with().scale(Scale.byDouble(-5.2)),
ULocale.ENGLISH,
2,
@@ -2206,6 +2554,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Negative One Multiplier",
"scale/-1",
+ "scale/-1",
NumberFormatter.with().scale(Scale.byDouble(-1)),
ULocale.ENGLISH,
444444,
@@ -2214,6 +2563,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Two-Type Multiplier with Overlap",
"scale/10000",
+ "scale/10000",
NumberFormatter.with().scale(Scale.byDoubleAndPowerOfTen(100, 2)),
ULocale.ENGLISH,
2,
@@ -2259,6 +2609,7 @@ public class NumberFormatterApiTest {
FormattedNumber fmtd = assertFormatSingle(
message,
"",
+ "",
NumberFormatter.with(),
ULocale.ENGLISH,
-9876543210.12,
@@ -2276,9 +2627,10 @@ public class NumberFormatterApiTest {
assertNumberFieldPositions(message, fmtd, expectedFieldPositions);
// Test the iteration functionality of nextFieldPosition
- FieldPosition actual = new FieldPosition(NumberFormat.Field.GROUPING_SEPARATOR);
+ ConstrainedFieldPosition actual = new ConstrainedFieldPosition();
+ actual.constrainField(NumberFormat.Field.GROUPING_SEPARATOR);
int i = 1;
- while (fmtd.nextFieldPosition(actual)) {
+ while (fmtd.nextPosition(actual)) {
Object[] cas = expectedFieldPositions[i++];
NumberFormat.Field expectedField = (NumberFormat.Field) cas[0];
int expectedBeginIndex = (Integer) cas[1];
@@ -2287,22 +2639,23 @@ public class NumberFormatterApiTest {
assertEquals(
"Next for grouping, field, case #" + i,
expectedField,
- actual.getFieldAttribute());
+ actual.getField());
assertEquals(
"Next for grouping, begin index, case #" + i,
expectedBeginIndex,
- actual.getBeginIndex());
+ actual.getStart());
assertEquals(
"Next for grouping, end index, case #" + i,
expectedEndIndex,
- actual.getEndIndex());
+ actual.getLimit());
}
assertEquals("Should have seen all grouping separators", 4, i);
// Make sure strings without fraction do not contain fraction field
- actual = new FieldPosition(NumberFormat.Field.FRACTION);
+ actual.reset();
+ actual.constrainField(NumberFormat.Field.FRACTION);
fmtd = NumberFormatter.withLocale(ULocale.ENGLISH).format(5);
- assertFalse("No fraction part in an integer", fmtd.nextFieldPosition(actual));
+ assertFalse("No fraction part in an integer", fmtd.nextPosition(actual));
}
@Test
@@ -2312,6 +2665,7 @@ public class NumberFormatterApiTest {
FormattedNumber result = assertFormatSingle(
message,
"measure-unit/temperature-fahrenheit",
+ "unit/fahrenheit",
NumberFormatter.with().unit(MeasureUnit.FAHRENHEIT),
ULocale.ENGLISH,
68,
@@ -2331,6 +2685,7 @@ public class NumberFormatterApiTest {
FormattedNumber result = assertFormatSingle(
message,
"measure-unit/temperature-fahrenheit per-measure-unit/duration-day",
+ "unit/fahrenheit-per-day",
NumberFormatter.with().unit(MeasureUnit.FAHRENHEIT).perUnit(MeasureUnit.DAY),
ULocale.ENGLISH,
68,
@@ -2350,6 +2705,7 @@ public class NumberFormatterApiTest {
FormattedNumber result = assertFormatSingle(
message,
"measure-unit/length-meter unit-width-full-name",
+ "unit/meter unit-width-full-name",
NumberFormatter.with().unit(MeasureUnit.METER).unitWidth(UnitWidth.FULL_NAME),
ULocale.ENGLISH,
68,
@@ -2370,6 +2726,7 @@ public class NumberFormatterApiTest {
FormattedNumber result = assertFormatSingle(
message,
"measure-unit/length-meter per-measure-unit/duration-second unit-width-full-name",
+ "~unit/meter-per-second unit-width-full-name", // does not round-trip to the full skeleton above
NumberFormatter.with().unit(MeasureUnit.METER).perUnit(MeasureUnit.SECOND).unitWidth(UnitWidth.FULL_NAME),
new ULocale("ky"), // locale with the interesting data
68,
@@ -2390,6 +2747,7 @@ public class NumberFormatterApiTest {
FormattedNumber result = assertFormatSingle(
message,
"measure-unit/temperature-fahrenheit unit-width-full-name",
+ "unit/fahrenheit unit-width-full-name",
NumberFormatter.with().unit(MeasureUnit.FAHRENHEIT).unitWidth(UnitWidth.FULL_NAME),
new ULocale("vi"), // locale with the interesting data
68,
@@ -2413,6 +2771,7 @@ public class NumberFormatterApiTest {
FormattedNumber result = assertFormatSingle(
message,
"measure-unit/temperature-kelvin",
+ "unit/kelvin",
NumberFormatter.with().unit(MeasureUnit.KELVIN),
new ULocale("fa"), // locale with the interesting data
68,
@@ -2432,6 +2791,7 @@ public class NumberFormatterApiTest {
FormattedNumber result = assertFormatSingle(
message,
"compact-short",
+ "K",
NumberFormatter.with().notation(Notation.compactShort()),
ULocale.US,
65000,
@@ -2451,6 +2811,7 @@ public class NumberFormatterApiTest {
FormattedNumber result = assertFormatSingle(
message,
"compact-long",
+ "KK",
NumberFormatter.with().notation(Notation.compactLong()),
ULocale.US,
65000,
@@ -2470,6 +2831,7 @@ public class NumberFormatterApiTest {
FormattedNumber result = assertFormatSingle(
message,
"compact-long",
+ "KK",
NumberFormatter.with().notation(Notation.compactLong()),
new ULocale("fil"), // locale with interesting data
6000,
@@ -2489,6 +2851,7 @@ public class NumberFormatterApiTest {
FormattedNumber result = assertFormatSingle(
message,
"compact-long",
+ "KK",
NumberFormatter.with().notation(Notation.compactLong()),
new ULocale("he"), // locale with interesting data
6000,
@@ -2508,6 +2871,7 @@ public class NumberFormatterApiTest {
FormattedNumber result = assertFormatSingle(
message,
"compact-short currency/USD",
+ "K currency/USD",
NumberFormatter.with().notation(Notation.compactShort()).unit(USD),
new ULocale("sr_Latn"), // locale with interesting data
65000,
@@ -2528,6 +2892,7 @@ public class NumberFormatterApiTest {
FormattedNumber result = assertFormatSingle(
message,
"currency/USD unit-width-full-name",
+ "currency/USD unit-width-full-name",
NumberFormatter.with().unit(USD)
.unitWidth(UnitWidth.FULL_NAME),
ULocale.ENGLISH,
@@ -2551,6 +2916,7 @@ public class NumberFormatterApiTest {
FormattedNumber result = assertFormatSingle(
message,
"compact-long measure-unit/length-meter unit-width-full-name",
+ "KK unit/meter unit-width-full-name",
NumberFormatter.with().notation(Notation.compactLong())
.unit(MeasureUnit.METER)
.unitWidth(UnitWidth.FULL_NAME),
@@ -2617,6 +2983,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Plural 1",
"currency/USD precision-integer unit-width-full-name",
+ "currency/USD . unit-width-full-name",
NumberFormatter.with().unit(USD).unitWidth(UnitWidth.FULL_NAME).precision(Precision.fixedFraction(0)),
ULocale.ENGLISH,
1,
@@ -2625,6 +2992,7 @@ public class NumberFormatterApiTest {
assertFormatSingle(
"Plural 1.00",
"currency/USD .00 unit-width-full-name",
+ "currency/USD .00 unit-width-full-name",
NumberFormatter.with().unit(USD).unitWidth(UnitWidth.FULL_NAME).precision(Precision.fixedFraction(2)),
ULocale.ENGLISH,
1,
@@ -2748,26 +3116,29 @@ public class NumberFormatterApiTest {
static void assertFormatDescending(
String message,
String skeleton,
+ String conciseSkeleton,
UnlocalizedNumberFormatter f,
ULocale locale,
String... expected) {
final double[] inputs = new double[] { 87650, 8765, 876.5, 87.65, 8.765, 0.8765, 0.08765, 0.008765, 0 };
- assertFormatDescending(message, skeleton, f, locale, inputs, expected);
+ assertFormatDescending(message, skeleton, conciseSkeleton, f, locale, inputs, expected);
}
static void assertFormatDescendingBig(
String message,
String skeleton,
+ String conciseSkeleton,
UnlocalizedNumberFormatter f,
ULocale locale,
String... expected) {
final double[] inputs = new double[] { 87650000, 8765000, 876500, 87650, 8765, 876.5, 87.65, 8.765, 0 };
- assertFormatDescending(message, skeleton, f, locale, inputs, expected);
+ assertFormatDescending(message, skeleton, conciseSkeleton, f, locale, inputs, expected);
}
static void assertFormatDescending(
String message,
String skeleton,
+ String conciseSkeleton,
UnlocalizedNumberFormatter f,
ULocale locale,
double[] inputs,
@@ -2793,6 +3164,22 @@ public class NumberFormatterApiTest {
String actual3 = l3.format(d).toString();
assertEquals(message + ": Skeleton Path: " + d, expected[i], actual3);
}
+ // Concise skeletons should have same output, and usually round-trip to the normalized skeleton.
+ // If the concise skeleton starts with '~', disable the round-trip check.
+ boolean shouldRoundTrip = true;
+ if (conciseSkeleton.length() > 0 && conciseSkeleton.charAt(0) == '~') {
+ conciseSkeleton = conciseSkeleton.substring(1);
+ shouldRoundTrip = false;
+ }
+ LocalizedNumberFormatter l4 = NumberFormatter.forSkeleton(conciseSkeleton).locale(locale);
+ if (shouldRoundTrip) {
+ assertEquals(message + ": Concise Skeleton:", normalized, l4.toSkeleton());
+ }
+ for (int i = 0; i < 9; i++) {
+ double d = inputs[i];
+ String actual4 = l4.format(d).toString();
+ assertEquals(message + ": Concise Skeleton Path: '" + normalized + "': " + d, expected[i], actual4);
+ }
} else {
assertUndefinedSkeleton(f);
}
@@ -2801,6 +3188,7 @@ public class NumberFormatterApiTest {
static FormattedNumber assertFormatSingle(
String message,
String skeleton,
+ String conciseSkeleton,
UnlocalizedNumberFormatter f,
ULocale locale,
Number input,
@@ -2820,6 +3208,19 @@ public class NumberFormatterApiTest {
LocalizedNumberFormatter l3 = NumberFormatter.forSkeleton(normalized).locale(locale);
String actual3 = l3.format(input).toString();
assertEquals(message + ": Skeleton Path: " + input, expected, actual3);
+ // Concise skeletons should have same output, and usually round-trip to the normalized skeleton.
+ // If the concise skeleton starts with '~', disable the round-trip check.
+ boolean shouldRoundTrip = true;
+ if (conciseSkeleton.length() > 0 && conciseSkeleton.charAt(0) == '~') {
+ conciseSkeleton = conciseSkeleton.substring(1);
+ shouldRoundTrip = false;
+ }
+ LocalizedNumberFormatter l4 = NumberFormatter.forSkeleton(conciseSkeleton).locale(locale);
+ if (shouldRoundTrip) {
+ assertEquals(message + ": Concise Skeleton:", normalized, l4.toSkeleton());
+ }
+ String actual4 = l4.format(input).toString();
+ assertEquals(message + ": Concise Skeleton Path: '" + normalized + "': " + input, expected, actual4);
} else {
assertUndefinedSkeleton(f);
}
@@ -2829,6 +3230,7 @@ public class NumberFormatterApiTest {
static void assertFormatSingleMeasure(
String message,
String skeleton,
+ String conciseSkeleton,
UnlocalizedNumberFormatter f,
ULocale locale,
Measure input,
@@ -2847,6 +3249,19 @@ public class NumberFormatterApiTest {
LocalizedNumberFormatter l3 = NumberFormatter.forSkeleton(normalized).locale(locale);
String actual3 = l3.format(input).toString();
assertEquals(message + ": Skeleton Path: " + input, expected, actual3);
+ // Concise skeletons should have same output, and usually round-trip to the normalized skeleton.
+ // If the concise skeleton starts with '~', disable the round-trip check.
+ boolean shouldRoundTrip = true;
+ if (conciseSkeleton.length() > 0 && conciseSkeleton.charAt(0) == '~') {
+ conciseSkeleton = conciseSkeleton.substring(1);
+ shouldRoundTrip = false;
+ }
+ LocalizedNumberFormatter l4 = NumberFormatter.forSkeleton(conciseSkeleton).locale(locale);
+ if (shouldRoundTrip) {
+ assertEquals(message + ": Concise Skeleton:", normalized, l4.toSkeleton());
+ }
+ String actual4 = l4.format(input).toString();
+ assertEquals(message + ": Concise Skeleton Path: '" + normalized + "': " + input, expected, actual4);
} else {
assertUndefinedSkeleton(f);
}
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/number/NumberPermutationTest.java b/android_icu4j/src/main/tests/android/icu/dev/test/number/NumberPermutationTest.java
index c932149ec..31999aafd 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/number/NumberPermutationTest.java
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/number/NumberPermutationTest.java
@@ -99,7 +99,7 @@ public class NumberPermutationTest extends TestFmwk {
// Build up the golden data string as we evaluate all permutations
ArrayList<String> resultLines = new ArrayList<>();
- resultLines.add("# © 2017 and later: Unicode, Inc. and others.");
+ resultLines.add("# © 2019 and later: Unicode, Inc. and others.");
resultLines.add("# License & terms of use: http://www.unicode.org/copyright.html");
resultLines.add("");
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/number/NumberSkeletonTest.java b/android_icu4j/src/main/tests/android/icu/dev/test/number/NumberSkeletonTest.java
index 1a2a913c4..71825e85c 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/number/NumberSkeletonTest.java
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/number/NumberSkeletonTest.java
@@ -31,26 +31,35 @@ public class NumberSkeletonTest {
"precision-integer",
"precision-unlimited",
"@@@##",
+ "@@*",
"@@+",
".000##",
+ ".00*",
".00+",
".",
+ ".*",
".+",
".######",
+ ".00/@@*",
".00/@@+",
".00/@##",
"precision-increment/3.14",
"precision-currency-standard",
"precision-integer rounding-mode-half-up",
".00# rounding-mode-ceiling",
+ ".00/@@* rounding-mode-floor",
".00/@@+ rounding-mode-floor",
"scientific",
+ "scientific/*ee",
"scientific/+ee",
"scientific/sign-always",
+ "scientific/*ee/sign-always",
"scientific/+ee/sign-always",
+ "scientific/sign-always/*ee",
"scientific/sign-always/+ee",
"scientific/sign-except-zero",
"engineering",
+ "engineering/*eee",
"engineering/+eee",
"compact-short",
"compact-long",
@@ -60,6 +69,7 @@ public class NumberSkeletonTest {
"measure-unit/length-meter",
"measure-unit/area-square-meter",
"measure-unit/energy-joule per-measure-unit/length-meter",
+ "unit/square-meter-per-square-meter",
"currency/XXX",
"currency/ZZZ",
"currency/usd",
@@ -70,6 +80,7 @@ public class NumberSkeletonTest {
"group-thousands",
"integer-width/00",
"integer-width/#0",
+ "integer-width/*00",
"integer-width/+00",
"sign-always",
"sign-auto",
@@ -95,7 +106,20 @@ public class NumberSkeletonTest {
"numbering-system/latn",
"precision-integer/@##",
"precision-integer rounding-mode-ceiling",
- "precision-currency-cash rounding-mode-ceiling" };
+ "precision-currency-cash rounding-mode-ceiling",
+ "0",
+ "00",
+ "000",
+ "E0",
+ "E00",
+ "E000",
+ "EE0",
+ "EE00",
+ "EE+?0",
+ "EE+?00",
+ "EE+!0",
+ "EE+!00",
+ };
for (String cas : cases) {
try {
@@ -111,16 +135,22 @@ public class NumberSkeletonTest {
String[] cases = {
".00x",
".00##0",
+ ".##*",
+ ".00##*",
+ ".0#*",
+ "@#*",
".##+",
".00##+",
".0#+",
+ "@#+",
"@@x",
"@@##0",
- "@#+",
".00/@",
".00/@@",
".00/@@x",
".00/@@#",
+ ".00/@@#*",
+ ".00/floor/@@*", // wrong order
".00/@@#+",
".00/floor/@@+", // wrong order
"precision-increment/français", // non-invariant characters for C++
@@ -136,11 +166,29 @@ public class NumberSkeletonTest {
"currency/ççç", // three characters but not ASCII
"measure-unit/foo",
"integer-width/xxx",
+ "integer-width/0*",
+ "integer-width/*0#",
+ "integer-width/*#",
+ "integer-width/*#0",
"integer-width/0+",
"integer-width/+0#",
"integer-width/+#",
"integer-width/+#0",
- "scientific/foo" };
+ "scientific/foo",
+ "E",
+ "E1",
+ "E+",
+ "E+?",
+ "E+!",
+ "E+0",
+ "EE",
+ "EE+",
+ "EEE",
+ "EEE0",
+ "001",
+ "00*",
+ "00+",
+ };
for (String cas : cases) {
try {
@@ -273,6 +321,26 @@ public class NumberSkeletonTest {
}
@Test
+ public void wildcardCharacters() {
+ String[][] cases = {
+ { ".00*", ".00+" },
+ { "@@*", "@@+" },
+ { ".00/@@*", ".00/@@+" },
+ { "scientific/*ee", "scientific/+ee" },
+ { "integer-width/*00", "integer-width/+00" },
+ };
+
+ for (String[] cas : cases) {
+ String star = cas[0];
+ String plus = cas[1];
+
+ String normalized = NumberFormatter.forSkeleton(plus)
+ .toSkeleton();
+ assertEquals("Plus should normalize to star", star, normalized);
+ }
+ }
+
+ @Test
public void roundingModeNames() {
for (RoundingMode mode : RoundingMode.values()) {
if (mode == RoundingMode.HALF_EVEN) {
@@ -284,4 +352,65 @@ public class NumberSkeletonTest {
assertEquals(mode.toString(), modeString, skeleton.substring(14));
}
}
+
+ @Test
+ public void perUnitInArabic() {
+ String[][] cases = {
+ {"area", "acre"},
+ {"digital", "bit"},
+ {"digital", "byte"},
+ {"temperature", "celsius"},
+ {"length", "centimeter"},
+ {"duration", "day"},
+ {"angle", "degree"},
+ {"temperature", "fahrenheit"},
+ {"volume", "fluid-ounce"},
+ {"length", "foot"},
+ {"volume", "gallon"},
+ {"digital", "gigabit"},
+ {"digital", "gigabyte"},
+ {"mass", "gram"},
+ {"area", "hectare"},
+ {"duration", "hour"},
+ {"length", "inch"},
+ {"digital", "kilobit"},
+ {"digital", "kilobyte"},
+ {"mass", "kilogram"},
+ {"length", "kilometer"},
+ {"volume", "liter"},
+ {"digital", "megabit"},
+ {"digital", "megabyte"},
+ {"length", "meter"},
+ {"length", "mile"},
+ {"length", "mile-scandinavian"},
+ {"volume", "milliliter"},
+ {"length", "millimeter"},
+ {"duration", "millisecond"},
+ {"duration", "minute"},
+ {"duration", "month"},
+ {"mass", "ounce"},
+ {"concentr", "percent"},
+ {"digital", "petabyte"},
+ {"mass", "pound"},
+ {"duration", "second"},
+ {"mass", "stone"},
+ {"digital", "terabit"},
+ {"digital", "terabyte"},
+ {"duration", "week"},
+ {"length", "yard"},
+ {"duration", "year"},
+ };
+
+ ULocale arabic = new ULocale("ar");
+ for (String[] cas1 : cases) {
+ for (String[] cas2 : cases) {
+ String skeleton = "measure-unit/";
+ skeleton += cas1[0] + "-" + cas1[1] + " per-measure-unit/" + cas2[0] + "-" + cas2[1];
+
+ String actual = NumberFormatter.forSkeleton(skeleton).locale(arabic).format(5142.3)
+ .toString();
+ // Just make sure it won't throw exception
+ }
+ }
+ }
}
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/rbbi/RBBITestMonkey.java b/android_icu4j/src/main/tests/android/icu/dev/test/rbbi/RBBITestMonkey.java
index a9323ff57..5246ac862 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/rbbi/RBBITestMonkey.java
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/rbbi/RBBITestMonkey.java
@@ -57,6 +57,11 @@ public class RBBITestMonkey extends TestFmwk {
// testing, but works purely in terms of the interface defined here.
//
abstract static class RBBIMonkeyKind {
+ RBBIMonkeyKind() {
+ fSets = new ArrayList();
+ fClassNames = new ArrayList();
+ fAppliedRules = new ArrayList();
+ }
// Return a List of UnicodeSets, representing the character classes used
// for this type of iterator.
@@ -69,10 +74,59 @@ public class RBBITestMonkey extends TestFmwk {
// Return -1 after reaching end of string.
abstract int next(int i);
+ // Name of each character class, parallel with charClasses. Used for debugging output
+ // of characters.
+ List<String> characterClassNames() {
+ return fClassNames;
+ }
+
+ void setAppliedRule(int position, String value) {
+ fAppliedRules.set(position, value);
+ }
+
+ String getAppliedRule(int position) {
+ return fAppliedRules.get(position);
+ }
+
+ String classNameFromCodepoint(int c) {
+ // Simply iterate through fSets to find character's class
+ for (int aClassNum = 0; aClassNum < charClasses().size(); aClassNum++) {
+ UnicodeSet classSet = (UnicodeSet)charClasses().get(aClassNum);
+ if (classSet.contains(c)) {
+ return fClassNames.get(aClassNum);
+ }
+ }
+ return "bad class name";
+ }
+
+ int maxClassNameSize() {
+ int maxSize = 0;
+ for (int aClassNum = 0; aClassNum < charClasses().size(); aClassNum++) {
+ if (fClassNames.get(aClassNum).length() > maxSize) {
+ maxSize = fClassNames.get(aClassNum).length();
+ }
+ }
+ return maxSize;
+ }
+
+ // Clear `appliedRules` and fill it with empty strings in the size of test text.
+ void prepareAppliedRules(int size) {
+ // Remove all the information in the `appliedRules`.
+ fAppliedRules.clear();
+ fAppliedRules.ensureCapacity(size + 1);
+ while (fAppliedRules.size() < size + 1) {
+ fAppliedRules.add("");
+ }
+ }
+
// A Character Property, one of the constants defined in class UProperty.
// The value of this property will be displayed for the characters
// near any test failure.
int fCharProperty;
+
+ List fSets;
+ ArrayList<String> fClassNames;
+ ArrayList<String> fAppliedRules;
}
/**
@@ -80,8 +134,6 @@ public class RBBITestMonkey extends TestFmwk {
* Note: As of Unicode 6.1, fPrependSet is empty, so don't add it to fSets
*/
static class RBBICharMonkey extends RBBIMonkeyKind {
- List fSets;
-
UnicodeSet fCRLFSet;
UnicodeSet fControlSet;
UnicodeSet fExtendSet;
@@ -104,7 +156,6 @@ public class RBBITestMonkey extends TestFmwk {
StringBuffer fText;
-
RBBICharMonkey() {
fText = null;
fCharProperty = UProperty.GRAPHEME_CLUSTER_BREAK;
@@ -136,28 +187,28 @@ public class RBBITestMonkey extends TestFmwk {
fAnySet = new UnicodeSet("[\\u0000-\\U0010ffff]");
- fSets = new ArrayList();
- fSets.add(fCRLFSet);
- fSets.add(fControlSet);
- fSets.add(fExtendSet);
- fSets.add(fRegionalIndicatorSet);
+ fSets.add(fCRLFSet); fClassNames.add("CRLF");
+ fSets.add(fControlSet); fClassNames.add("Control");
+ fSets.add(fExtendSet); fClassNames.add("Extended");
+ fSets.add(fRegionalIndicatorSet); fClassNames.add("RegionalIndicator");
if (!fPrependSet.isEmpty()) {
- fSets.add(fPrependSet);
+ fSets.add(fPrependSet); fClassNames.add("Prepend");
}
- fSets.add(fSpacingSet);
- fSets.add(fHangulSet);
- fSets.add(fAnySet);
- fSets.add(fZWJSet);
- fSets.add(fExtendedPictSet);
- fSets.add(fViramaSet);
- fSets.add(fLinkingConsonantSet);
- fSets.add(fExtCccZwjSet);
+ fSets.add(fSpacingSet); fClassNames.add("Spacing");
+ fSets.add(fHangulSet); fClassNames.add("Hangul");
+ fSets.add(fAnySet); fClassNames.add("Any");
+ fSets.add(fZWJSet); fClassNames.add("ZWJ");
+ fSets.add(fExtendedPictSet); fClassNames.add("ExtendedPict");
+ fSets.add(fViramaSet); fClassNames.add("Virama");
+ fSets.add(fLinkingConsonantSet); fClassNames.add("LinkingConsonant");
+ fSets.add(fExtCccZwjSet); fClassNames.add("ExtCccZwj");
}
@Override
void setText(StringBuffer s) {
fText = s;
+ prepareAppliedRules(s.length());
}
@Override
@@ -200,74 +251,72 @@ public class RBBITestMonkey extends TestFmwk {
continue;
}
if (p2 == fText.length()) {
- // Reached end of string. Always a break position.
+ setAppliedRule(p2, "End of String");
break;
}
- // Rule GB3 CR x LF
// No Extend or Format characters may appear between the CR and LF,
// which requires the additional check for p2 immediately following p1.
//
if (c1==0x0D && c2==0x0A && p1==(p2-1)) {
+ setAppliedRule(p2, "GB 3 CR x LF");
continue;
}
- // Rule (GB4). ( Control | CR | LF ) <break>
if (fControlSet.contains(c1) ||
c1 == 0x0D ||
c1 == 0x0A) {
+ setAppliedRule(p2, "GB 4 ( Control | CR | LF ) <break>");
break;
}
- // Rule (GB5) <break> ( Control | CR | LF )
- //
if (fControlSet.contains(c2) ||
c2 == 0x0D ||
c2 == 0x0A) {
+ setAppliedRule(p2, "GB 5 <break> ( Control | CR | LF )");
break;
}
- // Rule (GB6) L x ( L | V | LV | LVT )
if (fLSet.contains(c1) &&
(fLSet.contains(c2) ||
fVSet.contains(c2) ||
fLVSet.contains(c2) ||
fLVTSet.contains(c2))) {
+ setAppliedRule(p2, "GB 6 L x ( L | V | LV | LVT )");
continue;
}
- // Rule (GB7) ( LV | V ) x ( V | T )
if ((fLVSet.contains(c1) || fVSet.contains(c1)) &&
(fVSet.contains(c2) || fTSet.contains(c2))) {
+ setAppliedRule(p2, "GB 7 ( LV | V ) x ( V | T )");
continue;
}
- // Rule (GB8) ( LVT | T) x T
if ((fLVTSet.contains(c1) || fTSet.contains(c1)) &&
fTSet.contains(c2)) {
+ setAppliedRule(p2, "GB 8 ( LVT | T) x T");
continue;
}
- // Rule (GB9) x (Extend | ZWJ)
if (fExtendSet.contains(c2) || fZWJSet.contains(c2)) {
if (!fExtendSet.contains(c1)) {
cBase = c1;
}
+ setAppliedRule(p2, "GB 9 x (Extend | ZWJ)");
continue;
}
- // Rule (GB9a) x SpacingMark
if (fSpacingSet.contains(c2)) {
+ setAppliedRule(p2, "GB 9a x SpacingMark");
continue;
}
- // Rule (GB9b) Prepend x
if (fPrependSet.contains(c1)) {
+ setAppliedRule(p2, "GB 9b Prepend x");
continue;
}
- // Rule (GB9.3) LinkingConsonant ExtCccZwj* Virama ExtCccZwj* × LinkingConsonant
// Note: Viramas are also included in the ExtCccZwj class.
if (fLinkingConsonantSet.contains(c2)) {
int pi = p1;
@@ -279,37 +328,39 @@ public class RBBITestMonkey extends TestFmwk {
pi = fText.offsetByCodePoints(pi, -1);
}
if (sawVirama && fLinkingConsonantSet.contains(fText.codePointAt(pi))) {
+ setAppliedRule(p2, "GB 9.3 LinkingConsonant ExtCccZwj* Virama ExtCccZwj* × LinkingConsonant");
continue;
}
}
- // Rule (GB11) Extended_Pictographic ZWJ x Extended_Pictographic
if (fExtendedPictSet.contains(cBase) && fZWJSet.contains(c1) && fExtendedPictSet.contains(c2) ) {
+ setAppliedRule(p2, "GB 11 Extended_Pictographic Extend * ZWJ x Extended_Pictographic");
continue;
}
- // Rule (GB12-13) Regional_Indicator x Regional_Indicator
// Note: The first if condition is a little tricky. We only need to force
// a break if there are three or more contiguous RIs. If there are
// only two, a break following will occur via other rules, and will include
// any trailing extend characters, which is needed behavior.
if (fRegionalIndicatorSet.contains(c0) && fRegionalIndicatorSet.contains(c1)
&& fRegionalIndicatorSet.contains(c2)) {
+ setAppliedRule(p2, "GB 12-13 Regional_Indicator x Regional_Indicator");
break;
}
if (fRegionalIndicatorSet.contains(c1) && fRegionalIndicatorSet.contains(c2)) {
+ setAppliedRule(p2, "GB 12-13 Regional_Indicator x Regional_Indicator");
continue;
}
- // Rule (GB999) Any <break> Any
+ setAppliedRule(p2, "GB 999 Any <break> Any");
break;
}
breakPos = p2;
return breakPos;
}
- }
+ }
/**
*
@@ -319,7 +370,6 @@ public class RBBITestMonkey extends TestFmwk {
*
*/
static class RBBIWordMonkey extends RBBIMonkeyKind {
- List fSets;
StringBuffer fText;
UnicodeSet fCRSet;
@@ -344,7 +394,6 @@ public class RBBITestMonkey extends TestFmwk {
UnicodeSet fZWJSet;
UnicodeSet fExtendedPictSet;
-
RBBIWordMonkey() {
fCharProperty = UProperty.WORD_BREAK;
@@ -405,30 +454,28 @@ public class RBBITestMonkey extends TestFmwk {
fOtherSet.removeAll(new UnicodeSet("[[\\p{LineBreak = Complex_Context}][:Line_Break=Surrogate:]]"));
fOtherSet.removeAll(fDictionarySet);
- fSets = new ArrayList();
- fSets.add(fCRSet);
- fSets.add(fLFSet);
- fSets.add(fNewlineSet);
- fSets.add(fRegionalIndicatorSet);
- fSets.add(fHebrew_LetterSet);
- fSets.add(fALetterSet);
+ fSets.add(fCRSet); fClassNames.add("CR");
+ fSets.add(fLFSet); fClassNames.add("LF");
+ fSets.add(fNewlineSet); fClassNames.add("Newline");
+ fSets.add(fRegionalIndicatorSet); fClassNames.add("RegionalIndicator");
+ fSets.add(fHebrew_LetterSet); fClassNames.add("Hebrew");
+ fSets.add(fALetterSet); fClassNames.add("ALetter");
//fSets.add(fKatakanaSet); // Omit Katakana from fSets, which omits Katakana characters
// from the test data. They are all in the dictionary set,
// which this (old, to be retired) monkey test cannot handle.
- fSets.add(fSingle_QuoteSet);
- fSets.add(fDouble_QuoteSet);
- fSets.add(fMidLetterSet);
- fSets.add(fMidNumLetSet);
- fSets.add(fMidNumSet);
- fSets.add(fNumericSet);
- fSets.add(fFormatSet);
- fSets.add(fExtendSet);
- fSets.add(fExtendNumLetSet);
- fSets.add(fWSegSpaceSet);
- fSets.add(fRegionalIndicatorSet);
- fSets.add(fZWJSet);
- fSets.add(fExtendedPictSet);
- fSets.add(fOtherSet);
+ fSets.add(fSingle_QuoteSet); fClassNames.add("Single Quote");
+ fSets.add(fDouble_QuoteSet); fClassNames.add("Double Quote");
+ fSets.add(fMidLetterSet); fClassNames.add("MidLetter");
+ fSets.add(fMidNumLetSet); fClassNames.add("MidNumLet");
+ fSets.add(fMidNumSet); fClassNames.add("MidNum");
+ fSets.add(fNumericSet); fClassNames.add("Numeric");
+ fSets.add(fFormatSet); fClassNames.add("Format");
+ fSets.add(fExtendSet); fClassNames.add("Extend");
+ fSets.add(fExtendNumLetSet); fClassNames.add("ExtendNumLet");
+ fSets.add(fWSegSpaceSet); fClassNames.add("WSegSpace");
+ fSets.add(fZWJSet); fClassNames.add("ZWJ");
+ fSets.add(fExtendedPictSet); fClassNames.add("ExtendedPict");
+ fSets.add(fOtherSet); fClassNames.add("Other");
}
@@ -440,6 +487,7 @@ public class RBBITestMonkey extends TestFmwk {
@Override
void setText(StringBuffer s) {
fText = s;
+ prepareAppliedRules(s.length());
}
@Override
@@ -492,149 +540,146 @@ public class RBBITestMonkey extends TestFmwk {
break;
}
- // Rule (3) CR x LF
// No Extend or Format characters may appear between the CR and LF,
// which requires the additional check for p2 immediately following p1.
//
if (c1==0x0D && c2==0x0A) {
+ setAppliedRule(p2, "WB 3 CR x LF");
continue;
}
- // Rule (3a) Break before and after newlines (including CR and LF)
//
if (fCRSet.contains(c1) || fLFSet.contains(c1) || fNewlineSet.contains(c1)) {
+ setAppliedRule(p2, "WB 3a Break before and after newlines (including CR and LF)");
break;
}
if (fCRSet.contains(c2) || fLFSet.contains(c2) || fNewlineSet.contains(c2)) {
+ setAppliedRule(p2, "WB 3a Break before and after newlines (including CR and LF)");
break;
}
- // Rule (3c) ZWJ x Extended_Pictographic.
// Not ignoring extend chars, so peek into input text to
// get the potential ZWJ, the character immediately preceding c2.
if (fZWJSet.contains(fText.codePointBefore(p2)) && fExtendedPictSet.contains(c2)) {
+ setAppliedRule(p2, "WB 3c ZWJ x Extended_Pictographic");
continue;
}
- // Rule (3d) Keep horizontal whitespace together.
if (fWSegSpaceSet.contains(fText.codePointBefore(p2)) && fWSegSpaceSet.contains(c2)) {
+ setAppliedRule(p2, "WB 3d Keep horizontal whitespace together");
continue;
}
- // Rule (5). (ALetter | Hebrew_Letter) x (ALetter | Hebrew_Letter)
if ((fALetterSet.contains(c1) || fHebrew_LetterSet.contains(c1)) &&
(fALetterSet.contains(c2) || fHebrew_LetterSet.contains(c2))) {
+ setAppliedRule(p2, "WB 4 (ALetter | Hebrew_Letter) x (ALetter | Hebrew_Letter)");
continue;
}
- // Rule (6) (ALetter | Hebrew_Letter) x (MidLetter | MidNumLet | Single_Quote) (ALetter | Hebrew_Letter)
- //
if ( (fALetterSet.contains(c1) || fHebrew_LetterSet.contains(c1)) &&
(fMidLetterSet.contains(c2) || fMidNumLetSet.contains(c2) || fSingle_QuoteSet.contains(c2)) &&
(setContains(fALetterSet, c3) || setContains(fHebrew_LetterSet, c3))) {
+ setAppliedRule(p2, "WB 6 (ALetter | Hebrew_Letter) x (MidLetter | MidNumLet | Single_Quote) (ALetter | Hebrew_Letter)");
continue;
}
- // Rule (7) (ALetter | Hebrew_Letter) (MidLetter | MidNumLet | Single_Quote) x (ALetter | Hebrew_Letter)
if ((fALetterSet.contains(c0) || fHebrew_LetterSet.contains(c0)) &&
(fMidLetterSet.contains(c1) || fMidNumLetSet.contains(c1) || fSingle_QuoteSet.contains(c1)) &&
(fALetterSet.contains(c2) || fHebrew_LetterSet.contains(c2))) {
+ setAppliedRule(p2, "WB 7 (ALetter | Hebrew_Letter) (MidLetter | MidNumLet | Single_Quote) x (ALetter | Hebrew_Letter)");
continue;
}
- // Rule (7a) Hebrew_Letter x Single_Quote
if (fHebrew_LetterSet.contains(c1) && fSingle_QuoteSet.contains(c2)) {
+ setAppliedRule(p2, "WB 7a Hebrew_Letter x Single_Quote");
continue;
}
- // Rule (7b) Hebrew_Letter x Double_Quote Hebrew_Letter
if (fHebrew_LetterSet.contains(c1) && fDouble_QuoteSet.contains(c2) && setContains(fHebrew_LetterSet,c3)) {
+ setAppliedRule(p2, "WB 7b Hebrew_Letter x Single_Quote");
continue;
}
- // Rule (7c) Hebrew_Letter Double_Quote x Hebrew_Letter
if (fHebrew_LetterSet.contains(c0) && fDouble_QuoteSet.contains(c1) && fHebrew_LetterSet.contains(c2)) {
+ setAppliedRule(p2, "WB 7c Hebrew_Letter Double_Quote x Hebrew_Letter");
continue;
}
- // Rule (8) Numeric x Numeric
if (fNumericSet.contains(c1) &&
fNumericSet.contains(c2)) {
+ setAppliedRule(p2, "WB 8 Numeric x Numeric");
continue;
}
- // Rule (9) (ALetter | Hebrew_Letter) x Numeric
if ((fALetterSet.contains(c1) || fHebrew_LetterSet.contains(c1)) &&
fNumericSet.contains(c2)) {
+ setAppliedRule(p2, "WB 9 (ALetter | Hebrew_Letter) x Numeric");
continue;
}
- // Rule (10) Numeric x (ALetter | Hebrew_Letter)
if (fNumericSet.contains(c1) &&
(fALetterSet.contains(c2) || fHebrew_LetterSet.contains(c2))) {
+ setAppliedRule(p2, "WB 10 Numeric x (ALetter | Hebrew_Letter)");
continue;
}
- // Rule (11) Numeric (MidNum | MidNumLet | Single_Quote) x Numeric
if (fNumericSet.contains(c0) &&
(fMidNumSet.contains(c1) || fMidNumLetSet.contains(c1) || fSingle_QuoteSet.contains(c1)) &&
fNumericSet.contains(c2)) {
+ setAppliedRule(p2, "WB 11 Numeric (MidNum | MidNumLet | Single_Quote) x Numeric");
continue;
}
- // Rule (12) Numeric x (MidNum | MidNumLet | SingleQuote) Numeric
if (fNumericSet.contains(c1) &&
(fMidNumSet.contains(c2) || fMidNumLetSet.contains(c2) || fSingle_QuoteSet.contains(c2)) &&
setContains(fNumericSet, c3)) {
+ setAppliedRule(p2, "WB 12 Numeric x (MidNum | MidNumLet | SingleQuote) Numeric");
continue;
}
- // Rule (13) Katakana x Katakana
// Note: matches UAX 29 rules, but doesn't come into play for ICU because
// all Katakana are handled by the dictionary breaker.
if (fKatakanaSet.contains(c1) &&
fKatakanaSet.contains(c2)) {
+ setAppliedRule(p2, "WB 13 Katakana x Katakana");
continue;
}
- // Rule 13a (ALetter | Hebrew_Letter | Numeric | KataKana | ExtendNumLet) x ExtendNumLet
if ((fALetterSet.contains(c1) || fHebrew_LetterSet.contains(c1) ||fNumericSet.contains(c1) ||
fKatakanaSet.contains(c1) || fExtendNumLetSet.contains(c1)) &&
fExtendNumLetSet.contains(c2)) {
+ setAppliedRule(p2, "WB 13a (ALetter | Hebrew_Letter | Numeric | KataKana | ExtendNumLet) x ExtendNumLet");
continue;
}
- // Rule 13b ExtendNumLet x (ALetter | Hebrew_Letter | Numeric | Katakana)
if (fExtendNumLetSet.contains(c1) &&
(fALetterSet.contains(c2) || fHebrew_LetterSet.contains(c2) ||
fNumericSet.contains(c2) || fKatakanaSet.contains(c2))) {
+ setAppliedRule(p2, "WB 13b ExtendNumLet x (ALetter | Hebrew_Letter | Numeric | Katakana)");
continue;
}
- // Rule 15 - 17 Group piars of Regional Indicators
if (fRegionalIndicatorSet.contains(c0) && fRegionalIndicatorSet.contains(c1)) {
+ setAppliedRule(p2, "WB 15-17 Group pairs of Regional Indicators.");
break;
}
if (fRegionalIndicatorSet.contains(c1) && fRegionalIndicatorSet.contains(c2)) {
+ setAppliedRule(p2, "WB 15-17 Group pairs of Regional Indicators.");
continue;
}
- // Rule 999. Break found here.
+ setAppliedRule(p2, "WB 999");
break;
}
breakPos = p2;
return breakPos;
}
-
}
static class RBBILineMonkey extends RBBIMonkeyKind {
-
- List fSets;
-
// UnicodeSets for each of the Line Breaking character classes.
// Order matches that of Unicode UAX 14, Table 1, which makes it a little easier
// to verify that they are all accounted for.
@@ -688,7 +733,6 @@ public class RBBITestMonkey extends TestFmwk {
StringBuffer fText;
int fOrigPositions;
-
// XUnicodeSet is like UnicodeSet, except that the method contains(int codePoint) does not
// throw exceptions on out-of-range codePoints. This matches ICU4C behavior.
// The LineMonkey test (ported from ICU4C) relies on this behavior, it uses a value of -1
@@ -708,7 +752,6 @@ public class RBBITestMonkey extends TestFmwk {
RBBILineMonkey()
{
fCharProperty = UProperty.LINE_BREAK;
- fSets = new ArrayList();
fBK = new XUnicodeSet("[\\p{Line_Break=BK}]");
fCR = new XUnicodeSet("[\\p{Line_break=CR}]");
@@ -772,55 +815,56 @@ public class RBBITestMonkey extends TestFmwk {
fHH.add('\u2010'); // Hyphen, '‐'
- fSets.add(fBK);
- fSets.add(fCR);
- fSets.add(fLF);
- fSets.add(fCM);
- fSets.add(fNL);
- fSets.add(fWJ);
- fSets.add(fZW);
- fSets.add(fGL);
- fSets.add(fSP);
- fSets.add(fB2);
- fSets.add(fBA);
- fSets.add(fBB);
- fSets.add(fHY);
- fSets.add(fCB);
- fSets.add(fCL);
- fSets.add(fCP);
- fSets.add(fEX);
- fSets.add(fIN);
- fSets.add(fJL);
- fSets.add(fJT);
- fSets.add(fJV);
- fSets.add(fNS);
- fSets.add(fOP);
- fSets.add(fQU);
- fSets.add(fIS);
- fSets.add(fNU);
- fSets.add(fPO);
- fSets.add(fPR);
- fSets.add(fSY);
- fSets.add(fAI);
- fSets.add(fAL);
- fSets.add(fH2);
- fSets.add(fH3);
- fSets.add(fHL);
- fSets.add(fID);
- fSets.add(fWJ);
- fSets.add(fRI);
- fSets.add(fSG);
- fSets.add(fEB);
- fSets.add(fEM);
- fSets.add(fZWJ);
+ fSets.add(fBK); fClassNames.add("BK");
+ fSets.add(fCR); fClassNames.add("CR");
+ fSets.add(fLF); fClassNames.add("LF");
+ fSets.add(fCM); fClassNames.add("CM");
+ fSets.add(fNL); fClassNames.add("NL");
+ fSets.add(fWJ); fClassNames.add("WJ");
+ fSets.add(fZW); fClassNames.add("ZW");
+ fSets.add(fGL); fClassNames.add("GL");
+ fSets.add(fSP); fClassNames.add("SP");
+ fSets.add(fB2); fClassNames.add("B2");
+ fSets.add(fBA); fClassNames.add("BA");
+ fSets.add(fBB); fClassNames.add("BB");
+ fSets.add(fHY); fClassNames.add("HY");
+ fSets.add(fCB); fClassNames.add("CB");
+ fSets.add(fCL); fClassNames.add("CL");
+ fSets.add(fCP); fClassNames.add("CP");
+ fSets.add(fEX); fClassNames.add("EX");
+ fSets.add(fIN); fClassNames.add("IN");
+ fSets.add(fJL); fClassNames.add("JL");
+ fSets.add(fJT); fClassNames.add("JT");
+ fSets.add(fJV); fClassNames.add("JV");
+ fSets.add(fNS); fClassNames.add("NV");
+ fSets.add(fOP); fClassNames.add("OP");
+ fSets.add(fQU); fClassNames.add("QU");
+ fSets.add(fIS); fClassNames.add("IS");
+ fSets.add(fNU); fClassNames.add("NU");
+ fSets.add(fPO); fClassNames.add("PO");
+ fSets.add(fPR); fClassNames.add("PR");
+ fSets.add(fSY); fClassNames.add("SY");
+ fSets.add(fAI); fClassNames.add("AI");
+ fSets.add(fAL); fClassNames.add("AL");
+ fSets.add(fH2); fClassNames.add("H2");
+ fSets.add(fH3); fClassNames.add("H3");
+ fSets.add(fHL); fClassNames.add("HL");
+ fSets.add(fID); fClassNames.add("ID");
+ fSets.add(fWJ); fClassNames.add("WJ");
+ fSets.add(fRI); fClassNames.add("RI");
+ fSets.add(fSG); fClassNames.add("SG");
+ fSets.add(fEB); fClassNames.add("EB");
+ fSets.add(fEM); fClassNames.add("EM");
+ fSets.add(fZWJ); fClassNames.add("ZWJ");
// TODO: fOP30 & fCP30 overlap with plain fOP. Probably OK, but fOP/CP chars will be over-represented.
- fSets.add(fOP30);
- fSets.add(fCP30);
+ fSets.add(fOP30); fClassNames.add("OP30");
+ fSets.add(fCP30); fClassNames.add("CP30");
}
@Override
void setText(StringBuffer s) {
fText = s;
+ prepareAppliedRules(s.length());
}
@@ -872,12 +916,11 @@ public class RBBITestMonkey extends TestFmwk {
pos = nextPos;
nextPos = moveIndex32(fText, pos, 1);
- // Rule LB2 - Break at end of text.
if (pos >= fText.length()) {
+ setAppliedRule(pos, "LB 2 Break at end of text");
break;
}
- // Rule LB 9 - adjust for combining sequences.
// We do this rule out-of-order because the adjustment does
// not effect the way that rules LB 3 through LB 6 match,
// and doing it here rather than after LB 6 is substantially
@@ -915,41 +958,43 @@ public class RBBITestMonkey extends TestFmwk {
// -1 positions out of prevPos yet - loop back to advance the
// position in the input without any further looking for breaks.
if (prevPos == -1) {
+ setAppliedRule(pos, "LB 9 adjust for combining sequences.");
continue;
}
- // LB 4 Always break after hard line breaks,
if (fBK.contains(prevChar)) {
+ setAppliedRule(pos, "LB 4 Always break after hard line breaks");
break;
}
- // LB 5 Break after CR, LF, NL, but not inside CR LF
if (fCR.contains(prevChar) && fLF.contains(thisChar)) {
+ setAppliedRule(pos, "LB 5 Break after CR, LF, NL, but not inside CR LF");
continue;
}
if (fCR.contains(prevChar) ||
fLF.contains(prevChar) ||
fNL.contains(prevChar)) {
+ setAppliedRule(pos, "LB 5 Break after CR, LF, NL, but not inside CR LF");
break;
}
- // LB 6 Don't break before hard line breaks
if (fBK.contains(thisChar) || fCR.contains(thisChar) ||
fLF.contains(thisChar) || fNL.contains(thisChar) ) {
+ setAppliedRule(pos, "LB 6 Don't break before hard line breaks");
continue;
}
- // LB 7 Don't break before spaces or zero-width space.
if (fSP.contains(thisChar)) {
+ setAppliedRule(pos, "LB 7 Don't break before spaces or zero-width space");
continue;
}
if (fZW.contains(thisChar)) {
+ setAppliedRule(pos, "LB 7 Don't break before spaces or zero-width space");
continue;
}
- // LB 8 Break after zero width space
// ZW SP* ÷
// Scan backwards from prevChar for SP* ZW
tPos = prevPos;
@@ -957,10 +1002,10 @@ public class RBBITestMonkey extends TestFmwk {
tPos = moveIndex32(fText, tPos, -1);
}
if (fZW.contains(UTF16.charAt(fText, tPos))) {
+ setAppliedRule(pos, "LB 8 Break after zero width space");
break;
}
- // LB 25 Numbers
// Move this test up, before LB8a, because numbers can match a longer sequence that would
// also match 8a. e.g. NU ZWJ IS PO (ZWJ acts like CM)
matchVals = LBNumberCheck(fText, prevPos, matchVals);
@@ -982,57 +1027,53 @@ public class RBBITestMonkey extends TestFmwk {
}
while (fCM.contains(thisChar));
}
+ setAppliedRule(pos, "LB 25 Numbers");
continue;
}
}
- // LB 8a: ZWJ x (ID | Extended_Pictographic | Emoji)
// The monkey test's way of ignoring combining characters doesn't work
// for this rule. ZWJ is also a CM. Need to get the actual character
// preceding "thisChar", not ignoring combining marks, possibly ZWJ.
{
int prevC = fText.codePointBefore(pos);
if (fZWJ.contains(prevC)) {
+ setAppliedRule(pos, "LB 8a ZWJ x");
continue;
}
}
- // LB 9, 10 Already done, at top of loop.
- //
+ // appliedRule: "LB 9, 10"; // Already done, at top of loop.";
- // LB 11
// x WJ
// WJ x
if (fWJ.contains(thisChar) || fWJ.contains(prevChar)) {
+ setAppliedRule(pos, "LB 11 Do not break before or after WORD JOINER and related characters.");
continue;
}
- // LB 12
- // GL x
if (fGL.contains(prevChar)) {
+ setAppliedRule(pos, "LB 12 GL x");
continue;
}
- // LB 12a
- // [^SP BA HY] x GL
if (!(fSP.contains(prevChar) ||
fBA.contains(prevChar) ||
fHY.contains(prevChar) ) && fGL.contains(thisChar)) {
+ setAppliedRule(pos, "LB 12a [^SP BA HY] x GL");
continue;
}
- // LB 13 Don't break before closings.
- //
if (fCL.contains(thisChar) ||
fCP.contains(thisChar) ||
fEX.contains(thisChar) ||
fSY.contains(thisChar)) {
+ setAppliedRule(pos, "LB 13 Don't break before closings");
continue;
}
- // LB 14 Don't break after OP SP*
// Scan backwards, checking for this sequence.
// The OP char could include combining marks, so we actually check for
// OP CM* SP* x
@@ -1046,24 +1087,23 @@ public class RBBITestMonkey extends TestFmwk {
tPos=moveIndex32(fText, tPos, -1);
}
if (fOP.contains(UTF16.charAt(fText, tPos))) {
+ setAppliedRule(pos, "LB 14 Don't break after OP SP*");
continue;
}
- // LB 14a Break before an IS that begins a number and follows a space
if (nextPos < fText.length()) {
int nextChar = fText.codePointAt(nextPos);
if (fSP.contains(prevChar) && fIS.contains(thisChar) && fNU.contains(nextChar)) {
+ setAppliedRule(pos, "LB 14a Break before an IS that begins a number and follows a space");
break;
}
}
- // LB14b Do not break before numeric separators, even after spaces.
if (fIS.contains(thisChar)) {
+ setAppliedRule(pos, "LB 14b Do not break before numeric separators, even after spaces");
continue;
}
- // LB 15 Do not break within "[
- // QU CM* SP* x OP
if (fOP.contains(thisChar)) {
// Scan backwards from prevChar to see if it is preceded by QU CM* SP*
tPos = prevPos;
@@ -1074,11 +1114,11 @@ public class RBBITestMonkey extends TestFmwk {
tPos = moveIndex32(fText, tPos, -1);
}
if (fQU.contains(UTF16.charAt(fText, tPos))) {
+ setAppliedRule(pos, "LB 15 QU SP* x OP");
continue;
}
}
- // LB 16 (CL | CP) SP* x NS
if (fNS.contains(thisChar)) {
tPos = prevPos;
while (tPos > 0 && fSP.contains(UTF16.charAt(fText, tPos))) {
@@ -1088,12 +1128,12 @@ public class RBBITestMonkey extends TestFmwk {
tPos = moveIndex32(fText, tPos, -1);
}
if (fCL.contains(UTF16.charAt(fText, tPos)) || fCP.contains(UTF16.charAt(fText, tPos))) {
+ setAppliedRule(pos, "LB 16 (CL | CP) SP* x NS");
continue;
}
}
- // LB 17 B2 SP* x B2
if (fB2.contains(thisChar)) {
tPos = prevPos;
while (tPos > 0 && fSP.contains(UTF16.charAt(fText, tPos))) {
@@ -1103,156 +1143,169 @@ public class RBBITestMonkey extends TestFmwk {
tPos = moveIndex32(fText, tPos, -1);
}
if (fB2.contains(UTF16.charAt(fText, tPos))) {
+ setAppliedRule(pos, "LB 17 B2 SP* x B2");
continue;
}
}
- // LB 18 break after space
if (fSP.contains(prevChar)) {
+ setAppliedRule(pos, "LB 18 break after space");
break;
}
- // LB 19
// x QU
// QU x
if (fQU.contains(thisChar) || fQU.contains(prevChar)) {
+ setAppliedRule(pos, "LB 19");
continue;
}
- // LB 20 Break around a CB
if (fCB.contains(thisChar) || fCB.contains(prevChar)) {
+ setAppliedRule(pos, "LB 20 Break around a CB");
break;
}
- // LB 20.09 Don't break between Hyphens and letters if a break precedes the hyphen.
+ // Don't break between Hyphens and letters if a break precedes the hyphen.
// Formerly this was a Finnish tailoring.
// Moved to root in ICU 63. This is an ICU customization, not in UAX-14.
// ^($HY | $HH) $AL;
if (fAL.contains(thisChar) && (fHY.contains(prevChar) || fHH.contains(prevChar)) &&
prevPosX2 == -1) {
+ setAppliedRule(pos, "LB 20.09");
continue;
}
- // LB 21
if (fBA.contains(thisChar) ||
fHY.contains(thisChar) ||
fNS.contains(thisChar) ||
fBB.contains(prevChar) ) {
+ setAppliedRule(pos, "LB 21");
continue;
}
- // LB 21a, HL (HY | BA) x
if (fHL.contains(prevCharX2) && (fHY.contains(prevChar) || fBA.contains(prevChar))) {
+ setAppliedRule(pos, "LB 21a HL (HY | BA) x");
continue;
}
- // LB 21b, SY x HL
if (fSY.contains(prevChar) && fHL.contains(thisChar)) {
+ setAppliedRule(pos, "LB 21b SY x HL");
continue;
}
- // LB 22
if (fIN.contains(thisChar)) {
+ setAppliedRule(pos, "LB 22");
continue;
}
- // LB 23 (AL | HL) x NU
+ // (AL | HL) x NU
// NU x (AL | HL)
if ((fAL.contains(prevChar) || fHL.contains(prevChar)) && fNU.contains(thisChar)) {
+ setAppliedRule(pos, "LB 23");
continue;
}
if (fNU.contains(prevChar) && (fAL.contains(thisChar) || fHL.contains(thisChar))) {
+ setAppliedRule(pos, "LB 23");
continue;
}
- // LB 23a Do not break between numeric prefixes and ideographs, or between ideographs and numeric postfixes.
+ // Do not break between numeric prefixes and ideographs, or between ideographs and numeric postfixes.
// PR x (ID | EB | EM)
// (ID | EB | EM) x PO
if (fPR.contains(prevChar) &&
(fID.contains(thisChar) || fEB.contains(thisChar) || fEM.contains(thisChar))) {
+ setAppliedRule(pos, "LB 23a");
continue;
}
if ((fID.contains(prevChar) || fEB.contains(prevChar) || fEM.contains(prevChar)) &&
fPO.contains(thisChar)) {
+ setAppliedRule(pos, "LB 23a");
continue;
}
- // LB 24 Do not break between prefix and letters or ideographs.
+ // Do not break between prefix and letters or ideographs.
// (PR | PO) x (AL | HL)
// (AL | HL) x (PR | PO)
if ((fPR.contains(prevChar) || fPO.contains(prevChar)) &&
(fAL.contains(thisChar) || fHL.contains(thisChar))) {
+ setAppliedRule(pos, "LB 24 no break between prefix and letters or ideographs");
continue;
}
if ((fAL.contains(prevChar) || fHL.contains(prevChar)) &&
(fPR.contains(thisChar) || fPO.contains(thisChar))) {
+ setAppliedRule(pos, "LB 24 no break between prefix and letters or ideographs");
continue;
}
- // LB 25 Numbers match, moved up, before LB 8a.
+ // appliedRule: "LB 25 numbers match"; // moved up, before LB 8a,
- // LB 26 Do not break Korean Syllables
if (fJL.contains(prevChar) && (fJL.contains(thisChar) ||
fJV.contains(thisChar) ||
fH2.contains(thisChar) ||
fH3.contains(thisChar))) {
+ setAppliedRule(pos, "LB 26 Do not break a Korean syllable.");
continue;
}
if ((fJV.contains(prevChar) || fH2.contains(prevChar)) &&
(fJV.contains(thisChar) || fJT.contains(thisChar))) {
+ setAppliedRule(pos, "LB 26 Do not break a Korean syllable.");
continue;
}
if ((fJT.contains(prevChar) || fH3.contains(prevChar)) &&
fJT.contains(thisChar)) {
+ setAppliedRule(pos, "LB 26 Do not break a Korean syllable.");
continue;
}
- // LB 27 Treat a Korean Syllable Block the same as ID
if ((fJL.contains(prevChar) || fJV.contains(prevChar) ||
fJT.contains(prevChar) || fH2.contains(prevChar) || fH3.contains(prevChar)) &&
fIN.contains(thisChar)) {
+ setAppliedRule(pos, "LB 27 Treat a Korean Syllable Block the same as ID.");
continue;
}
if ((fJL.contains(prevChar) || fJV.contains(prevChar) ||
fJT.contains(prevChar) || fH2.contains(prevChar) || fH3.contains(prevChar)) &&
fPO.contains(thisChar)) {
+ setAppliedRule(pos, "LB 27 Treat a Korean Syllable Block the same as ID.");
continue;
}
if (fPR.contains(prevChar) && (fJL.contains(thisChar) || fJV.contains(thisChar) ||
fJT.contains(thisChar) || fH2.contains(thisChar) || fH3.contains(thisChar))) {
+ setAppliedRule(pos, "LB 27 Treat a Korean Syllable Block the same as ID.");
continue;
}
- // LB 28 Do not break between alphabetics
if ((fAL.contains(prevChar) || fHL.contains(prevChar)) && (fAL.contains(thisChar) || fHL.contains(thisChar))) {
+ setAppliedRule(pos, "LB 28 Do not break between alphabetics");
continue;
}
- // LB 29 Do not break between numeric punctuation and alphabetics
if (fIS.contains(prevChar) && (fAL.contains(thisChar) || fHL.contains(thisChar))) {
+ setAppliedRule(pos, "LB 29 Do not break between numeric punctuation and alphabetics");
continue;
}
- // LB 30 Do not break between letters, numbers, or ordinary symbols and opening or closing punctuation.
// (AL | NU) x OP
// CP x (AL | NU)
if ((fAL.contains(prevChar) || fHL.contains(prevChar) || fNU.contains(prevChar)) &&
fOP30.contains(thisChar)) {
+ setAppliedRule(pos, "LB 30 Do not break between letters, numbers, or ordinary symbols and opening or closing punctuation.");
continue;
}
if (fCP30.contains(prevChar) &&
(fAL.contains(thisChar) || fHL.contains(thisChar) || fNU.contains(thisChar))) {
+ setAppliedRule(pos, "LB 30 Do not break between letters, numbers, or ordinary symbols and opening or closing punctuation.");
continue;
}
- // LB 30a Break between pairs of Regional Indicators.
// RI RI ÷ RI
// RI x RI
if (fRI.contains(prevCharX2) && fRI.contains(prevChar) && fRI.contains(thisChar)) {
+ setAppliedRule(pos, "LB 30a Break between pairs of Regional Indicators.");
break;
}
if (fRI.contains(prevChar) && fRI.contains(thisChar)) {
@@ -1260,14 +1313,16 @@ public class RBBITestMonkey extends TestFmwk {
// Over-write the trailing one (thisChar) to prevent it from forming another pair with a
// following RI. This is a hack.
thisChar = -1;
+ setAppliedRule(pos, "LB 30a Break between pairs of Regional Indicators.");
continue;
}
- // LB30b Emoji Base x Emoji Modifier
if (fEB.contains(prevChar) && fEM.contains(thisChar)) {
+ setAppliedRule(pos, "LB 30b Emoji Base x Emoji Modifier");
continue;
}
// LB 31 Break everywhere else
+ setAppliedRule(pos, "LB 31 Break everywhere else");
break;
}
@@ -1450,7 +1505,6 @@ public class RBBITestMonkey extends TestFmwk {
*
*/
static class RBBISentenceMonkey extends RBBIMonkeyKind {
- List fSets;
StringBuffer fText;
UnicodeSet fSepSet;
@@ -1467,13 +1521,9 @@ public class RBBITestMonkey extends TestFmwk {
UnicodeSet fOtherSet;
UnicodeSet fExtendSet;
-
-
RBBISentenceMonkey() {
fCharProperty = UProperty.SENTENCE_BREAK;
- fSets = new ArrayList();
-
// Separator Set Note: Beginning with Unicode 5.1, CR and LF were removed from the separator
// set and made into character classes of their own. For the monkey impl,
// they remain in SEP, since Sep always appears with CR and LF in the rules.
@@ -1506,20 +1556,20 @@ public class RBBITestMonkey extends TestFmwk {
fOtherSet.removeAll(fCloseSet);
fOtherSet.removeAll(fExtendSet);
- fSets.add(fSepSet);
- fSets.add(fFormatSet);
-
- fSets.add(fSpSet);
- fSets.add(fLowerSet);
- fSets.add(fUpperSet);
- fSets.add(fOLetterSet);
- fSets.add(fNumericSet);
- fSets.add(fATermSet);
- fSets.add(fSContinueSet);
- fSets.add(fSTermSet);
- fSets.add(fCloseSet);
- fSets.add(fOtherSet);
- fSets.add(fExtendSet);
+ fSets.add(fSepSet); fClassNames.add("Sep");
+ fSets.add(fFormatSet); fClassNames.add("Format");
+
+ fSets.add(fSpSet); fClassNames.add("Sp");
+ fSets.add(fLowerSet); fClassNames.add("Lower");
+ fSets.add(fUpperSet); fClassNames.add("Upper");
+ fSets.add(fOLetterSet); fClassNames.add("OLetter");
+ fSets.add(fNumericSet); fClassNames.add("Numeric");
+ fSets.add(fATermSet); fClassNames.add("ATerm");
+ fSets.add(fSContinueSet); fClassNames.add("SContinue");
+ fSets.add(fSTermSet); fClassNames.add("STerm");
+ fSets.add(fCloseSet); fClassNames.add("Close");
+ fSets.add(fOtherSet); fClassNames.add("Other");
+ fSets.add(fExtendSet); fClassNames.add("Extend");
}
@@ -1531,6 +1581,7 @@ public class RBBITestMonkey extends TestFmwk {
@Override
void setText(StringBuffer s) {
fText = s;
+ prepareAppliedRules(s.length());
}
@@ -1601,43 +1652,44 @@ public class RBBITestMonkey extends TestFmwk {
p1 = p2; c1 = c2;
p2 = p3; c2 = c3;
- // Advancd p3 by X(Extend | Format)* Rule 4
+ // Advance p3 by X(Extend | Format)* Rule 4
p3 = moveForward(p3);
c3 = cAt(p3);
- // Rule (3) CR x LF
if (c1==0x0d && c2==0x0a && p2==(p1+1)) {
+ setAppliedRule(p2, "SB3 CR x LF");
continue;
}
- // Rule (4) Sep <break>
if (fSepSet.contains(c1)) {
p2 = p1+1; // Separators don't combine with Extend or Format
+ setAppliedRule(p2, "SB4 Sep <break>");
break;
}
if (p2 >= fText.length()) {
// Reached end of string. Always a break position.
+ setAppliedRule(p2, "SB4 Sep <break>");
break;
}
if (p2 == prevPos) {
// Still warming up the loop. (won't work with zero length strings, but we don't care)
+ setAppliedRule(p2, "SB4 Sep <break>");
continue;
}
- // Rule (6). ATerm x Numeric
if (fATermSet.contains(c1) && fNumericSet.contains(c2)) {
+ setAppliedRule(p2, "SB6 ATerm x Numeric");
continue;
}
- // Rule (7). (Upper | Lower) ATerm x Uppper
if ((fUpperSet.contains(c0) || fLowerSet.contains(c0)) &&
fATermSet.contains(c1) && fUpperSet.contains(c2)) {
+ setAppliedRule(p2, "SB7 (Upper | Lower) ATerm x Uppper");
continue;
}
- // Rule (8) ATerm Close* Sp* x (not (OLettter | Upper | Lower | Sep))* Lower
// Note: Sterm | ATerm are added to the negated part of the expression by a
// note to the Unicode 5.0 documents.
int p8 = p1;
@@ -1655,16 +1707,17 @@ public class RBBITestMonkey extends TestFmwk {
fLowerSet.contains(c) || fSepSet.contains(c) ||
fATermSet.contains(c) || fSTermSet.contains(c))
{
+ setAppliedRule(p2, "SB8 ATerm Close* Sp* x (not (OLettter | Upper | Lower | Sep))* Lower");
break;
}
p8 = moveForward(p8);
}
if (p8<fText.length() && fLowerSet.contains(cAt(p8))) {
+ setAppliedRule(p2, "SB8 ATerm Close* Sp* x (not (OLettter | Upper | Lower | Sep))* Lower");
continue;
}
}
- // Rule 8a (STerm | ATerm) Close* Sp* x (SContinue | Sterm | ATerm)
if (fSContinueSet.contains(c2) || fSTermSet.contains(c2) || fATermSet.contains(c2)) {
p8 = p1;
while (setContains(fSpSet, cAt(p8))) {
@@ -1675,12 +1728,12 @@ public class RBBITestMonkey extends TestFmwk {
}
c = cAt(p8);
if (setContains(fSTermSet, c) || setContains(fATermSet, c)) {
+ setAppliedRule(p2, "SB8a (STerm | ATerm) Close* Sp* x (SContinue | Sterm | ATerm)");
continue;
}
}
- // Rule (9) (STerm | ATerm) Close* x (Close | Sp | Sep | CR | LF)
int p9 = p1;
while (p9>0 && fCloseSet.contains(cAt(p9))) {
p9 = moveBack(p9);
@@ -1688,11 +1741,11 @@ public class RBBITestMonkey extends TestFmwk {
c = cAt(p9);
if ((fSTermSet.contains(c) || fATermSet.contains(c))) {
if (fCloseSet.contains(c2) || fSpSet.contains(c2) || fSepSet.contains(c2)) {
+ setAppliedRule(p2, "SB9 (STerm | ATerm) Close* x (Close | Sp | Sep | CR | LF)");
continue;
}
}
- // Rule (10) (Sterm | ATerm) Close* Sp* x (Sp | Sep | CR | LF)
int p10 = p1;
while (p10>0 && fSpSet.contains(cAt(p10))) {
p10 = moveBack(p10);
@@ -1702,11 +1755,11 @@ public class RBBITestMonkey extends TestFmwk {
}
if (fSTermSet.contains(cAt(p10)) || fATermSet.contains(cAt(p10))) {
if (fSpSet.contains(c2) || fSepSet.contains(c2)) {
+ setAppliedRule(p2, "SB10 (Sterm | ATerm) Close* Sp* x (Sp | Sep | CR | LF)");
continue;
}
}
- // Rule (11) (STerm | ATerm) Close* Sp* <break>
int p11 = p1;
if (p11>0 && fSepSet.contains(cAt(p11))) {
p11 = moveBack(p11);
@@ -1718,18 +1771,16 @@ public class RBBITestMonkey extends TestFmwk {
p11 = moveBack(p11);
}
if (fSTermSet.contains(cAt(p11)) || fATermSet.contains(cAt(p11))) {
+ setAppliedRule(p2, "SB11 (STerm | ATerm) Close* Sp* <break>");
break;
}
- // Rule (12) Any x Any
+ setAppliedRule(p2, "SB12 Any x Any");
continue;
}
breakPos = p2;
return breakPos;
}
-
-
-
}
@@ -1879,7 +1930,6 @@ public class RBBITestMonkey extends TestFmwk {
StringBuffer testText = new StringBuffer();
int numCharClasses;
List chClasses;
- int[] expected = new int[TESTSTRINGLEN*2 + 1];
int expectedCount = 0;
boolean[] expectedBreaks = new boolean[TESTSTRINGLEN*2 + 1];
boolean[] forwardBreaks = new boolean[TESTSTRINGLEN*2 + 1];
@@ -1926,6 +1976,9 @@ public class RBBITestMonkey extends TestFmwk {
//
//--------------------------------------------------------------------------------------------
+ // For minimizing width of class name output.
+ int classNameSize = mk.maxClassNameSize();
+
int dotsOnLine = 0;
while (loopCount < numIterations || numIterations == -1) {
if (numIterations == -1 && loopCount % 10 == 0) {
@@ -1969,7 +2022,6 @@ public class RBBITestMonkey extends TestFmwk {
System.out.println();
}
- Arrays.fill(expected, 0);
Arrays.fill(expectedBreaks, false);
Arrays.fill(forwardBreaks, false);
Arrays.fill(reverseBreaks, false);
@@ -1977,11 +2029,10 @@ public class RBBITestMonkey extends TestFmwk {
Arrays.fill(followingBreaks, false);
Arrays.fill(precedingBreaks, false);
- // Calculate the expected results for this test string.
+ // Calculate the expected results for this test string and reset applied rules.
mk.setText(testText);
expectedCount = 0;
expectedBreaks[0] = true;
- expected[expectedCount ++] = 0;
int breakPos = 0;
int lastBreakPos = -1;
for (;;) {
@@ -1998,7 +2049,6 @@ public class RBBITestMonkey extends TestFmwk {
// break;
}
expectedBreaks[breakPos] = true;
- expected[expectedCount ++] = breakPos;
}
// Find the break positions using forward iteration
@@ -2079,16 +2129,22 @@ public class RBBITestMonkey extends TestFmwk {
// Compare the expected and actual results.
for (i=0; i<=testText.length(); i++) {
String errorType = null;
+ boolean[] currentBreakData = null;
if (forwardBreaks[i] != expectedBreaks[i]) {
errorType = "next()";
+ currentBreakData = forwardBreaks;
} else if (reverseBreaks[i] != forwardBreaks[i]) {
errorType = "previous()";
+ currentBreakData = reverseBreaks;
} else if (isBoundaryBreaks[i] != expectedBreaks[i]) {
errorType = "isBoundary()";
+ currentBreakData = isBoundaryBreaks;
} else if (followingBreaks[i] != expectedBreaks[i]) {
errorType = "following()";
+ currentBreakData = followingBreaks;
} else if (precedingBreaks[i] != expectedBreaks[i]) {
errorType = "preceding()";
+ currentBreakData = precedingBreaks;
}
if (errorType != null) {
@@ -2122,43 +2178,47 @@ public class RBBITestMonkey extends TestFmwk {
}
}
- // Format looks like "<data><>\uabcd\uabcd<>\U0001abcd...</data>"
- StringBuffer errorText = new StringBuffer();
-
- int c; // Char from test data
+ // Formatting of each line includes:
+ // character code
+ // reference break: '|' -> a break, '.' -> no break
+ // actual break: '|' -> a break, '.' -> no break
+ // (name of character clase)
+ // Unicode name of character
+ // '--→' indicates location of the difference.
+
+ StringBuilder buffer = new StringBuilder();
+ buffer.append("\n")
+ .append((expectedBreaks[i] ? "Break expected but not found." : "Break found but not expected."))
+ .append(
+ String.format(" at index %d. Parameters to reproduce: @\"type=%s seed=%d loop=1\"\n",
+ i, name, seed));
+
+ int c; // Char from test data
for (ci = startContext; ci <= endContext && ci != -1; ci = nextCP(testText, ci)) {
- if (ci == i) {
- // This is the location of the error.
- errorText.append("<?>---------------------------------\n");
- } else if (expectedBreaks[ci]) {
- // This a non-error expected break position.
- errorText.append("------------------------------------\n");
- }
- if (ci < testText.length()) {
- c = UTF16.charAt(testText, ci);
- appendCharToBuf(errorText, c, 11);
- String gc = UCharacter.getPropertyValueName(UProperty.GENERAL_CATEGORY, UCharacter.getType(c), UProperty.NameChoice.SHORT);
- appendToBuf(errorText, gc, 8);
- int extraProp = UCharacter.getIntPropertyValue(c, mk.fCharProperty);
- String extraPropValue =
- UCharacter.getPropertyValueName(mk.fCharProperty, extraProp, UProperty.NameChoice.LONG);
- appendToBuf(errorText, extraPropValue, 20);
-
- String charName = UCharacter.getExtendedName(c);
- appendToBuf(errorText, charName, 40);
- errorText.append('\n');
+
+ c = testText.codePointAt(ci);
+ buffer.append((ci == i) ? " --→" : " ")
+ .append(String.format(" %3d : ", ci))
+ .append(!expectedBreaks[ci] ? " . " : " | ") // Reference break
+ .append(!currentBreakData[ci] ? " . " : " | "); // Actual break
+
+ // BMP or SMP character in hex
+ if (c >= 0x10000) {
+ buffer.append("\\U").append(String.format("%08x", (int) c));
+ } else {
+ buffer.append(" \\u").append(String.format("%04x", (int) c));
}
+
+ buffer.append(
+ String.format(String.format(" %%-%ds", (int) classNameSize),
+ mk.classNameFromCodepoint(c)))
+ .append(String.format(" %-40s", mk.getAppliedRule(ci)))
+ .append(String.format(" %-40s\n", UCharacter.getExtendedName(c)));
+
+ if (ci >= endContext) { break; }
}
- if (ci == testText.length() && ci != -1) {
- errorText.append("<>");
- }
- errorText.append("</data>\n");
+ errln(buffer.toString());
- // Output the error
- errln(name + " break monkey test error. " +
- (expectedBreaks[i]? "Break expected but not found." : "Break found but not expected.") +
- "\nOperation = " + errorType + "; random seed = " + seed + "; buf Idx = " + i + "\n" +
- errorText);
break;
}
}
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/rbbi/rbbitst.txt b/android_icu4j/src/main/tests/android/icu/dev/test/rbbi/rbbitst.txt
index 240dbddf2..9962f94e4 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/rbbi/rbbitst.txt
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/rbbi/rbbitst.txt
@@ -1991,3 +1991,23 @@ Bangkok)•</data>
•より<400>詳しい<400>こと<400>を<400>お<400>知<400>り<400>に<400>なり<400>たい<400>方<400>は<400>、•Glossary<200>,• •Technical<200> •Introduction<200> •および<400> •Useful<200> •Resources<200>を<400>ご<400>参照<400>くだ<400>さい<400>。•
•</data>
+
+
+#
+# Bug 20303 Multiple Look-ahead rules with similar contexts.
+# Check that samples of such rules are being handled correctly.
+#
+
+<rules>
+!!forward;
+!!quoted_literals_only;
+!!chain;
+[a] [b] / [c] [d];
+[a] [b] / [c] [d] {100};
+[a] [b] / [e] [f] {200};
+[a] [b] / [e] [g] {300};
+[a] [b] [c] [h] {400};
+[x] [a] [b] / [c] [d] {500};
+[y] [a] [b] [c] [d] {600};
+</rules>
+<data>•ab<100>c•d•ab<200>e•f•ab<300>e•g•abch<400>xab<500>c•d•yabcd<600></data>
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/FormatHandler.java b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/FormatHandler.java
index 2d8d3e9b9..9ed7d9cdd 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/FormatHandler.java
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/FormatHandler.java
@@ -31,6 +31,7 @@ import android.icu.text.DateIntervalInfo;
import android.icu.text.DecimalFormat;
import android.icu.text.DecimalFormatSymbols;
import android.icu.text.DurationFormat;
+import android.icu.text.ListFormatter;
import android.icu.text.MessageFormat;
import android.icu.text.NumberFormat;
import android.icu.text.PluralFormat;
@@ -1834,6 +1835,36 @@ public class FormatHandler
}
}
+ public static class ListFormatterFieldHandler implements SerializableTestUtility.Handler
+ {
+ @Override
+ public Object[] getTestObjects()
+ {
+ return new Object[] {ListFormatter.Field.ELEMENT, ListFormatter.Field.LITERAL};
+ }
+
+ @Override
+ public boolean hasSameBehavior(Object a, Object b)
+ {
+ return (a == b);
+ }
+ }
+
+ public static class ListFormatterSpanFieldHandler implements SerializableTestUtility.Handler
+ {
+ @Override
+ public Object[] getTestObjects()
+ {
+ return new Object[] {ListFormatter.SpanField.LIST_SPAN};
+ }
+
+ @Override
+ public boolean hasSameBehavior(Object a, Object b)
+ {
+ return (a == b);
+ }
+ }
+
public static class DateFormatHandler implements SerializableTestUtility.Handler
{
static HashMap cannedPatterns = new HashMap();
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/SerializableTestUtility.java b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/SerializableTestUtility.java
index aca092de4..acc94028f 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/SerializableTestUtility.java
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/SerializableTestUtility.java
@@ -823,6 +823,8 @@ public class SerializableTestUtility {
map.put("android.icu.text.MessageFormat$Field", new FormatHandler.MessageFormatFieldHandler());
map.put("android.icu.text.RelativeDateTimeFormatter$Field", new FormatHandler.RelativeDateTimeFormatterFieldHandler());
map.put("android.icu.text.DateIntervalFormat$SpanField", new FormatHandler.DateIntervalSpanFieldHandler());
+ map.put("android.icu.text.ListFormatter$Field", new FormatHandler.ListFormatterFieldHandler());
+ map.put("android.icu.text.ListFormatter$SpanField", new FormatHandler.ListFormatterSpanFieldHandler());
map.put("android.icu.impl.duration.BasicDurationFormat", new FormatHandler.BasicDurationFormatHandler());
map.put("android.icu.impl.RelativeDateFormat", new FormatHandler.RelativeDateFormatHandler());
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.impl.IllegalIcuArgumentException.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.impl.IllegalIcuArgumentException.dat
deleted file mode 100644
index 9b78984e9..000000000
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.impl.IllegalIcuArgumentException.dat
+++ /dev/null
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.impl.InvalidFormatException.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.impl.InvalidFormatException.dat
deleted file mode 100644
index da8f973bd..000000000
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.impl.InvalidFormatException.dat
+++ /dev/null
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.impl.locale.LocaleSyntaxException.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.impl.locale.LocaleSyntaxException.dat
deleted file mode 100644
index 4798049bb..000000000
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.impl.locale.LocaleSyntaxException.dat
+++ /dev/null
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.number.SkeletonSyntaxException.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.number.SkeletonSyntaxException.dat
deleted file mode 100644
index 2a00128af..000000000
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.number.SkeletonSyntaxException.dat
+++ /dev/null
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.ArabicShapingException.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.ArabicShapingException.dat
deleted file mode 100644
index 80d91a70d..000000000
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.ArabicShapingException.dat
+++ /dev/null
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.ChineseDateFormat.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.ChineseDateFormat.dat
deleted file mode 100644
index 8eb1f6c70..000000000
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.ChineseDateFormat.dat
+++ /dev/null
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.ChineseDateFormatSymbols.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.ChineseDateFormatSymbols.dat
deleted file mode 100644
index 5da5423f2..000000000
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.ChineseDateFormatSymbols.dat
+++ /dev/null
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.DateFormat.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.DateFormat.dat
deleted file mode 100644
index 63f5bf19f..000000000
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.DateFormat.dat
+++ /dev/null
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.DateFormatSymbols.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.DateFormatSymbols.dat
deleted file mode 100644
index 976171fcc..000000000
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.DateFormatSymbols.dat
+++ /dev/null
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.DecimalFormat.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.DecimalFormat.dat
deleted file mode 100644
index 29efabacb..000000000
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.DecimalFormat.dat
+++ /dev/null
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.SimpleDateFormat.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.SimpleDateFormat.dat
deleted file mode 100644
index 1d53bbdf4..000000000
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.SimpleDateFormat.dat
+++ /dev/null
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.StringPrepParseException.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.StringPrepParseException.dat
deleted file mode 100644
index c713de0f0..000000000
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.StringPrepParseException.dat
+++ /dev/null
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.BuddhistCalendar.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.BuddhistCalendar.dat
deleted file mode 100644
index fee1a26ce..000000000
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.BuddhistCalendar.dat
+++ /dev/null
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.ChineseCalendar.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.ChineseCalendar.dat
deleted file mode 100644
index d0bc59af7..000000000
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.ChineseCalendar.dat
+++ /dev/null
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.DangiCalendar.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.DangiCalendar.dat
deleted file mode 100644
index a6a2e6cd2..000000000
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.DangiCalendar.dat
+++ /dev/null
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.EthiopicCalendar.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.EthiopicCalendar.dat
deleted file mode 100644
index fc8f092cf..000000000
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.EthiopicCalendar.dat
+++ /dev/null
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.HebrewCalendar.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.HebrewCalendar.dat
deleted file mode 100644
index f5056c8e9..000000000
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.HebrewCalendar.dat
+++ /dev/null
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.ICUCloneNotSupportedException.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.ICUCloneNotSupportedException.dat
deleted file mode 100644
index 77b327fdc..000000000
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.ICUCloneNotSupportedException.dat
+++ /dev/null
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.ICUException.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.ICUException.dat
deleted file mode 100644
index 123aaf008..000000000
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.ICUException.dat
+++ /dev/null
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.ICUUncheckedIOException.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.ICUUncheckedIOException.dat
deleted file mode 100644
index 9bdef522f..000000000
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.ICUUncheckedIOException.dat
+++ /dev/null
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.IllformedLocaleException.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.IllformedLocaleException.dat
deleted file mode 100644
index 2a66dbd8f..000000000
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.IllformedLocaleException.dat
+++ /dev/null
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.IndianCalendar.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.IndianCalendar.dat
deleted file mode 100644
index 2518d9c7c..000000000
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.IndianCalendar.dat
+++ /dev/null
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.JapaneseCalendar.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.JapaneseCalendar.dat
deleted file mode 100644
index b7e866ede..000000000
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.JapaneseCalendar.dat
+++ /dev/null
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.PersianCalendar.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.PersianCalendar.dat
deleted file mode 100644
index 3f842b076..000000000
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.PersianCalendar.dat
+++ /dev/null
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.TaiwanCalendar.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.TaiwanCalendar.dat
deleted file mode 100644
index 339b00147..000000000
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.TaiwanCalendar.dat
+++ /dev/null
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.UResourceTypeMismatchException.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.UResourceTypeMismatchException.dat
deleted file mode 100644
index 87d044695..000000000
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.UResourceTypeMismatchException.dat
+++ /dev/null
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.impl.DateNumberFormat.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.impl.DateNumberFormat.dat
index ab393f69c..ab393f69c 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.impl.DateNumberFormat.dat
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.impl.DateNumberFormat.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.impl.IllegalIcuArgumentException.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.impl.IllegalIcuArgumentException.dat
new file mode 100644
index 000000000..eecd041d2
--- /dev/null
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.impl.IllegalIcuArgumentException.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.impl.InvalidFormatException.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.impl.InvalidFormatException.dat
new file mode 100644
index 000000000..e5e894448
--- /dev/null
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.impl.InvalidFormatException.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.impl.OlsonTimeZone.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.impl.OlsonTimeZone.dat
index 22947440d..13b444859 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.impl.OlsonTimeZone.dat
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.impl.OlsonTimeZone.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.impl.RelativeDateFormat.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.impl.RelativeDateFormat.dat
index 727baf3af..b9c034bae 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.impl.RelativeDateFormat.dat
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.impl.RelativeDateFormat.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.impl.TZDBTimeZoneNames.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.impl.TZDBTimeZoneNames.dat
index be0463283..be0463283 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.impl.TZDBTimeZoneNames.dat
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.impl.TZDBTimeZoneNames.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.impl.TimeZoneAdapter.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.impl.TimeZoneAdapter.dat
index f284bee10..6ebd7fe7a 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.impl.TimeZoneAdapter.dat
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.impl.TimeZoneAdapter.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.impl.TimeZoneGenericNames.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.impl.TimeZoneGenericNames.dat
index de19e0718..de19e0718 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.impl.TimeZoneGenericNames.dat
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.impl.TimeZoneGenericNames.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.impl.TimeZoneNamesImpl.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.impl.TimeZoneNamesImpl.dat
index c04af70cf..c04af70cf 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.impl.TimeZoneNamesImpl.dat
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.impl.TimeZoneNamesImpl.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.impl.duration.BasicDurationFormat.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.impl.duration.BasicDurationFormat.dat
index 3bfb44296..3bfb44296 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.impl.duration.BasicDurationFormat.dat
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.impl.duration.BasicDurationFormat.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.impl.locale.LocaleSyntaxException.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.impl.locale.LocaleSyntaxException.dat
new file mode 100644
index 000000000..431275f82
--- /dev/null
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.impl.locale.LocaleSyntaxException.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.impl.number.CustomSymbolCurrency.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.impl.number.CustomSymbolCurrency.dat
index 7566c1dd6..7566c1dd6 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.impl.number.CustomSymbolCurrency.dat
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.impl.number.CustomSymbolCurrency.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.impl.number.DecimalFormatProperties.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.impl.number.DecimalFormatProperties.dat
index 1c6023b29..1c6023b29 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.impl.number.DecimalFormatProperties.dat
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.impl.number.DecimalFormatProperties.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.impl.number.LocalizedNumberFormatterAsFormat.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.impl.number.LocalizedNumberFormatterAsFormat.dat
index edb3d5dd1..edb3d5dd1 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.impl.number.LocalizedNumberFormatterAsFormat.dat
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.impl.number.LocalizedNumberFormatterAsFormat.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.impl.number.Properties.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.impl.number.Properties.dat
index 3e0c8db45..3e0c8db45 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.impl.number.Properties.dat
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.impl.number.Properties.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.math.BigDecimal.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.math.BigDecimal.dat
index dd4ce5221..dd4ce5221 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.math.BigDecimal.dat
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.math.BigDecimal.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.math.MathContext.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.math.MathContext.dat
index da7116766..da7116766 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.math.MathContext.dat
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.math.MathContext.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.number.SkeletonSyntaxException.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.number.SkeletonSyntaxException.dat
new file mode 100644
index 000000000..77f643059
--- /dev/null
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.number.SkeletonSyntaxException.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.ArabicShapingException.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.ArabicShapingException.dat
new file mode 100644
index 000000000..825d642e8
--- /dev/null
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.ArabicShapingException.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.ChineseDateFormat$Field.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.ChineseDateFormat$Field.dat
index 1480893fe..1480893fe 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.ChineseDateFormat$Field.dat
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.ChineseDateFormat$Field.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.ChineseDateFormat.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.ChineseDateFormat.dat
new file mode 100644
index 000000000..f85bb92ed
--- /dev/null
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.ChineseDateFormat.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.ChineseDateFormatSymbols.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.ChineseDateFormatSymbols.dat
new file mode 100644
index 000000000..f6681c6f9
--- /dev/null
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.ChineseDateFormatSymbols.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.CompactDecimalFormat.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.CompactDecimalFormat.dat
index 4795b267d..4795b267d 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.CompactDecimalFormat.dat
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.CompactDecimalFormat.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.CurrencyPluralInfo.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.CurrencyPluralInfo.dat
index ea54e21b2..ea54e21b2 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.CurrencyPluralInfo.dat
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.CurrencyPluralInfo.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.DateFormat$Field.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.DateFormat$Field.dat
index 06b87bb1b..06b87bb1b 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.DateFormat$Field.dat
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.DateFormat$Field.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.DateFormat.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.DateFormat.dat
new file mode 100644
index 000000000..27396f433
--- /dev/null
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.DateFormat.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.DateFormatSymbols.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.DateFormatSymbols.dat
new file mode 100644
index 000000000..0224350fd
--- /dev/null
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.DateFormatSymbols.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.DateIntervalFormat$SpanField.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.DateIntervalFormat$SpanField.dat
new file mode 100644
index 000000000..d1fb9f8a5
--- /dev/null
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.DateIntervalFormat$SpanField.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.DateIntervalFormat.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.DateIntervalFormat.dat
index 8560a4198..193369dba 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.DateIntervalFormat.dat
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.DateIntervalFormat.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.DateIntervalInfo$PatternInfo.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.DateIntervalInfo$PatternInfo.dat
index 25ebef5b2..25ebef5b2 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.DateIntervalInfo$PatternInfo.dat
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.DateIntervalInfo$PatternInfo.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.DateIntervalInfo.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.DateIntervalInfo.dat
index 59c719a70..546ebbb23 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.DateIntervalInfo.dat
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.DateIntervalInfo.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.DecimalFormat.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.DecimalFormat.dat
new file mode 100644
index 000000000..b9761f9ec
--- /dev/null
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.DecimalFormat.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.DecimalFormatSymbols.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.DecimalFormatSymbols.dat
index a9e8b06a1..1a9fe1e2b 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.DecimalFormatSymbols.dat
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.DecimalFormatSymbols.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.ListFormatter$Field.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.ListFormatter$Field.dat
new file mode 100644
index 000000000..d2e613616
--- /dev/null
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.ListFormatter$Field.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.ListFormatter$SpanField.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.ListFormatter$SpanField.dat
new file mode 100644
index 000000000..50e3c2b04
--- /dev/null
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.ListFormatter$SpanField.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.MeasureFormat.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.MeasureFormat.dat
index b61ad990f..3c8ca94b4 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.MeasureFormat.dat
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.MeasureFormat.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.MessageFormat$Field.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.MessageFormat$Field.dat
index a1aca43fd..a1aca43fd 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.MessageFormat$Field.dat
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.MessageFormat$Field.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.MessageFormat.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.MessageFormat.dat
index 3e8d13fe9..3e8d13fe9 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.MessageFormat.dat
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.MessageFormat.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.NumberFormat$Field.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.NumberFormat$Field.dat
index 78dbc5193..78dbc5193 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.NumberFormat$Field.dat
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.NumberFormat$Field.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.NumberFormat.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.NumberFormat.dat
index b8d94928e..6c6664bf2 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.NumberFormat.dat
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.NumberFormat.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.PluralFormat.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.PluralFormat.dat
index 6f74ae924..b60447ed2 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.PluralFormat.dat
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.PluralFormat.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.PluralRules.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.PluralRules.dat
index 8e3375ba4..8e3375ba4 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.PluralRules.dat
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.PluralRules.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.RelativeDateTimeFormatter$Field.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.RelativeDateTimeFormatter$Field.dat
new file mode 100644
index 000000000..9ff848c76
--- /dev/null
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.RelativeDateTimeFormatter$Field.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.RuleBasedNumberFormat.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.RuleBasedNumberFormat.dat
index d0b7ea93e..d0b7ea93e 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.RuleBasedNumberFormat.dat
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.RuleBasedNumberFormat.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.SelectFormat.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.SelectFormat.dat
index bbd38651c..bbd38651c 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.SelectFormat.dat
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.SelectFormat.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.SimpleDateFormat.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.SimpleDateFormat.dat
new file mode 100644
index 000000000..aa2351eed
--- /dev/null
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.SimpleDateFormat.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.StringPrepParseException.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.StringPrepParseException.dat
new file mode 100644
index 000000000..1bfd404df
--- /dev/null
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.StringPrepParseException.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.TimeUnitFormat.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.TimeUnitFormat.dat
index 4a160eccf..7cd75ffb7 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.TimeUnitFormat.dat
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.TimeUnitFormat.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.TimeZoneFormat.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.TimeZoneFormat.dat
index 247ad4fe4..247ad4fe4 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.text.TimeZoneFormat.dat
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.text.TimeZoneFormat.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.AnnualTimeZoneRule.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.AnnualTimeZoneRule.dat
index da8dfef5a..da8dfef5a 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.AnnualTimeZoneRule.dat
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.AnnualTimeZoneRule.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.BuddhistCalendar.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.BuddhistCalendar.dat
new file mode 100644
index 000000000..ba0e8603a
--- /dev/null
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.BuddhistCalendar.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.Calendar.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.Calendar.dat
index b4fd7f21a..47c3ced38 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.Calendar.dat
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.Calendar.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.ChineseCalendar.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.ChineseCalendar.dat
new file mode 100644
index 000000000..4c3a74449
--- /dev/null
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.ChineseCalendar.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.CopticCalendar.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.CopticCalendar.dat
index e3a828df6..7280bfa03 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.CopticCalendar.dat
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.CopticCalendar.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.Currency.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.Currency.dat
index 7566c1dd6..7566c1dd6 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.Currency.dat
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.Currency.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.DangiCalendar.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.DangiCalendar.dat
new file mode 100644
index 000000000..252aefbd5
--- /dev/null
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.DangiCalendar.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.DateInterval.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.DateInterval.dat
index c30bb84f4..c30bb84f4 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.DateInterval.dat
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.DateInterval.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.DateTimeRule.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.DateTimeRule.dat
index 7057de988..7057de988 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.DateTimeRule.dat
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.DateTimeRule.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.EthiopicCalendar.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.EthiopicCalendar.dat
new file mode 100644
index 000000000..d772ef323
--- /dev/null
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.EthiopicCalendar.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.GregorianCalendar.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.GregorianCalendar.dat
index 3c886337f..d1672cc87 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.GregorianCalendar.dat
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.GregorianCalendar.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.HebrewCalendar.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.HebrewCalendar.dat
new file mode 100644
index 000000000..dcb0cedf9
--- /dev/null
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.HebrewCalendar.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.ICUCloneNotSupportedException.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.ICUCloneNotSupportedException.dat
new file mode 100644
index 000000000..577df9362
--- /dev/null
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.ICUCloneNotSupportedException.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.ICUException.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.ICUException.dat
new file mode 100644
index 000000000..5d8784e72
--- /dev/null
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.ICUException.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.ICUUncheckedIOException.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.ICUUncheckedIOException.dat
new file mode 100644
index 000000000..020284881
--- /dev/null
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.ICUUncheckedIOException.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.IllformedLocaleException.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.IllformedLocaleException.dat
new file mode 100644
index 000000000..bde3c7ae2
--- /dev/null
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.IllformedLocaleException.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.IndianCalendar.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.IndianCalendar.dat
new file mode 100644
index 000000000..6ac000349
--- /dev/null
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.IndianCalendar.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.InitialTimeZoneRule.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.InitialTimeZoneRule.dat
index cc19377dd..cc19377dd 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.InitialTimeZoneRule.dat
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.InitialTimeZoneRule.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.IslamicCalendar.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.IslamicCalendar.dat
index 2c80c04e1..70ac880dc 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.IslamicCalendar.dat
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.IslamicCalendar.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.JapaneseCalendar.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.JapaneseCalendar.dat
new file mode 100644
index 000000000..00b1462a3
--- /dev/null
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.JapaneseCalendar.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.MeasureUnit.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.MeasureUnit.dat
index e97cd298a..e97cd298a 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.MeasureUnit.dat
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.MeasureUnit.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.NoUnit.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.NoUnit.dat
index e97cd298a..e97cd298a 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.NoUnit.dat
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.NoUnit.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.PersianCalendar.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.PersianCalendar.dat
new file mode 100644
index 000000000..ec332d41f
--- /dev/null
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.PersianCalendar.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.RuleBasedTimeZone.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.RuleBasedTimeZone.dat
index 9271a99ef..d69978e1a 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.RuleBasedTimeZone.dat
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.RuleBasedTimeZone.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.SimpleTimeZone.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.SimpleTimeZone.dat
index 71d8d1a4c..71d8d1a4c 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.SimpleTimeZone.dat
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.SimpleTimeZone.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.TaiwanCalendar.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.TaiwanCalendar.dat
new file mode 100644
index 000000000..750ee7f43
--- /dev/null
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.TaiwanCalendar.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.TimeArrayTimeZoneRule.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.TimeArrayTimeZoneRule.dat
index 189e4dae3..189e4dae3 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.TimeArrayTimeZoneRule.dat
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.TimeArrayTimeZoneRule.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.TimeUnit.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.TimeUnit.dat
index e97cd298a..e97cd298a 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.TimeUnit.dat
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.TimeUnit.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.TimeZone.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.TimeZone.dat
index 56e8cd29a..56e8cd29a 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.TimeZone.dat
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.TimeZone.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.ULocale.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.ULocale.dat
index b222926d5..b222926d5 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.ULocale.dat
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.ULocale.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.UResourceTypeMismatchException.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.UResourceTypeMismatchException.dat
new file mode 100644
index 000000000..c273bec33
--- /dev/null
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.UResourceTypeMismatchException.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.VTimeZone.dat b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.VTimeZone.dat
index 1f69b2990..1f69b2990 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_62.1/android.icu.util.VTimeZone.dat
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/serializable/data/ICU_67.1/android.icu.util.VTimeZone.dat
Binary files differ
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/timezone/TimeZoneTest.java b/android_icu4j/src/main/tests/android/icu/dev/test/timezone/TimeZoneTest.java
index 6900799be..25bd35cb6 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/timezone/TimeZoneTest.java
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/timezone/TimeZoneTest.java
@@ -1790,7 +1790,6 @@ public class TimeZoneTest extends TestFmwk
{"America/Sao_Paulo", "en", Boolean.FALSE, TZSHORT, "GMT-3"/*"BRT"*/},
{"America/Sao_Paulo", "en", Boolean.FALSE, TZLONG, "Brasilia Standard Time"},
-
// Per https://mm.icann.org/pipermail/tz-announce/2019-July/000056.html
// Brazil has canceled DST and will stay on standard time indefinitely.
// {"America/Sao_Paulo", "en", Boolean.TRUE, TZSHORT, "GMT-2"/*"BRST"*/},
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/util/CurrencyTest.java b/android_icu4j/src/main/tests/android/icu/dev/test/util/CurrencyTest.java
index bfb813a67..f29100cda 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/util/CurrencyTest.java
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/util/CurrencyTest.java
@@ -55,6 +55,7 @@ public class CurrencyTest extends TestFmwk {
Currency usd = Currency.getInstance("USD");
/*int hash = */usd.hashCode();
Currency jpy = Currency.getInstance("JPY");
+ Currency jpy2 = Currency.getInstance("jpy");
if (usd.equals(jpy)) {
errln("FAIL: USD == JPY");
}
@@ -67,6 +68,12 @@ public class CurrencyTest extends TestFmwk {
if (!usd.equals(usd)) {
errln("FAIL: USD != USD");
}
+ if (!jpy.equals(jpy2)) {
+ errln("FAIL: JPY != jpy");
+ }
+ if (!jpy2.equals(jpy)) {
+ errln("FAIL: jpy != JPY");
+ }
try {
Currency nullCurrency = Currency.getInstance((String)null);
@@ -90,7 +97,7 @@ public class CurrencyTest extends TestFmwk {
}
try {
- usd.getName(ULocale.US, 5, new boolean[1]);
+ usd.getName(ULocale.US, 6, new boolean[1]);
errln("expected getName with invalid type parameter to throw exception");
}
catch (Exception e) {
@@ -173,7 +180,7 @@ public class CurrencyTest extends TestFmwk {
Locale[] locs = Currency.getAvailableLocales();
found = false;
for (int i = 0; i < locs.length; ++i) {
- if (locs[i].equals(fu_FU)) {
+ if (locs[i].equals(fu_FU.toLocale())) {
found = true;
break;
}
@@ -242,26 +249,44 @@ public class CurrencyTest extends TestFmwk {
}
@Test
- public void test20484_NarrowSymbolFallback() {
+ public void testCurrencyVariants() {
Object[][] cases = new Object[][] {
- {"en-US", "CAD", "CA$", "$"},
- {"en-US", "CDF", "CDF", "CDF"},
- {"sw-CD", "CDF", "FC", "FC"},
- {"en-US", "GEL", "GEL", "₾"},
- {"ka-GE", "GEL", "₾", "₾"},
- {"ka", "GEL", "₾", "₾"},
+ {"en-US", "CAD", "CA$", "$", "CA$", "CA$"},
+ {"en-US", "CDF", "CDF", "CDF", "CDF", "CDF"},
+ {"sw-CD", "CDF", "FC", "FC", "FC", "FC"},
+ {"en-US", "GEL", "GEL", "₾", "GEL", "GEL"},
+ {"ka-GE", "GEL", "₾", "₾", "₾", "₾"},
+ {"ka", "GEL", "₾", "₾", "₾", "₾"},
+ {"zh-TW", "TWD", "$", "$", "NT$", "$"},
+ {"ccp", "TRY", "TRY", "₺", "TRY", "TL"}
};
for (Object[] cas : cases) {
ULocale locale = new ULocale((String) cas[0]);
String isoCode = (String) cas[1];
String expectedShort = (String) cas[2];
String expectedNarrow = (String) cas[3];
+ String expectedFormal = (String) cas[4];
+ String expectedVariant = (String) cas[5];
CurrencyDisplayNames cdn = CurrencyDisplayNames.getInstance(locale);
assertEquals("Short symbol: " + locale + ": " + isoCode,
expectedShort, cdn.getSymbol(isoCode));
assertEquals("Narrow symbol: " + locale + ": " + isoCode,
expectedNarrow, cdn.getNarrowSymbol(isoCode));
+ assertEquals("Formal symbol: " + locale + ": " + isoCode,
+ expectedFormal, cdn.getFormalSymbol(isoCode));
+ assertEquals("Variant symbol: " + locale + ": " + isoCode,
+ expectedVariant, cdn.getVariantSymbol(isoCode));
+
+ Currency currency = Currency.getInstance(isoCode);
+ assertEquals("Old API, Short symbol: " + locale + ": " + isoCode,
+ expectedShort, currency.getName(locale, Currency.SYMBOL_NAME, null));
+ assertEquals("Old API, Narrow symbol: " + locale + ": " + isoCode,
+ expectedNarrow, currency.getName(locale, Currency.NARROW_SYMBOL_NAME, null));
+ assertEquals("Old API, Formal symbol: " + locale + ": " + isoCode,
+ expectedFormal, currency.getName(locale, Currency.FORMAL_SYMBOL_NAME, null));
+ assertEquals("Old API, Variant symbol: " + locale + ": " + isoCode,
+ expectedVariant, currency.getName(locale, Currency.VARIANT_SYMBOL_NAME, null));
}
}
@@ -544,7 +569,7 @@ public class CurrencyTest extends TestFmwk {
assertTrue("More than one currency for switzerland", currencies.size() > 1);
assertEquals(
"With tender",
- Arrays.asList(new String[] {"CHF", "CHE", "CHW"}),
+ Arrays.asList(new String[] {"CHF"}), // no longer include currencies with tender=false
metainfo.currencies(filter.withTender()));
}
@@ -652,8 +677,8 @@ public class CurrencyTest extends TestFmwk {
{ "eo_AO", "1969-12-31" },
{ "eo_DE@currency=DEM", "2000-12-23", "EUR", "DEM" },
{ "eo-DE-u-cu-dem", "2000-12-23", "EUR", "DEM" },
- { "en_US", null, "USD", "USN" },
- { "en_US_Q", null, "USD", "USN" },
+ { "en_US", null, "USD" }, // no longer include currencies with tender=false
+ { "en_US_Q", null, "USD" }, // no longer include currencies with tender=false
};
DateFormat fmt = new SimpleDateFormat("yyyy-MM-dd", Locale.US);
@@ -741,20 +766,20 @@ public class CurrencyTest extends TestFmwk {
final String[][] PREFERRED = {
{"root", },
{"und", },
- {"und_ZZ", "XAG", "XAU", "XBA", "XBB", "XBC", "XBD", "XDR", "XPD", "XPT", "XSU", "XTS", "XUA", "XXX"},
- {"en_US", "USD", "USN"},
+ {"und_ZZ", }, // no longer include currencies with tender=false
+ {"en_US", "USD"}, // no longer include currencies with tender=false
{"en_029", },
{"en_TH", "THB"},
{"de", "EUR"},
{"de_DE", "EUR"},
- {"de_ZZ", "XAG", "XAU", "XBA", "XBB", "XBC", "XBD", "XDR", "XPD", "XPT", "XSU", "XTS", "XUA", "XXX"},
+ {"de_ZZ", }, // no longer include currencies with tender=false
{"ar", "EGP"},
{"ar_PS", "ILS", "JOD"},
- {"en@currency=CAD", "USD", "USN"},
+ {"en@currency=CAD", "USD"}, // no longer include currencies with tender=false
{"fr@currency=ZZZ", "EUR"},
{"de_DE@currency=DEM", "EUR"},
{"en_US@rg=THZZZZ", "THB"},
- {"de@rg=USZZZZ", "USD", "USN"},
+ {"de@rg=USZZZZ", "USD"}, // no longer include currencies with tender=false
{"en_US@currency=CAD;rg=THZZZZ", "THB"},
};
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/util/DebugUtilitiesData.java b/android_icu4j/src/main/tests/android/icu/dev/test/util/DebugUtilitiesData.java
index 29e4f17b2..0f609cc57 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/util/DebugUtilitiesData.java
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/util/DebugUtilitiesData.java
@@ -14,7 +14,7 @@ import android.icu.testsharding.MainTestShard;
@MainTestShard
public class DebugUtilitiesData extends Object {
- public static final String ICU4C_VERSION="66.1";
+ public static final String ICU4C_VERSION="67.1";
public static final int UDebugEnumType = 0;
public static final int UCalendarDateFields = 1;
public static final int UCalendarMonths = 2;
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/util/ICUResourceBundleTest.java b/android_icu4j/src/main/tests/android/icu/dev/test/util/ICUResourceBundleTest.java
index 939323380..b172b5c7a 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/util/ICUResourceBundleTest.java
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/util/ICUResourceBundleTest.java
@@ -704,14 +704,13 @@ public final class ICUResourceBundleTest extends TestFmwk {
Set<String> localCountryExceptions = new HashSet<String>();
if (logKnownIssue("cldrbug:8903",
- "No localized region name for lrc_IQ, lrc_IR, nus_SS, nds_DE, ti_ER, ti_ET")) {
+ "No localized region name for lrc_IQ, lrc_IR, nus_SS, nds_DE, su_Latn_ID")) {
localCountryExceptions.add("lrc_IQ");
localCountryExceptions.add("lrc_IR");
localCountryExceptions.add("nus_SS");
localCountryExceptions.add("nds_DE");
localCountryExceptions.add("nds_NL");
- localCountryExceptions.add("ti_ER");
- localCountryExceptions.add("ti_ET");
+ localCountryExceptions.add("su_Latn_ID");
}
Set<String> localLangExceptions = new HashSet<String>();
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/util/LocaleBuilderTest.java b/android_icu4j/src/main/tests/android/icu/dev/test/util/LocaleBuilderTest.java
index 6cba79c7a..90b7a6f21 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/util/LocaleBuilderTest.java
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/util/LocaleBuilderTest.java
@@ -76,7 +76,7 @@ public class LocaleBuilderTest extends TestFmwk {
{"U", "ja_JP@calendar=japanese;currency=JPY", "L", "ko", "T", "ko-JP-u-ca-japanese-cu-jpy", "ko_JP@calendar=japanese;currency=jpy"},
{"U", "ja_JP@calendar=japanese;currency=JPY", "K", "ca", null, "T", "ja-JP-u-cu-jpy", "ja_JP@currency=jpy"},
{"U", "ja_JP@calendar=japanese;currency=JPY", "E", "u", "attr1-ca-gregory", "T", "ja-JP-u-attr1-ca-gregory", "ja_JP@attribute=attr1;calendar=gregorian"},
- {"U", "en@colnumeric=yes", "K", "kn", "", "T", "en-u-kn-true", "en@colnumeric=yes"},
+ {"U", "en@colnumeric=yes", "K", "kn", "", "T", "en-u-kn", "en@colnumeric=yes"},
{"L", "th", "R", "th", "K", "nu", "thai", "T", "th-TH-u-nu-thai", "th_TH@numbers=thai"},
{"U", "zh_Hans", "R", "sg", "K", "ca", "badcalendar", "X"},
{"U", "zh_Hans", "R", "sg", "K", "cal", "gregory", "X"},
@@ -90,18 +90,18 @@ public class LocaleBuilderTest extends TestFmwk {
// However, once the legacy keyword is translated back to BCP 47 u extension, key "0a" is unknown,
// so "yes" is preserved - not mapped to "true". We could change the code to automatically transform
// "yes" to "true", but it will break roundtrip conversion if BCP 47 u extension has "0a-yes".
- {"L", "en", "E", "u", "bbb-aaa-0a", "T", "en-u-aaa-bbb-0a-yes", "en@0a=yes;attribute=aaa-bbb"},
+ {"L", "en", "E", "u", "bbb-aaa-0a", "T", "en-u-aaa-bbb-0a", "en@0a=yes;attribute=aaa-bbb"},
{"L", "fr", "R", "FR", "P", "Yoshito-ICU", "T", "fr-FR-x-yoshito-icu", "fr_FR@x=yoshito-icu"},
{"L", "ja", "R", "jp", "K", "ca", "japanese", "T", "ja-JP-u-ca-japanese", "ja_JP@calendar=japanese"},
{"K", "co", "PHONEBK", "K", "ca", "gregory", "L", "De", "T", "de-u-ca-gregory-co-phonebk", "de@calendar=gregorian;collation=phonebook"},
{"E", "o", "OPQR", "E", "a", "aBcD", "T", "und-a-abcd-o-opqr", "@a=abcd;o=opqr"},
{"E", "u", "nu-thai-ca-gregory", "L", "TH", "T", "th-u-ca-gregory-nu-thai", "th@calendar=gregorian;numbers=thai"},
{"L", "en", "K", "tz", "usnyc", "R", "US", "T", "en-US-u-tz-usnyc", "en_US@timezone=America/New_York"},
- {"L", "de", "K", "co", "phonebk", "K", "ks", "level1", "K", "kk", "true", "T", "de-u-co-phonebk-kk-true-ks-level1", "de@collation=phonebook;colnormalization=yes;colstrength=primary"},
+ {"L", "de", "K", "co", "phonebk", "K", "ks", "level1", "K", "kk", "true", "T", "de-u-co-phonebk-kk-ks-level1", "de@collation=phonebook;colnormalization=yes;colstrength=primary"},
{"L", "en", "R", "US", "K", "ca", "gregory", "T", "en-US-u-ca-gregory", "en_US@calendar=gregorian"},
{"L", "en", "R", "US", "K", "cal", "gregory", "X"},
{"L", "en", "R", "US", "K", "ca", "gregorian", "X"},
- {"L", "en", "R", "US", "K", "kn", "", "T", "en-US-u-kn-true", "en_US@colnumeric=yes"},
+ {"L", "en", "R", "US", "K", "kn", "", "T", "en-US-u-kn", "en_US@colnumeric=yes"},
{"B", "de-DE-u-co-phonebk", "C", "L", "pt", "T", "pt", "pt"},
{"B", "ja-jp-u-ca-japanese", "N", "T", "ja-JP", "ja_JP"},
{"B", "es-u-def-abc-co-trad", "A", "hij", "D", "def", "T", "es-u-abc-hij-co-trad", "es@attribute=abc-hij;collation=traditional"},
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/util/LocaleMatcherTest.java b/android_icu4j/src/main/tests/android/icu/dev/test/util/LocaleMatcherTest.java
index a7dde6a94..5afb9806b 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/util/LocaleMatcherTest.java
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/util/LocaleMatcherTest.java
@@ -197,7 +197,7 @@ public class LocaleMatcherTest extends TestFmwk {
assertEquals("getBestMatchResult(ja_JP).supp",
"en_GB", locString(result.getSupportedULocale()));
assertEquals("getBestMatchResult(ja_JP).suppIndex",
- 1, result.getSupportedIndex());
+ -1, result.getSupportedIndex());
}
@Test
@@ -642,6 +642,21 @@ public class LocaleMatcherTest extends TestFmwk {
}
@Test
+ public void testDirection() {
+ List<ULocale> desired = Arrays.asList(new ULocale("arz-EG"), new ULocale("nb-DK"));
+ LocaleMatcher.Builder builder =
+ LocaleMatcher.builder().setSupportedLocales("ar, nn");
+ // arz is a close one-way match to ar, and the region matches.
+ // (Egyptian Arabic vs. Arabic)
+ LocaleMatcher withOneWay = builder.build();
+ assertEquals("with one-way", "ar", withOneWay.getBestMatch(desired).toString());
+ // nb is a less close two-way match to nn, and the regions differ.
+ // (Norwegian Bokmal vs. Nynorsk)
+ LocaleMatcher onlyTwoWay = builder.setDirection(LocaleMatcher.Direction.ONLY_TWO_WAY).build();
+ assertEquals("only two-way", "nn", onlyTwoWay.getBestMatch(desired).toString());
+ }
+
+ @Test
public void testCanonicalize() {
LocaleMatcher matcher = LocaleMatcher.builder().build();
assertEquals("bh --> bho", new ULocale("bho"), matcher.canonicalize(new ULocale("bh")));
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/util/ULocaleTest.java b/android_icu4j/src/main/tests/android/icu/dev/test/util/ULocaleTest.java
index dc9abdd6c..7c1f639e5 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/util/ULocaleTest.java
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/util/ULocaleTest.java
@@ -23,7 +23,6 @@ import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
-import java.util.TreeMap;
import java.util.TreeSet;
import java.util.regex.Pattern;
@@ -35,7 +34,6 @@ import org.junit.runners.JUnit4;
import android.icu.dev.test.TestFmwk;
import android.icu.dev.test.TestUtil;
import android.icu.dev.test.TestUtil.JavaVendor;
-import android.icu.lang.UCharacter;
import android.icu.text.DateFormat;
import android.icu.text.DecimalFormat;
import android.icu.text.DisplayContext;
@@ -47,6 +45,7 @@ import android.icu.text.SimpleDateFormat;
import android.icu.util.Calendar;
import android.icu.util.IllformedLocaleException;
import android.icu.util.LocaleData;
+import android.icu.util.LocalePriorityList;
import android.icu.util.ULocale;
import android.icu.util.ULocale.Builder;
import android.icu.util.ULocale.Category;
@@ -676,10 +675,10 @@ public class ULocaleTest extends TestFmwk {
{"x-piglatin", "", "ML", "", "x-piglatin_ML.MBE", "x-piglatin_ML.MBE", "x-piglatin_ML"}, /* Multibyte English */
{"i-cherokee", "","US", "", "i-Cherokee_US.utf7", "i-cherokee_US.utf7", "i-cherokee_US"},
{"x-filfli", "", "MT", "FILFLA", "x-filfli_MT_FILFLA.gb-18030", "x-filfli_MT_FILFLA.gb-18030", "x-filfli_MT_FILFLA"},
- {"no", "", "NO", "NY_B", "no-no-ny.utf32@B", "no_NO_NY.utf32@B", "no_NO_NY_B"},
- {"no", "", "NO", "B", "no-no.utf32@B", "no_NO.utf32@B", "no_NO_B"},
- {"no", "", "", "NY", "no__ny", "no__NY", null},
- {"no", "", "", "NY", "no@ny", "no@ny", "no__NY"},
+ {"no", "", "NO", "NY_B", "no-no-ny.utf32@B", "no_NO_NY.utf32@B", "nb_NO_NY_B"},
+ {"no", "", "NO", "B", "no-no.utf32@B", "no_NO.utf32@B", "nb_NO_B"},
+ {"no", "", "", "NY", "no__ny", "no__NY", "nb__NY"},
+ {"no", "", "", "NY", "no@ny", "no@ny", "nb__NY"},
{"el", "Latn", "", "", "el-latn", "el_Latn", null},
{"en", "Cyrl", "RU", "", "en-cyrl-ru", "en_Cyrl_RU", null},
{"qq", "Qqqq", "QQ", "QQ", "qq_Qqqq_QQ_QQ", "qq_Qqqq_QQ_QQ", null},
@@ -896,13 +895,13 @@ public class ULocaleTest extends TestFmwk {
public void TestCanonicalization(){
final String[][]testCases = new String[][]{
{ "zh@collation=pinyin", "zh@collation=pinyin", "zh@collation=pinyin" },
- { "zh_CN@collation=pinyin", "zh_CN@collation=pinyin", "zh_CN@collation=pinyin" },
- { "zh_CN_CA@collation=pinyin", "zh_CN_CA@collation=pinyin", "zh_CN_CA@collation=pinyin" },
+ { "zh_CN@collation=pinyin", "zh_CN@collation=pinyin", "zh_Hans_CN@collation=pinyin" },
+ { "zh_CN_CA@collation=pinyin", "zh_CN_CA@collation=pinyin", "zh_Hans_CN_CA@collation=pinyin" },
{ "en_US_POSIX", "en_US_POSIX", "en_US_POSIX" },
{ "hy_AM_REVISED", "hy_AM_REVISED", "hy_AM_REVISED" },
- { "no_NO_NY", "no_NO_NY", "no_NO_NY" /* not: "nn_NO" [alan ICU3.0] */ },
- { "no@ny", null, "no__NY" /* not: "nn" [alan ICU3.0] */ }, /* POSIX ID */
- { "no-no.utf32@B", null, "no_NO_B" /* not: "nb_NO_B" [alan ICU3.0] */ }, /* POSIX ID */
+ { "no_NO_NY", "no_NO_NY", "nb_NO_NY" /* not: "nn_NO" [alan ICU3.0] */ },
+ { "no@ny", null, "nb__NY" /* not: "nn" [alan ICU3.0] */ }, /* POSIX ID */
+ { "no-no.utf32@B", null, "nb_NO_B" /* not: "nb_NO_B" [alan ICU3.0] */ }, /* POSIX ID */
{ "en-BOONT", "en__BOONT", "en__BOONT" }, /* registered name */
{ "de-1901", "de__1901", "de__1901" }, /* registered name */
{ "de-1906", "de__1906", "de__1906" }, /* registered name */
@@ -913,7 +912,7 @@ public class ULocaleTest extends TestFmwk {
{ "x-piglatin_ML.MBE", null, "x-piglatin_ML" },
{ "i-cherokee_US.utf7", null, "i-cherokee_US" },
{ "x-filfli_MT_FILFLA.gb-18030", null, "x-filfli_MT_FILFLA" },
- { "no-no-ny.utf8@B", null, "no_NO_NY_B" /* not: "nn_NO" [alan ICU3.0] */ }, /* @ ignored unless variant is empty */
+ { "no-no-ny.utf8@B", null, "nb_NO_NY_B" /* not: "nn_NO" [alan ICU3.0] */ }, /* @ ignored unless variant is empty */
/* fleshing out canonicalization */
/* sort keywords, ';' is separator so not present at end in canonical form */
@@ -922,7 +921,7 @@ public class ULocaleTest extends TestFmwk {
{ "en_Hant_IL_VALLEY_GIRL@calendar=Japanese;currency=EUR", "en_Hant_IL_VALLEY_GIRL@calendar=Japanese;currency=EUR", "en_Hant_IL_VALLEY_GIRL@calendar=Japanese;currency=EUR" },
/* norwegian is just too weird, if we handle things in their full generality */
/* this is a negative test to show that we DO NOT handle 'lang=no,var=NY' specially. */
- { "no-Hant-GB_NY@currency=$$$", "no_Hant_GB_NY@currency=$$$", "no_Hant_GB_NY@currency=$$$" /* not: "nn_Hant_GB@currency=$$$" [alan ICU3.0] */ },
+ { "no-Hant-GB_NY@currency=$$$", "no_Hant_GB_NY@currency=$$$", "nb_Hant_GB_NY@currency=$$$" /* not: "nn_Hant_GB@currency=$$$" [alan ICU3.0] */ },
/* test cases reflecting internal resource bundle usage */
/* root is just a language */
@@ -960,14 +959,14 @@ public class ULocaleTest extends TestFmwk {
{ "hi__DIRECT", "hi__DIRECT", "hi__DIRECT" },
{ "ja_JP_TRADITIONAL", "ja_JP_TRADITIONAL", "ja_JP_TRADITIONAL" },
{ "th_TH_TRADITIONAL", "th_TH_TRADITIONAL", "th_TH_TRADITIONAL" },
- { "zh_TW_STROKE", "zh_TW_STROKE", "zh_TW_STROKE" },
+ { "zh_TW_STROKE", "zh_TW_STROKE", "zh_Hant_TW_STROKE" },
{ "zh__PINYIN", "zh__PINYIN", "zh__PINYIN" },
{ "qz-qz@Euro", null, "qz_QZ_EURO" }, /* qz-qz uses private use iso codes */
{ "sr-SP-Cyrl", "sr_SP_CYRL", "sr_SP_CYRL" }, /* .NET name */
{ "sr-SP-Latn", "sr_SP_LATN", "sr_SP_LATN" }, /* .NET name */
- { "sr_YU_CYRILLIC", "sr_YU_CYRILLIC", "sr_YU_CYRILLIC" }, /* Linux name */
- { "uz-UZ-Cyrl", "uz_UZ_CYRL", "uz_UZ_CYRL" }, /* .NET name */
- { "uz-UZ-Latn", "uz_UZ_LATN", "uz_UZ_LATN" }, /* .NET name */
+ { "sr_YU_CYRILLIC", "sr_YU_CYRILLIC", "sr_RS_CYRILLIC" }, /* Linux name */
+ { "uz-UZ-Cyrl", "uz_UZ_CYRL", "uz_Latn_UZ_CYRL" }, /* .NET name */
+ { "uz-UZ-Latn", "uz_UZ_LATN", "uz_Latn_UZ_LATN" }, /* .NET name */
{ "zh-CHS", "zh_CHS", "zh_CHS" }, /* .NET name */
{ "zh-CHT", "zh_CHT", "zh_CHT" }, /* .NET name This may change back to zh_Hant */
/* PRE_EURO and EURO conversions don't affect other keywords */
@@ -1593,15 +1592,15 @@ public class ULocaleTest extends TestFmwk {
/*3*/ { null, "true" },
/*4*/ { "es", "false" },
/*5*/ { "de", "false" },
- /*6*/ { "zh_TW", "false" },
- /*7*/ { "zh", "true" },
+ /*6*/ { "zh_Hant_TW", "true" },
+ /*7*/ { "zh_Hant", "true" },
};
private static final String ACCEPT_LANGUAGE_HTTP[] = {
/*0*/ "mt-mt, ja;q=0.76, en-us;q=0.95, en;q=0.92, en-gb;q=0.89, fr;q=0.87, iu-ca;q=0.84, iu;q=0.82, ja-jp;q=0.79, mt;q=0.97, de-de;q=0.74, de;q=0.71, es;q=0.68, it-it;q=0.66, it;q=0.63, vi-vn;q=0.61, vi;q=0.58, nl-nl;q=0.55, nl;q=0.53, th-th-traditional;q=.01",
/*1*/ "ja;q=0.5, en;q=0.8, tlh",
/*2*/ "en-zzz, de-lx;q=0.8",
- /*3*/ "mga-ie;q=0.9, tlh",
+ /*3*/ "mga-ie;q=0.9, sux",
/*4*/ "xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, "+
"xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, "+
"xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, "+
@@ -1613,16 +1612,16 @@ public class ULocaleTest extends TestFmwk {
"xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, "+
"xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, "+
"es",
- /*5*/ "de;q=.9, fr;q=.9, xxx-yyy, sr;q=.8",
- /*6*/ "zh-tw",
- /*7*/ "zh-hant-cn",
+ /*5*/ "de;q=.9, fr;q=.9, xxx-yyy, sr;q=.8",
+ /*6*/ "zh-tw",
+ /*7*/ "zh-hant-cn",
};
@Test
public void TestAcceptLanguage() {
for(int i = 0 ; i < (ACCEPT_LANGUAGE_HTTP.length); i++) {
- Boolean expectBoolean = new Boolean(ACCEPT_LANGUAGE_TESTS[i][1]);
+ Boolean expectBoolean = Boolean.valueOf(ACCEPT_LANGUAGE_TESTS[i][1]);
String expectLocale=ACCEPT_LANGUAGE_TESTS[i][0];
logln("#" + i + ": expecting: " + expectLocale + " (" + expectBoolean + ")");
@@ -1630,128 +1629,50 @@ public class ULocaleTest extends TestFmwk {
boolean r[] = { false };
ULocale n = ULocale.acceptLanguage(ACCEPT_LANGUAGE_HTTP[i], r);
if((n==null)&&(expectLocale!=null)) {
- errln("result was null! line #" + i);
+ errln("#" + i + ": result was null!");
continue;
}
if(((n==null)&&(expectLocale==null)) || (n.toString().equals(expectLocale))) {
- logln(" locale: OK." );
+ logln("#" + i + ": locale: OK." );
} else {
- errln("expected " + expectLocale + " but got " + n.toString());
+ errln("#" + i + ": locale: expected " + expectLocale + " but got " + n);
}
- if(expectBoolean.equals(new Boolean(r[0]))) {
- logln(" bool: OK.");
+ Boolean actualBoolean = Boolean.valueOf(r[0]);
+ if(expectBoolean.equals(actualBoolean)) {
+ logln("#" + i + ": fallback: OK.");
} else {
- errln("bool: not OK, was " + new Boolean(r[0]).toString() + " expected " + expectBoolean.toString());
+ errln("#" + i + ": fallback: was " + actualBoolean + " expected " + expectBoolean);
}
}
}
- private ULocale[] StringToULocaleArray(String acceptLanguageList){
- //following code is copied from
- //ULocale.acceptLanguage(String acceptLanguageList, ULocale[] availableLocales, boolean[] fallback)
- class ULocaleAcceptLanguageQ implements Comparable {
- private double q;
- private double serial;
- public ULocaleAcceptLanguageQ(double theq, int theserial) {
- q = theq;
- serial = theserial;
- }
- @Override
- public int compareTo(Object o) {
- ULocaleAcceptLanguageQ other = (ULocaleAcceptLanguageQ) o;
- if(q > other.q) { // reverse - to sort in descending order
- return -1;
- } else if(q < other.q) {
- return 1;
- }
- if(serial < other.serial) {
- return -1;
- } else if(serial > other.serial) {
- return 1;
- } else {
- return 0; // same object
- }
- }
- }
-
- // 1st: parse out the acceptLanguageList into an array
-
- TreeMap map = new TreeMap();
-
- final int l = acceptLanguageList.length();
- int n;
- for(n=0;n<l;n++) {
- int itemEnd = acceptLanguageList.indexOf(',',n);
- if(itemEnd == -1) {
- itemEnd = l;
- }
- int paramEnd = acceptLanguageList.indexOf(';',n);
- double q = 1.0;
-
- if((paramEnd != -1) && (paramEnd < itemEnd)) {
- /* semicolon (;) is closer than end (,) */
- int t = paramEnd + 1;
- while(UCharacter.isWhitespace(acceptLanguageList.charAt(t))) {
- t++;
- }
- if(acceptLanguageList.charAt(t)=='q') {
- t++;
- }
- while(UCharacter.isWhitespace(acceptLanguageList.charAt(t))) {
- t++;
- }
- if(acceptLanguageList.charAt(t)=='=') {
- t++;
- }
- while(UCharacter.isWhitespace(acceptLanguageList.charAt(t))) {
- t++;
- }
- try {
- String val = acceptLanguageList.substring(t,itemEnd).trim();
- q = Double.parseDouble(val);
- } catch (NumberFormatException nfe) {
- q = 1.0;
- }
- } else {
- q = 1.0; //default
- paramEnd = itemEnd;
- }
-
- String loc = acceptLanguageList.substring(n,paramEnd).trim();
- int serial = map.size();
- ULocaleAcceptLanguageQ entry = new ULocaleAcceptLanguageQ(q,serial);
- map.put(entry, new ULocale(ULocale.canonicalize(loc))); // sort in reverse order.. 1.0, 0.9, 0.8 .. etc
- n = itemEnd; // get next item. (n++ will skip over delimiter)
- }
-
- // 2. pull out the map
- ULocale acceptList[] = (ULocale[])map.values().toArray(new ULocale[map.size()]);
- return acceptList;
- }
-
@Test
public void TestAcceptLanguage2() {
for(int i = 0 ; i < (ACCEPT_LANGUAGE_HTTP.length); i++) {
- Boolean expectBoolean = new Boolean(ACCEPT_LANGUAGE_TESTS[i][1]);
+ Boolean expectBoolean = Boolean.valueOf(ACCEPT_LANGUAGE_TESTS[i][1]);
String expectLocale=ACCEPT_LANGUAGE_TESTS[i][0];
logln("#" + i + ": expecting: " + expectLocale + " (" + expectBoolean + ")");
boolean r[] = { false };
- ULocale n = ULocale.acceptLanguage(StringToULocaleArray(ACCEPT_LANGUAGE_HTTP[i]), r);
+ Set<ULocale> desiredSet =
+ LocalePriorityList.add(ACCEPT_LANGUAGE_HTTP[i]).build().getULocales();
+ ULocale[] desiredArray = desiredSet.toArray(new ULocale[desiredSet.size()]);
+ ULocale n = ULocale.acceptLanguage(desiredArray, r);
if((n==null)&&(expectLocale!=null)) {
- errln("result was null! line #" + i);
+ errln("#" + i + ": result was null!");
continue;
}
if(((n==null)&&(expectLocale==null)) || (n.toString().equals(expectLocale))) {
- logln(" locale: OK." );
+ logln("#" + i + ": locale: OK.");
} else {
- errln("expected " + expectLocale + " but got " + n.toString());
+ errln("#" + i + ": expected " + expectLocale + " but got " + n.toString());
}
- if(expectBoolean.equals(new Boolean(r[0]))) {
- logln(" bool: OK.");
+ Boolean actualBoolean = Boolean.valueOf(r[0]);
+ if(expectBoolean.equals(actualBoolean)) {
+ logln("#" + i + ": fallback: OK.");
} else {
- errln("bool: not OK, was " + new Boolean(r[0]).toString() + " expected " + expectBoolean.toString());
+ errln("#" + i + ": fallback: was " + actualBoolean + " expected " + expectBoolean);
}
}
}
@@ -4109,8 +4030,8 @@ public class ULocaleTest extends TestFmwk {
{"aa_BB_CYRL", "aa-BB-x-lvariant-cyrl"},
{"en_US_1234", "en-US-1234"},
{"en_US_VARIANTA_VARIANTB", "en-US-varianta-variantb"},
- {"en_US_VARIANTB_VARIANTA", "en-US-variantb-varianta"},
- {"ja__9876_5432", "ja-9876-5432"},
+ {"en_US_VARIANTB_VARIANTA", "en-US-varianta-variantb"}, /* ICU-20478 */
+ {"ja__9876_5432", "ja-5432-9876"}, /* ICU-20478 */
{"zh_Hant__VAR", "zh-Hant-x-lvariant-var"},
{"es__BADVARIANT_GOODVAR", "es"},
{"es__GOODVAR_BAD_BADVARIANT", "es-goodvar-x-lvariant-bad"},
@@ -4134,6 +4055,16 @@ public class ULocaleTest extends TestFmwk {
{"en@a=bar;attribute=baz;calendar=islamic-civil;x=u-foo", "en-a-bar-u-baz-ca-islamic-civil-x-u-foo"},
/* ICU-20320*/
{"en@9=efg;a=baz", "en-9-efg-a-baz"},
+ /* ICU-20478 */
+ {"sl__ROZAJ_BISKE_1994", "sl-1994-biske-rozaj"},
+ {"en__SCOUSE_FONIPA", "en-fonipa-scouse"},
+ /* ICU-20310 */
+ {"en-u-kn-true", "en-u-kn"},
+ {"en-u-kn", "en-u-kn"},
+ {"de-u-co-yes", "de-u-co"},
+ {"de-u-co", "de-u-co"},
+ {"de@collation=yes", "de-u-co"},
+ {"cmn-hans-cn-u-ca-t-ca-x-t-u", "cmn-Hans-CN-t-ca-u-ca-x-t-u"},
};
for (int i = 0; i < locale_to_langtag.length; i++) {
@@ -4231,7 +4162,7 @@ public class ULocaleTest extends TestFmwk {
{"bogus", "bogus", NOERROR},
{"boguslang", "", Integer.valueOf(0)},
{"EN-lATN-us", "en_Latn_US", NOERROR},
- {"und-variant-1234", "__VARIANT_1234", NOERROR},
+ {"und-variant-1234", "__1234_VARIANT", NOERROR}, /* ICU-20478 */
{"und-varzero-var1-vartwo", "__VARZERO", Integer.valueOf(12)},
{"en-u-ca-gregory", "en@calendar=gregorian", NOERROR},
{"en-U-cu-USD", "en@currency=usd", NOERROR},
@@ -4277,6 +4208,16 @@ public class ULocaleTest extends TestFmwk {
/* #20410 */
{"art-lojban-x-0", "jbo@x=0", NOERROR},
{"zh-xiang-u-nu-thai-x-0", "hsn@numbers=thai;x=0", NOERROR},
+ /* ICU-20478 */
+ {"ja-9876-5432", "ja__5432_9876", NOERROR},
+ {"en-US-variantb-varianta", "en_US_VARIANTA_VARIANTB", NOERROR},
+ {"en-US-varianta-variantb", "en_US_VARIANTA_VARIANTB", NOERROR},
+ {"sl-rozaj-biske-1994", "sl__1994_BISKE_ROZAJ", NOERROR},
+ {"sl-biske-rozaj-1994", "sl__1994_BISKE_ROZAJ", NOERROR},
+ {"sl-biske-1994-rozaj", "sl__1994_BISKE_ROZAJ", NOERROR},
+ {"sl-1994-biske-rozaj", "sl__1994_BISKE_ROZAJ", NOERROR},
+ {"en-fonipa-scouse", "en__FONIPA_SCOUSE", NOERROR},
+ {"en-scouse-fonipa", "en__FONIPA_SCOUSE", NOERROR},
};
for (int i = 0; i < langtag_to_locale.length; i++) {
@@ -5120,4 +5061,101 @@ public class ULocaleTest extends TestFmwk {
Assert.assertEquals(displayName, locale_tag.getDisplayName(displayLocale));
Assert.assertEquals(displayName, locale_build.getDisplayName(displayLocale));
}
+
+ @Test
+ public void Test20900() {
+ final String [][] testData = new String[][]{
+ {"art-lojban", "jbo"},
+ {"zh-guoyu", "zh"},
+ {"zh-hakka", "hak"},
+ {"zh-xiang", "hsn"},
+ {"zh-min-nan", "nan"},
+ {"zh-gan", "gan"},
+ {"zh-yue", "yue"},
+ };
+ for (int row=0;row<testData.length;row++) {
+ ULocale loc = ULocale.createCanonical(testData[row][0]);
+ Assert.assertEquals(testData[row][1], loc.toLanguageTag());
+ }
+ }
+
+ // Helper function
+ private String canonicalTag(String languageTag) {
+ return ULocale.createCanonical(ULocale.forLanguageTag(languageTag)).toLanguageTag();
+ }
+
+ @Test
+ public void TestCanonical() {
+ // Test replacement of languageAlias
+
+ // language _ variant -> language
+ Assert.assertEquals("nb", canonicalTag("no-BOKMAL"));
+ // also test with script, country and extensions
+ Assert.assertEquals("nb-Cyrl-ID-u-ca-japanese", canonicalTag("no-Cyrl-ID-BOKMAL-u-ca-japanese"));
+ // also test with other variants, script, country and extensions
+ Assert.assertEquals("nb-Cyrl-ID-1901-xsistemo-u-ca-japanese",
+ canonicalTag("no-Cyrl-ID-1901-BOKMAL-xsistemo-u-ca-japanese"));
+ Assert.assertEquals("nb-Cyrl-ID-1901-u-ca-japanese",
+ canonicalTag("no-Cyrl-ID-1901-BOKMAL-u-ca-japanese"));
+ Assert.assertEquals("nb-Cyrl-ID-xsistemo-u-ca-japanese",
+ canonicalTag("no-Cyrl-ID-BOKMAL-xsistemo-u-ca-japanese"));
+
+ Assert.assertEquals("nn", canonicalTag("no-NYNORSK"));
+ // also test with script, country and extensions
+ Assert.assertEquals("nn-Cyrl-ID-u-ca-japanese", canonicalTag("no-Cyrl-ID-NYNORSK-u-ca-japanese"));
+
+ Assert.assertEquals("ssy", canonicalTag("aa-SAAHO"));
+ // also test with script, country and extensions
+ Assert.assertEquals("ssy-Devn-IN-u-ca-japanese", canonicalTag("aa-Devn-IN-SAAHO-u-ca-japanese"));
+
+ // language -> language
+ Assert.assertEquals("aas", canonicalTag("aam"));
+ // also test with script, country, variants and extensions
+ Assert.assertEquals("aas-Cyrl-ID-3456-u-ca-japanese", canonicalTag("aam-Cyrl-ID-3456-u-ca-japanese"));
+
+ // language -> language _ Script
+ Assert.assertEquals("sr-Latn", canonicalTag("sh"));
+ // also test with script
+ Assert.assertEquals("sr-Cyrl", canonicalTag("sh-Cyrl"));
+ // also test with country, variants and extensions
+ Assert.assertEquals("sr-Latn-ID-3456-u-ca-roc", canonicalTag("sh-ID-3456-u-ca-roc"));
+
+ // language -> language _ country
+ Assert.assertEquals("fa-AF", canonicalTag("prs"));
+ // also test with country
+ Assert.assertEquals("fa-RU", canonicalTag("prs-RU"));
+ // also test with script, variants and extensions
+ Assert.assertEquals("fa-Cyrl-AF-1009-u-ca-roc", canonicalTag("prs-Cyrl-1009-u-ca-roc"));
+
+ // language _ country -> language _ script _ country
+ Assert.assertEquals("pa-Guru-IN", canonicalTag("pa-IN"));
+ // also test with script
+ Assert.assertEquals("pa-Latn-IN", canonicalTag("pa-Latn-IN"));
+ // also test with variants and extensions
+ Assert.assertEquals("pa-Guru-IN-5678-u-ca-hindi", canonicalTag("pa-IN-5678-u-ca-hindi"));
+
+ // language _ script _ country -> language _ country
+ Assert.assertEquals("ky-KG", canonicalTag("ky-Cyrl-KG"));
+ // also test with variants and extensions
+ Assert.assertEquals("ky-KG-3456-u-ca-roc", canonicalTag("ky-Cyrl-KG-3456-u-ca-roc"));
+
+ // Test replacement of territoryAlias
+ // 554 has one replacement
+ Assert.assertEquals("en-NZ", canonicalTag("en-554"));
+ Assert.assertEquals("en-NZ-u-nu-arab", canonicalTag("en-554-u-nu-arab"));
+
+ // 172 has multiple replacements
+ // also test with variants
+ Assert.assertEquals("ru-RU-1234", canonicalTag("ru-172-1234"));
+ // also test with variants
+ Assert.assertEquals("ru-RU-1234-u-nu-latn", canonicalTag("ru-172-1234-u-nu-latn"));
+ Assert.assertEquals("uz-UZ", canonicalTag("uz-172"));
+ // also test with scripts
+ Assert.assertEquals("uz-Cyrl-UZ", canonicalTag("uz-Cyrl-172"));
+ Assert.assertEquals("uz-Bopo-UZ", canonicalTag("uz-Bopo-172"));
+ // also test with variants and scripts
+ Assert.assertEquals("uz-Cyrl-UZ-5678-u-nu-latn", canonicalTag("uz-Cyrl-172-5678-u-nu-latn"));
+ // a language not used in this region
+ Assert.assertEquals("fr-RU", canonicalTag("fr-172"));
+ }
}
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/util/data/localeDistanceTest.txt b/android_icu4j/src/main/tests/android/icu/dev/test/util/data/localeDistanceTest.txt
index ba783b569..a6926da6e 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/util/data/localeDistanceTest.txt
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/util/data/localeDistanceTest.txt
@@ -18,10 +18,10 @@ zh ; cmn ; 0
# fallback languages get closer distances, between script (40) and region (4)
@debug
-to ; en ; 14 ; 100
+to ; en ; 34 ; 100
no ; no-DE ; 4
-nn ; no ; 10
-no-DE ; nn ; 14
+nn ; no ; 20
+no-DE ; nn ; 24
no ; no ; 0
no ; da ; 12
da ; zh-Hant ; 100
diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/util/data/localeMatcherTest.txt b/android_icu4j/src/main/tests/android/icu/dev/test/util/data/localeMatcherTest.txt
index 21c9b6014..7a1098673 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/test/util/data/localeMatcherTest.txt
+++ b/android_icu4j/src/main/tests/android/icu/dev/test/util/data/localeMatcherTest.txt
@@ -733,7 +733,7 @@ ja >> fr
@favor=script
en-GB >> en-GB
en-US >> en
-fr >> en-GB
+fr >> en
ja >> fr
** test: testEmptyWithDefault
@@ -761,8 +761,8 @@ en-GB >> en-GB
en-US >> en
fr-FR >> fr
ja-JP >> fr
+zu >> en
# For a language that doesn't match anything, return the default.
-zu >> en-GB
zxx >> fr
@favor=script
@@ -770,7 +770,7 @@ en-GB >> en-GB
en-US >> en
fr-FR >> fr
ja-JP >> fr
-zu >> en-GB
+zu >> en
zxx >> en
** test: TestExactMatch
@@ -1052,9 +1052,9 @@ en >> en-DE
ar-EG >> ar-SY
pt-BR >> pt
ar-XB >> ar-XB
-ar-PSBIDI >> ar-XB # These are equivalent.
+ar-PSBIDI >> ar-PSBIDI
en-XA >> en-XA
-en-PSACCENT >> en-XA # These are equivalent.
+en-PSACCENT >> en-PSACCENT
ar-PSCRACK >> ar-PSCRACK
@favor=script
@@ -1063,9 +1063,9 @@ en >> en-DE
ar-EG >> ar-SY
pt-BR >> pt
ar-XB >> ar-XB
-ar-PSBIDI >> ar-XB # These are equivalent.
+ar-PSBIDI >> ar-PSBIDI
en-XA >> en-XA
-en-PSACCENT >> en-XA # These are equivalent.
+en-PSACCENT >> en-PSACCENT
ar-PSCRACK >> ar-PSCRACK
** test: BestMatchForTraditionalChinese
@@ -1322,7 +1322,7 @@ en >> en-US
@favor=script
und >> und
ja >> und
-fr-CA >> en-GB
+fr-CA >> en-US
en-AU >> en-GB
en-BZ >> en-GB
en-CA >> en-GB
@@ -1359,8 +1359,8 @@ fr >> und
@supported=en-GB, en-US, en, en-AU
und >> und
ja >> und
-fr-CA >> en-GB
-fr >> en-GB
+fr-CA >> en-US
+fr >> en-US
@supported=en-AU, ja, ca
fr >> en-AU
@supported=pl, ja, ca
@@ -1464,10 +1464,10 @@ da >> no
@supported=en, nb
da >> nb
-** test: prefer matching languages over language variants.
+** test: prefer matching languages over language variants. Get en-GB, should get nn?
@supported=nn, en-GB
-no, en-US >> nn
-nb, en-US >> nn
+no, en-US >> en-GB
+nb, en-US >> en-GB
@favor=script
no, en-US >> nn
@@ -1544,50 +1544,44 @@ zh-TW, en >> en-US
zh-Hant-CN, en >> en-US
zh-Hans, en >> zh-Hans-CN
-** test: return first among likely-subtags equivalent locales
-# Was: more specific script should win in case regions are identical
-# with some different results.
+** test: return most originally similar among likely-subtags equivalent locales
@supported=af, af-Latn, af-Arab
af >> af
af-ZA >> af
-af-Latn-ZA >> af
-af-Latn >> af
+af-Latn-ZA >> af-Latn
+af-Latn >> af-Latn
@favor=script
af >> af
af-ZA >> af
-af-Latn-ZA >> af
-af-Latn >> af
+af-Latn-ZA >> af-Latn
+af-Latn >> af-Latn
-# Was: more specific region should win
-# with some different results.
@supported=nl, nl-NL, nl-BE
@favor=
nl >> nl
nl-Latn >> nl
-nl-Latn-NL >> nl
-nl-NL >> nl
+nl-Latn-NL >> nl-NL
+nl-NL >> nl-NL
@favor=script
nl >> nl
nl-Latn >> nl
-nl-Latn-NL >> nl
-nl-NL >> nl
+nl-Latn-NL >> nl-NL
+nl-NL >> nl-NL
-# Was: more specific region wins over more specific script
-# with some different results.
@supported=nl, nl-Latn, nl-NL, nl-BE
@favor=
nl >> nl
-nl-Latn >> nl
-nl-NL >> nl
-nl-Latn-NL >> nl
+nl-Latn >> nl-Latn
+nl-NL >> nl-NL
+nl-Latn-NL >> nl-Latn
@favor=script
nl >> nl
-nl-Latn >> nl
-nl-NL >> nl
-nl-Latn-NL >> nl
+nl-Latn >> nl-Latn
+nl-NL >> nl-NL
+nl-Latn-NL >> nl-Latn
** test: region may replace matched if matched is enclosing
@supported=es-419, es
@@ -1670,22 +1664,22 @@ ja-Jpan-JP, en-GB >> ja
** test: pick best maximized tag
@supported=ja, ja-Jpan-US, ja-JP, en, ru
ja-Jpan, ru >> ja
-ja-JP, ru >> ja
+ja-JP, ru >> ja-JP
ja-US, ru >> ja-Jpan-US
@favor=script
ja-Jpan, ru >> ja
-ja-JP, ru >> ja
+ja-JP, ru >> ja-JP
ja-US, ru >> ja-Jpan-US
** test: termination: pick best maximized match
@supported=ja, ja-Jpan, ja-JP, en, ru
-ja-Jpan-JP, ru >> ja
-ja-Jpan, ru >> ja
+ja-Jpan-JP, ru >> ja-Jpan
+ja-Jpan, ru >> ja-Jpan
@favor=script
-ja-Jpan-JP, ru >> ja
-ja-Jpan, ru >> ja
+ja-Jpan-JP, ru >> ja-Jpan
+ja-Jpan, ru >> ja-Jpan
** test: same language over exact, but distinguish when user is explicit
@supported=fr, en-GB, ja, es-ES, es-MX
@@ -1900,14 +1894,14 @@ zh-TW >> zh
** test: testGetBestMatchWithMinMatchScore
@supported=fr-FR, fr, fr-CA, en
@default=und
-fr >> fr-FR # First likely-subtags equivalent match is chosen.
+fr >> fr
@supported=en, fr, fr-CA
fr-FR >> fr # Parent match is chosen.
@supported=en, fr-CA
fr-FR >> fr-CA # Sibling match is chosen.
@supported=fr-CA, fr-FR
fr >> fr-FR # Inferred region match is chosen.
-fr-SN >> fr-CA
+fr-SN >> fr-FR
@supported=en, fr-FR
fr >> fr-FR # Child match is chosen.
@supported=de, en, it
@@ -1930,14 +1924,14 @@ ru >> und
@favor=script
@supported=fr-FR, fr, fr-CA, en
-fr >> fr-FR
+fr >> fr
@supported=en, fr, fr-CA
fr-FR >> fr
@supported=en, fr-CA
fr-FR >> fr-CA
@supported=fr-CA, fr-FR
fr >> fr-FR
-fr-SN >> fr-CA
+fr-SN >> fr-FR
@supported=en, fr-FR
fr >> fr-FR
@supported=de, en, it
@@ -1957,3 +1951,10 @@ ru >> uk
zh-CN >> zh-TW
@supported=ja
ru >> und
+
+** test: favor a more-default locale among equally imperfect matches
+@supported=fr-CA, fr-CH, fr-FR, fr-GB
+fr-SN >> fr-FR
+@supported=sr-Latn, sr-Cyrl, sr-Grek
+@threshold=60
+sr-Thai >> sr-Cyrl
diff --git a/android_icu4j/src/main/tests/android/icu/dev/tool/locale/LikelySubtagsBuilder.java b/android_icu4j/src/main/tests/android/icu/dev/tool/locale/LikelySubtagsBuilder.java
index f9e093344..9887e89ab 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/tool/locale/LikelySubtagsBuilder.java
+++ b/android_icu4j/src/main/tests/android/icu/dev/tool/locale/LikelySubtagsBuilder.java
@@ -142,10 +142,11 @@ public class LikelySubtagsBuilder {
Map<LSR, Integer> lsrIndexes = new LinkedHashMap<>();
// Reserve index 0 as "no value":
// The runtime lookup returns 0 for an intermediate match with no value.
- lsrIndexes.put(new LSR("", "", ""), 0); // arbitrary LSR
+ lsrIndexes.put(new LSR("", "", "", LSR.DONT_CARE_FLAGS), 0); // arbitrary LSR
// Reserve index 1 for SKIP_SCRIPT:
// The runtime lookup returns 1 for an intermediate match with a value.
- lsrIndexes.put(new LSR("skip", "script", ""), 1); // looks good when printing the data
+ // This LSR looks good when printing the data.
+ lsrIndexes.put(new LSR("skip", "script", "", LSR.DONT_CARE_FLAGS), 1);
// We could prefill the lsrList with common locales to give them small indexes,
// and see if that improves performance a little.
for (Map.Entry<String, Map<String, Map<String, LSR>>> ls : langTable.entrySet()) {
@@ -254,7 +255,7 @@ public class LikelySubtagsBuilder {
}
}
// hack
- set(result, "und", "Latn", "", new LSR("en", "Latn", "US"));
+ set(result, "und", "Latn", "", new LSR("en", "Latn", "US", LSR.DONT_CARE_FLAGS));
// hack, ensure that if und-YY => und-Xxxx-YY, then we add Xxxx=>YY to the table
// <likelySubtag from="und_GH" to="ak_Latn_GH"/>
@@ -297,7 +298,9 @@ public class LikelySubtagsBuilder {
String lang = parts[0];
String p2 = parts.length < 2 ? "" : parts[1];
String p3 = parts.length < 3 ? "" : parts[2];
- return p2.length() < 4 ? new LSR(lang, "", p2) : new LSR(lang, p2, p3);
+ return p2.length() < 4 ?
+ new LSR(lang, "", p2, LSR.DONT_CARE_FLAGS) :
+ new LSR(lang, p2, p3, LSR.DONT_CARE_FLAGS);
}
private static void set(Map<String, Map<String, Map<String, LSR>>> langTable,
diff --git a/android_icu4j/src/main/tests/android/icu/dev/tool/locale/LocaleDistanceBuilder.java b/android_icu4j/src/main/tests/android/icu/dev/tool/locale/LocaleDistanceBuilder.java
index 42fc03bcd..6e41d976f 100644
--- a/android_icu4j/src/main/tests/android/icu/dev/tool/locale/LocaleDistanceBuilder.java
+++ b/android_icu4j/src/main/tests/android/icu/dev/tool/locale/LocaleDistanceBuilder.java
@@ -16,6 +16,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -487,10 +488,14 @@ public final class LocaleDistanceBuilder {
ICUResourceBundle supplementalData = getSupplementalDataBundle("supplementalData");
String[] paradigms = supplementalData.getValueWithFallback(
"languageMatchingInfo/written/paradigmLocales").getStringArray();
- Set<LSR> paradigmLSRs = new HashSet<>(); // could be TreeSet if LSR were Comparable
+ // LinkedHashSet for stable order; otherwise a unit test is flaky.
+ Set<LSR> paradigmLSRs = new LinkedHashSet<>(); // could be TreeSet if LSR were Comparable
for (String paradigm : paradigms) {
ULocale pl = new ULocale(paradigm);
- paradigmLSRs.add(XLikelySubtags.INSTANCE.makeMaximizedLsrFrom(pl));
+ LSR max = XLikelySubtags.INSTANCE.makeMaximizedLsrFrom(pl);
+ // Clear the LSR flags to make the data equality test in
+ // LocaleDistanceTest happy.
+ paradigmLSRs.add(new LSR(max.language, max.script, max.region, LSR.DONT_CARE_FLAGS));
}
TerritoryContainment tc = new TerritoryContainment(supplementalData);
diff --git a/android_icu4j/src/main/tests/android/icu/extratest/android_icu_version.properties b/android_icu4j/src/main/tests/android/icu/extratest/android_icu_version.properties
index 8912579eb..e6b3f0d59 100644
--- a/android_icu4j/src/main/tests/android/icu/extratest/android_icu_version.properties
+++ b/android_icu4j/src/main/tests/android/icu/extratest/android_icu_version.properties
@@ -1,2 +1,2 @@
# Property file for AndroidICUVersionTest.
-version=66.1.0.0
+version=67.1.0.0
diff --git a/android_icu4j/src/main/tests/android/icu/extratest/expected_transliteration_id_list.txt b/android_icu4j/src/main/tests/android/icu/extratest/expected_transliteration_id_list.txt
index dde80eb33..87abcf97a 100644
--- a/android_icu4j/src/main/tests/android/icu/extratest/expected_transliteration_id_list.txt
+++ b/android_icu4j/src/main/tests/android/icu/extratest/expected_transliteration_id_list.txt
@@ -35,6 +35,9 @@ Bengali-Telugu
Bopo-Latn
Bopomofo-Latin
Bulgarian-Latin/BGN
+Burmese-Latin
+CanadianAboriginal-Latin
+Cans-Latn
Cyrillic-Latin
Cyrl-Latn
Deva-Arab
@@ -59,6 +62,8 @@ Devanagari-Oriya
Devanagari-Tamil
Devanagari-Telugu
Digit-Tone
+Ethi-Latn
+Ethiopic-Latin
Fullwidth-Halfwidth
Geor-Latn
Georgian-Latin
@@ -162,8 +167,10 @@ Latin-Arabic
Latin-Armenian
Latin-Bengali
Latin-Bopomofo
+Latin-CanadianAboriginal
Latin-Cyrillic
Latin-Devanagari
+Latin-Ethiopic
Latin-Georgian
Latin-Greek
Latin-Greek/UNGEGN
@@ -188,8 +195,10 @@ Latn-Arab
Latn-Armn
Latn-Beng
Latn-Bopo
+Latn-Cans
Latn-Cyrl
Latn-Deva
+Latn-Ethi
Latn-Geor
Latn-Grek
Latn-Grek/UNGEGN
@@ -232,6 +241,7 @@ Mlym-Taml
Mlym-Telu
Mlym-ur
Mongolian-Latin/BGN
+Myanmar-Latin
NumericPinyin-Latin
NumericPinyin-Pinyin
Oriya-Arabic
@@ -429,6 +439,7 @@ my-ar
my-chr
my-fa
my-my_FONIPA
+my-my_Latn
nl-Title
nv-nv_FONIPA
pl-am
@@ -591,6 +602,8 @@ Any-Jamo
Any-Armenian
Any-Thai
Any-Cyrillic
+Any-CanadianAboriginal
+Any-Ethiopic
Any-Oriya
Any-Latin/BGN
Any-am_FONIPA
@@ -633,16 +646,18 @@ Any-Latin/Names
Any-vec_FONIPA
Any-uk_Latn/BGN
Any-Hira
+Any-Hang
+Any-Cans
+Any-Cyrl
+Any-Ethi
Any-Grek
Any-Grek/UNGEGN
Any-Syrc
Any-Bopo
-Any-Hang
Any-Thaa
Any-Geor
Any-Kana
Any-Hebr
-Any-Cyrl
Any-Armn
Any-mk_Latn/BGN
Any-mn_Latn/BGN
@@ -653,6 +668,7 @@ Any-eo_FONIPA
Any-tk/BGN
Any-chr_FONIPA
Any-my_FONIPA
+Any-my_Latn
Any-es_FONIPA
Any-ru/BGN
Any-dsb_FONIPA