ICU-21190 Adding PluralRules select for number ranges
See #1309
diff --git a/icu4c/source/i18n/i18n.vcxproj b/icu4c/source/i18n/i18n.vcxproj
index de36301..218772f 100644
--- a/icu4c/source/i18n/i18n.vcxproj
+++ b/icu4c/source/i18n/i18n.vcxproj
@@ -221,6 +221,7 @@
<ClCompile Include="numsys.cpp" />
<ClCompile Include="olsontz.cpp" />
<ClCompile Include="persncal.cpp" />
+ <ClCompile Include="pluralranges.cpp" />
<ClCompile Include="plurfmt.cpp" />
<ClCompile Include="plurrule.cpp" />
<ClCompile Include="quantityformatter.cpp" />
@@ -391,6 +392,7 @@
<ClInclude Include="nfsubs.h" />
<ClInclude Include="olsontz.h" />
<ClInclude Include="persncal.h" />
+ <ClInclude Include="pluralranges.h" />
<ClInclude Include="plurrule_impl.h" />
<ClInclude Include="quantityformatter.h" />
<ClInclude Include="sharedbreakiterator.h" />
diff --git a/icu4c/source/i18n/i18n.vcxproj.filters b/icu4c/source/i18n/i18n.vcxproj.filters
index a012465..2fc88e0 100644
--- a/icu4c/source/i18n/i18n.vcxproj.filters
+++ b/icu4c/source/i18n/i18n.vcxproj.filters
@@ -234,6 +234,9 @@
<ClCompile Include="persncal.cpp">
<Filter>formatting</Filter>
</ClCompile>
+ <ClCompile Include="pluralranges.cpp">
+ <Filter>formatting</Filter>
+ </ClCompile>
<ClCompile Include="plurfmt.cpp">
<Filter>formatting</Filter>
</ClCompile>
@@ -983,6 +986,9 @@
<ClInclude Include="persncal.h">
<Filter>formatting</Filter>
</ClInclude>
+ <ClInclude Include="pluralranges.h">
+ <Filter>formatting</Filter>
+ </ClInclude>
<ClInclude Include="plurrule_impl.h">
<Filter>formatting</Filter>
</ClInclude>
diff --git a/icu4c/source/i18n/i18n_uwp.vcxproj b/icu4c/source/i18n/i18n_uwp.vcxproj
index be7f4da..f836aeb 100644
--- a/icu4c/source/i18n/i18n_uwp.vcxproj
+++ b/icu4c/source/i18n/i18n_uwp.vcxproj
@@ -454,6 +454,7 @@
<ClCompile Include="numsys.cpp" />
<ClCompile Include="olsontz.cpp" />
<ClCompile Include="persncal.cpp" />
+ <ClCompile Include="pluralranges.cpp" />
<ClCompile Include="plurfmt.cpp" />
<ClCompile Include="plurrule.cpp" />
<ClCompile Include="quantityformatter.cpp" />
@@ -622,6 +623,7 @@
<ClInclude Include="nfsubs.h" />
<ClInclude Include="olsontz.h" />
<ClInclude Include="persncal.h" />
+ <ClInclude Include="pluralranges.h" />
<ClInclude Include="plurrule_impl.h" />
<ClInclude Include="quantityformatter.h" />
<ClInclude Include="sharedbreakiterator.h" />
diff --git a/icu4c/source/i18n/number_output.cpp b/icu4c/source/i18n/number_output.cpp
index 9b1a11d..7129b94 100644
--- a/icu4c/source/i18n/number_output.cpp
+++ b/icu4c/source/i18n/number_output.cpp
@@ -11,6 +11,7 @@
#include "util.h"
#include "number_decimalquantity.h"
#include "number_decnum.h"
+#include "numrange_impl.h"
U_NAMESPACE_BEGIN
namespace number {
@@ -47,6 +48,42 @@
impl::UFormattedNumberData::~UFormattedNumberData() = default;
+UPRV_FORMATTED_VALUE_SUBCLASS_AUTO_IMPL(FormattedNumberRange)
+
+#define UPRV_NOARG
+
+UnicodeString FormattedNumberRange::getFirstDecimal(UErrorCode& status) const {
+ UPRV_FORMATTED_VALUE_METHOD_GUARD(ICU_Utility::makeBogusString())
+ return fData->quantity1.toScientificString();
+}
+
+UnicodeString FormattedNumberRange::getSecondDecimal(UErrorCode& status) const {
+ UPRV_FORMATTED_VALUE_METHOD_GUARD(ICU_Utility::makeBogusString())
+ return fData->quantity2.toScientificString();
+}
+
+void FormattedNumberRange::getDecimalNumbers(ByteSink& sink1, ByteSink& sink2, UErrorCode& status) const {
+ UPRV_FORMATTED_VALUE_METHOD_GUARD(UPRV_NOARG)
+ impl::DecNum decnum1;
+ impl::DecNum decnum2;
+ fData->quantity1.toDecNum(decnum1, status).toString(sink1, status);
+ fData->quantity2.toDecNum(decnum2, status).toString(sink2, status);
+}
+
+UNumberRangeIdentityResult FormattedNumberRange::getIdentityResult(UErrorCode& status) const {
+ UPRV_FORMATTED_VALUE_METHOD_GUARD(UNUM_IDENTITY_RESULT_NOT_EQUAL)
+ return fData->identityResult;
+}
+
+const impl::UFormattedNumberRangeData* FormattedNumberRange::getData(UErrorCode& status) const {
+ UPRV_FORMATTED_VALUE_METHOD_GUARD(nullptr)
+ return fData;
+}
+
+
+impl::UFormattedNumberRangeData::~UFormattedNumberRangeData() = default;
+
+
} // namespace number
U_NAMESPACE_END
diff --git a/icu4c/source/i18n/numrange_capi.cpp b/icu4c/source/i18n/numrange_capi.cpp
index 184f0ab..a440a53 100644
--- a/icu4c/source/i18n/numrange_capi.cpp
+++ b/icu4c/source/i18n/numrange_capi.cpp
@@ -59,8 +59,8 @@
fImpl.fData = nullptr;
}
-}
-}
+} // namespace impl
+} // namespace number
U_NAMESPACE_END
@@ -71,6 +71,16 @@
unumrf)
+const UFormattedNumberRangeData* number::impl::validateUFormattedNumberRange(
+ const UFormattedNumberRange* uresult, UErrorCode& status) {
+ auto* result = UFormattedNumberRangeApiHelper::validate(uresult, status);
+ if (U_FAILURE(status)) {
+ return nullptr;
+ }
+ return &result->fData;
+}
+
+
U_CAPI UNumberRangeFormatter* U_EXPORT2
unumrf_openForSkeletonWithCollapseAndIdentityFallback(
const UChar* skeleton,
diff --git a/icu4c/source/i18n/numrange_fluent.cpp b/icu4c/source/i18n/numrange_fluent.cpp
index db1cdea..d9286d1 100644
--- a/icu4c/source/i18n/numrange_fluent.cpp
+++ b/icu4c/source/i18n/numrange_fluent.cpp
@@ -376,36 +376,4 @@
}
-UPRV_FORMATTED_VALUE_SUBCLASS_AUTO_IMPL(FormattedNumberRange)
-
-#define UPRV_NOARG
-
-UnicodeString FormattedNumberRange::getFirstDecimal(UErrorCode& status) const {
- UPRV_FORMATTED_VALUE_METHOD_GUARD(ICU_Utility::makeBogusString())
- return fData->quantity1.toScientificString();
-}
-
-UnicodeString FormattedNumberRange::getSecondDecimal(UErrorCode& status) const {
- UPRV_FORMATTED_VALUE_METHOD_GUARD(ICU_Utility::makeBogusString())
- return fData->quantity2.toScientificString();
-}
-
-void FormattedNumberRange::getDecimalNumbers(ByteSink& sink1, ByteSink& sink2, UErrorCode& status) const {
- UPRV_FORMATTED_VALUE_METHOD_GUARD(UPRV_NOARG)
- impl::DecNum decnum1;
- impl::DecNum decnum2;
- fData->quantity1.toDecNum(decnum1, status).toString(sink1, status);
- fData->quantity2.toDecNum(decnum2, status).toString(sink2, status);
-}
-
-UNumberRangeIdentityResult FormattedNumberRange::getIdentityResult(UErrorCode& status) const {
- UPRV_FORMATTED_VALUE_METHOD_GUARD(UNUM_IDENTITY_RESULT_NOT_EQUAL)
- return fData->identityResult;
-}
-
-
-UFormattedNumberRangeData::~UFormattedNumberRangeData() = default;
-
-
-
#endif /* #if !UCONFIG_NO_FORMATTING */
diff --git a/icu4c/source/i18n/numrange_impl.cpp b/icu4c/source/i18n/numrange_impl.cpp
index 9fb3dee..3aae5c2 100644
--- a/icu4c/source/i18n/numrange_impl.cpp
+++ b/icu4c/source/i18n/numrange_impl.cpp
@@ -12,6 +12,7 @@
#include "unicode/numberrangeformatter.h"
#include "numrange_impl.h"
#include "patternprops.h"
+#include "pluralranges.h"
#include "uresimp.h"
#include "util.h"
@@ -106,92 +107,9 @@
sink.fillInDefaults(status);
}
-class PluralRangesDataSink : public ResourceSink {
- public:
- PluralRangesDataSink(StandardPluralRanges& output) : fOutput(output) {}
-
- void put(const char* /*key*/, ResourceValue& value, UBool /*noFallback*/, UErrorCode& status) U_OVERRIDE {
- ResourceArray entriesArray = value.getArray(status);
- if (U_FAILURE(status)) { return; }
- fOutput.setCapacity(entriesArray.getSize());
- for (int i = 0; entriesArray.getValue(i, value); i++) {
- ResourceArray pluralFormsArray = value.getArray(status);
- if (U_FAILURE(status)) { return; }
- pluralFormsArray.getValue(0, value);
- StandardPlural::Form first = StandardPlural::fromString(value.getUnicodeString(status), status);
- if (U_FAILURE(status)) { return; }
- pluralFormsArray.getValue(1, value);
- StandardPlural::Form second = StandardPlural::fromString(value.getUnicodeString(status), status);
- if (U_FAILURE(status)) { return; }
- pluralFormsArray.getValue(2, value);
- StandardPlural::Form result = StandardPlural::fromString(value.getUnicodeString(status), status);
- if (U_FAILURE(status)) { return; }
- fOutput.addPluralRange(first, second, result);
- }
- }
-
- private:
- StandardPluralRanges& fOutput;
-};
-
-void getPluralRangesData(const Locale& locale, StandardPluralRanges& output, UErrorCode& status) {
- if (U_FAILURE(status)) { return; }
- LocalUResourceBundlePointer rb(ures_openDirect(nullptr, "pluralRanges", &status));
- if (U_FAILURE(status)) { return; }
-
- CharString dataPath;
- dataPath.append("locales/", -1, status);
- dataPath.append(locale.getLanguage(), -1, status);
- if (U_FAILURE(status)) { return; }
- int32_t setLen;
- // Not all languages are covered: fail gracefully
- UErrorCode internalStatus = U_ZERO_ERROR;
- const UChar* set = ures_getStringByKeyWithFallback(rb.getAlias(), dataPath.data(), &setLen, &internalStatus);
- if (U_FAILURE(internalStatus)) { return; }
-
- dataPath.clear();
- dataPath.append("rules/", -1, status);
- dataPath.appendInvariantChars(set, setLen, status);
- if (U_FAILURE(status)) { return; }
- PluralRangesDataSink sink(output);
- ures_getAllItemsWithFallback(rb.getAlias(), dataPath.data(), sink, status);
- if (U_FAILURE(status)) { return; }
-}
-
} // namespace
-void StandardPluralRanges::initialize(const Locale& locale, UErrorCode& status) {
- getPluralRangesData(locale, *this, status);
-}
-
-void StandardPluralRanges::addPluralRange(
- StandardPlural::Form first,
- StandardPlural::Form second,
- StandardPlural::Form result) {
- U_ASSERT(fTriplesLen < fTriples.getCapacity());
- fTriples[fTriplesLen] = {first, second, result};
- fTriplesLen++;
-}
-
-void StandardPluralRanges::setCapacity(int32_t length) {
- if (length > fTriples.getCapacity()) {
- fTriples.resize(length, 0);
- }
-}
-
-StandardPlural::Form
-StandardPluralRanges::resolve(StandardPlural::Form first, StandardPlural::Form second) const {
- for (int32_t i=0; i<fTriplesLen; i++) {
- const auto& triple = fTriples[i];
- if (triple.first == first && triple.second == second) {
- return triple.result;
- }
- }
- // Default fallback
- return StandardPlural::OTHER;
-}
-
NumberRangeFormatterImpl::NumberRangeFormatterImpl(const RangeMacroProps& macros, UErrorCode& status)
: formatterImpl1(macros.formatter1.fMacros, status),
@@ -213,7 +131,7 @@
fApproximatelyModifier = {data.approximatelyPattern, kUndefinedField, false};
// TODO: Get locale from PluralRules instead?
- fPluralRanges.initialize(macros.locale, status);
+ fPluralRanges = StandardPluralRanges::forLocale(macros.locale, status);
if (U_FAILURE(status)) { return; }
}
diff --git a/icu4c/source/i18n/numrange_impl.h b/icu4c/source/i18n/numrange_impl.h
index 8f4c8a4..b81a311 100644
--- a/icu4c/source/i18n/numrange_impl.h
+++ b/icu4c/source/i18n/numrange_impl.h
@@ -15,6 +15,7 @@
#include "number_formatimpl.h"
#include "formatted_string_builder.h"
#include "formattedval_impl.h"
+#include "pluralranges.h"
U_NAMESPACE_BEGIN namespace number {
namespace impl {
@@ -40,36 +41,6 @@
};
-class StandardPluralRanges : public UMemory {
- public:
- void initialize(const Locale& locale, UErrorCode& status);
- StandardPlural::Form resolve(StandardPlural::Form first, StandardPlural::Form second) const;
-
- /** Used for data loading. */
- void addPluralRange(
- StandardPlural::Form first,
- StandardPlural::Form second,
- StandardPlural::Form result);
-
- /** Used for data loading. */
- void setCapacity(int32_t length);
-
- private:
- struct StandardPluralRangeTriple {
- StandardPlural::Form first;
- StandardPlural::Form second;
- StandardPlural::Form result;
- };
-
- // TODO: An array is simple here, but it results in linear lookup time.
- // Certain locales have 20-30 entries in this list.
- // Consider changing to a smarter data structure.
- typedef MaybeStackArray<StandardPluralRangeTriple, 3> PluralRangeTriples;
- PluralRangeTriples fTriples;
- int32_t fTriplesLen = 0;
-};
-
-
class NumberRangeFormatterImpl : public UMemory {
public:
NumberRangeFormatterImpl(const RangeMacroProps& macros, UErrorCode& status);
@@ -105,6 +76,11 @@
};
+/** Helper function used in upluralrules.cpp */
+const UFormattedNumberRangeData* validateUFormattedNumberRange(
+ const UFormattedNumberRange* uresult, UErrorCode& status);
+
+
} // namespace impl
} // namespace number
U_NAMESPACE_END
diff --git a/icu4c/source/i18n/pluralranges.cpp b/icu4c/source/i18n/pluralranges.cpp
new file mode 100644
index 0000000..da10e21
--- /dev/null
+++ b/icu4c/source/i18n/pluralranges.cpp
@@ -0,0 +1,144 @@
+// © 2018 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html
+
+#include "unicode/utypes.h"
+
+#if !UCONFIG_NO_FORMATTING
+
+// Allow implicit conversion from char16_t* to UnicodeString for this file:
+// Helpful in toString methods and elsewhere.
+#define UNISTR_FROM_STRING_EXPLICIT
+
+#include "unicode/numberrangeformatter.h"
+#include "pluralranges.h"
+#include "uresimp.h"
+#include "charstr.h"
+#include "uassert.h"
+#include "util.h"
+#include "numrange_impl.h"
+
+U_NAMESPACE_BEGIN
+
+
+namespace {
+
+class PluralRangesDataSink : public ResourceSink {
+ public:
+ PluralRangesDataSink(StandardPluralRanges& output) : fOutput(output) {}
+
+ void put(const char* /*key*/, ResourceValue& value, UBool /*noFallback*/, UErrorCode& status) U_OVERRIDE {
+ ResourceArray entriesArray = value.getArray(status);
+ if (U_FAILURE(status)) { return; }
+ fOutput.setCapacity(entriesArray.getSize(), status);
+ if (U_FAILURE(status)) { return; }
+ for (int i = 0; entriesArray.getValue(i, value); i++) {
+ ResourceArray pluralFormsArray = value.getArray(status);
+ if (U_FAILURE(status)) { return; }
+ if (pluralFormsArray.getSize() != 3) {
+ status = U_RESOURCE_TYPE_MISMATCH;
+ return;
+ }
+ pluralFormsArray.getValue(0, value);
+ StandardPlural::Form first = StandardPlural::fromString(value.getUnicodeString(status), status);
+ if (U_FAILURE(status)) { return; }
+ pluralFormsArray.getValue(1, value);
+ StandardPlural::Form second = StandardPlural::fromString(value.getUnicodeString(status), status);
+ if (U_FAILURE(status)) { return; }
+ pluralFormsArray.getValue(2, value);
+ StandardPlural::Form result = StandardPlural::fromString(value.getUnicodeString(status), status);
+ if (U_FAILURE(status)) { return; }
+ fOutput.addPluralRange(first, second, result);
+ }
+ }
+
+ private:
+ StandardPluralRanges& fOutput;
+};
+
+void getPluralRangesData(const Locale& locale, StandardPluralRanges& output, UErrorCode& status) {
+ LocalUResourceBundlePointer rb(ures_openDirect(nullptr, "pluralRanges", &status));
+ if (U_FAILURE(status)) { return; }
+
+ CharString dataPath;
+ dataPath.append("locales/", -1, status);
+ dataPath.append(locale.getLanguage(), -1, status);
+ if (U_FAILURE(status)) { return; }
+ int32_t setLen;
+ // Not all languages are covered: fail gracefully
+ UErrorCode internalStatus = U_ZERO_ERROR;
+ const UChar* set = ures_getStringByKeyWithFallback(rb.getAlias(), dataPath.data(), &setLen, &internalStatus);
+ if (U_FAILURE(internalStatus)) { return; }
+
+ dataPath.clear();
+ dataPath.append("rules/", -1, status);
+ dataPath.appendInvariantChars(set, setLen, status);
+ if (U_FAILURE(status)) { return; }
+ PluralRangesDataSink sink(output);
+ ures_getAllItemsWithFallback(rb.getAlias(), dataPath.data(), sink, status);
+}
+
+} // namespace
+
+
+StandardPluralRanges
+StandardPluralRanges::forLocale(const Locale& locale, UErrorCode& status) {
+ StandardPluralRanges result;
+ getPluralRangesData(locale, result, status);
+ return result;
+}
+
+StandardPluralRanges
+StandardPluralRanges::copy(UErrorCode& status) const {
+ StandardPluralRanges result;
+ if (fTriplesLen > result.fTriples.getCapacity()) {
+ if (result.fTriples.resize(fTriplesLen) == nullptr) {
+ status = U_MEMORY_ALLOCATION_ERROR;
+ return result;
+ }
+ }
+ uprv_memcpy(result.fTriples.getAlias(),
+ fTriples.getAlias(),
+ fTriplesLen * sizeof(fTriples[0]));
+ result.fTriplesLen = fTriplesLen;
+ return result;
+}
+
+LocalPointer<StandardPluralRanges>
+StandardPluralRanges::toPointer(UErrorCode& status) && noexcept {
+ return LocalPointer<StandardPluralRanges>(new StandardPluralRanges(std::move(*this)), status);
+}
+
+void StandardPluralRanges::addPluralRange(
+ StandardPlural::Form first,
+ StandardPlural::Form second,
+ StandardPlural::Form result) {
+ U_ASSERT(fTriplesLen < fTriples.getCapacity());
+ fTriples[fTriplesLen] = {first, second, result};
+ fTriplesLen++;
+}
+
+void StandardPluralRanges::setCapacity(int32_t length, UErrorCode& status) {
+ if (U_FAILURE(status)) { return; }
+ if (length > fTriples.getCapacity()) {
+ if (fTriples.resize(length, 0) == nullptr) {
+ status = U_MEMORY_ALLOCATION_ERROR;
+ }
+ }
+}
+
+StandardPlural::Form
+StandardPluralRanges::resolve(StandardPlural::Form first, StandardPlural::Form second) const {
+ for (int32_t i=0; i<fTriplesLen; i++) {
+ const auto& triple = fTriples[i];
+ if (triple.first == first && triple.second == second) {
+ return triple.result;
+ }
+ }
+ // Default fallback
+ return StandardPlural::OTHER;
+}
+
+
+U_NAMESPACE_END
+
+#endif /* #if !UCONFIG_NO_FORMATTING */
diff --git a/icu4c/source/i18n/pluralranges.h b/icu4c/source/i18n/pluralranges.h
new file mode 100644
index 0000000..eba59c7
--- /dev/null
+++ b/icu4c/source/i18n/pluralranges.h
@@ -0,0 +1,67 @@
+// © 2018 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html
+
+#ifndef __PLURALRANGES_H__
+#define __PLURALRANGES_H__
+
+#include "unicode/utypes.h"
+
+#if !UCONFIG_NO_FORMATTING
+
+#include "unicode/uobject.h"
+#include "unicode/locid.h"
+#include "unicode/plurrule.h"
+#include "standardplural.h"
+#include "cmemory.h"
+
+U_NAMESPACE_BEGIN
+
+// Forward declarations
+namespace number {
+namespace impl {
+class UFormattedNumberRangeData;
+}
+}
+
+class StandardPluralRanges : public UMemory {
+ public:
+ /** Create a new StandardPluralRanges for the given locale */
+ static StandardPluralRanges forLocale(const Locale& locale, UErrorCode& status);
+
+ /** Explicit copy constructor */
+ StandardPluralRanges copy(UErrorCode& status) const;
+
+ /** Create an object (called on an rvalue) */
+ LocalPointer<StandardPluralRanges> toPointer(UErrorCode& status) && noexcept;
+
+ /** Select rule based on the first and second forms */
+ StandardPlural::Form resolve(StandardPlural::Form first, StandardPlural::Form second) const;
+
+ /** Used for data loading. */
+ void addPluralRange(
+ StandardPlural::Form first,
+ StandardPlural::Form second,
+ StandardPlural::Form result);
+
+ /** Used for data loading. */
+ void setCapacity(int32_t length, UErrorCode& status);
+
+ private:
+ struct StandardPluralRangeTriple {
+ StandardPlural::Form first;
+ StandardPlural::Form second;
+ StandardPlural::Form result;
+ };
+
+ // TODO: An array is simple here, but it results in linear lookup time.
+ // Certain locales have 20-30 entries in this list.
+ // Consider changing to a smarter data structure.
+ typedef MaybeStackArray<StandardPluralRangeTriple, 3> PluralRangeTriples;
+ PluralRangeTriples fTriples;
+ int32_t fTriplesLen = 0;
+};
+
+U_NAMESPACE_END
+
+#endif /* #if !UCONFIG_NO_FORMATTING */
+#endif //__PLURALRANGES_H__
diff --git a/icu4c/source/i18n/plurrule.cpp b/icu4c/source/i18n/plurrule.cpp
index e7d5b5b..7ecf1bc 100644
--- a/icu4c/source/i18n/plurrule.cpp
+++ b/icu4c/source/i18n/plurrule.cpp
@@ -19,6 +19,7 @@
#include "unicode/ures.h"
#include "unicode/numfmt.h"
#include "unicode/decimfmt.h"
+#include "unicode/numberrangeformatter.h"
#include "charstr.h"
#include "cmemory.h"
#include "cstring.h"
@@ -36,6 +37,8 @@
#include "unifiedcache.h"
#include "number_decimalquantity.h"
#include "util.h"
+#include "pluralranges.h"
+#include "numrange_impl.h"
#if !UCONFIG_NO_FORMATTING
@@ -68,6 +71,7 @@
PluralRules::PluralRules(UErrorCode& /*status*/)
: UObject(),
mRules(nullptr),
+ mStandardPluralRanges(nullptr),
mInternalStatus(U_ZERO_ERROR)
{
}
@@ -75,6 +79,7 @@
PluralRules::PluralRules(const PluralRules& other)
: UObject(other),
mRules(nullptr),
+ mStandardPluralRanges(nullptr),
mInternalStatus(U_ZERO_ERROR)
{
*this=other;
@@ -82,6 +87,7 @@
PluralRules::~PluralRules() {
delete mRules;
+ delete mStandardPluralRanges;
}
SharedPluralRules::~SharedPluralRules() {
@@ -90,14 +96,20 @@
PluralRules*
PluralRules::clone() const {
- PluralRules* newObj = new PluralRules(*this);
// Since clone doesn't have a 'status' parameter, the best we can do is return nullptr if
// the newly created object was not fully constructed properly (an error occurred).
- if (newObj != nullptr && U_FAILURE(newObj->mInternalStatus)) {
- delete newObj;
- newObj = nullptr;
+ UErrorCode localStatus = U_ZERO_ERROR;
+ return clone(localStatus);
+}
+
+PluralRules*
+PluralRules::clone(UErrorCode& status) const {
+ LocalPointer<PluralRules> newObj(new PluralRules(*this), status);
+ if (U_SUCCESS(status) && U_FAILURE(newObj->mInternalStatus)) {
+ status = newObj->mInternalStatus;
+ newObj.adoptInstead(nullptr);
}
- return newObj;
+ return newObj.orphan();
}
PluralRules&
@@ -105,6 +117,8 @@
if (this != &other) {
delete mRules;
mRules = nullptr;
+ delete mStandardPluralRanges;
+ mStandardPluralRanges = nullptr;
mInternalStatus = other.mInternalStatus;
if (U_FAILURE(mInternalStatus)) {
// bail out early if the object we were copying from was already 'invalid'.
@@ -120,6 +134,11 @@
mInternalStatus = mRules->fInternalStatus;
}
}
+ if (other.mStandardPluralRanges != nullptr) {
+ mStandardPluralRanges = other.mStandardPluralRanges->copy(mInternalStatus)
+ .toPointer(mInternalStatus)
+ .orphan();
+ }
}
return *this;
}
@@ -212,11 +231,8 @@
if (U_FAILURE(status)) {
return nullptr;
}
- PluralRules *result = (*shared)->clone();
+ PluralRules *result = (*shared)->clone(status);
shared->removeRef();
- if (result == nullptr) {
- status = U_MEMORY_ALLOCATION_ERROR;
- }
return result;
}
@@ -253,6 +269,10 @@
// Original impl used default rules.
// Ask the question to ICU Core.
+ newObj->mStandardPluralRanges = StandardPluralRanges::forLocale(locale, status)
+ .toPointer(status)
+ .orphan();
+
return newObj.orphan();
}
@@ -273,6 +293,10 @@
if (U_FAILURE(status)) {
return ICU_Utility::makeBogusString();
}
+ if (U_FAILURE(mInternalStatus)) {
+ status = mInternalStatus;
+ return ICU_Utility::makeBogusString();
+ }
return select(dq);
}
@@ -286,6 +310,33 @@
}
}
+UnicodeString
+PluralRules::select(const number::FormattedNumberRange& range, UErrorCode& status) const {
+ return select(range.getData(status), status);
+}
+
+UnicodeString
+PluralRules::select(const number::impl::UFormattedNumberRangeData* impl, UErrorCode& status) const {
+ if (U_FAILURE(status)) {
+ return ICU_Utility::makeBogusString();
+ }
+ if (U_FAILURE(mInternalStatus)) {
+ status = mInternalStatus;
+ return ICU_Utility::makeBogusString();
+ }
+ if (mStandardPluralRanges == nullptr) {
+ // Happens if PluralRules was constructed via createRules()
+ status = U_UNSUPPORTED_ERROR;
+ return ICU_Utility::makeBogusString();
+ }
+ auto form1 = StandardPlural::fromString(select(impl->quantity1), status);
+ auto form2 = StandardPlural::fromString(select(impl->quantity2), status);
+ if (U_FAILURE(status)) {
+ return ICU_Utility::makeBogusString();
+ }
+ auto result = mStandardPluralRanges->resolve(form1, form2);
+ return UnicodeString(StandardPlural::getKeyword(result), -1, US_INV);
+}
StringEnumeration*
diff --git a/icu4c/source/i18n/sources.txt b/icu4c/source/i18n/sources.txt
index b26485d..b28f34c 100644
--- a/icu4c/source/i18n/sources.txt
+++ b/icu4c/source/i18n/sources.txt
@@ -140,6 +140,7 @@
numsys.cpp
olsontz.cpp
persncal.cpp
+pluralranges.cpp
plurfmt.cpp
plurrule.cpp
quant.cpp
diff --git a/icu4c/source/i18n/unicode/numberrangeformatter.h b/icu4c/source/i18n/unicode/numberrangeformatter.h
index e92db1d..4e0a15b 100644
--- a/icu4c/source/i18n/unicode/numberrangeformatter.h
+++ b/icu4c/source/i18n/unicode/numberrangeformatter.h
@@ -47,6 +47,9 @@
U_NAMESPACE_BEGIN
+// Forward declarations:
+class PluralRules;
+
namespace number { // icu::number
// Forward declarations:
@@ -733,6 +736,11 @@
void getDecimalNumbers(ByteSink& sink1, ByteSink& sink2, UErrorCode& status) const;
+ const impl::UFormattedNumberRangeData* getData(UErrorCode& status) const;
+
+ // To allow PluralRules to access the underlying data
+ friend class ::icu::PluralRules;
+
// To give LocalizedNumberRangeFormatter format methods access to this class's constructor:
friend class LocalizedNumberRangeFormatter;
diff --git a/icu4c/source/i18n/unicode/plurrule.h b/icu4c/source/i18n/unicode/plurrule.h
index a92d4cb..dd53223 100644
--- a/icu4c/source/i18n/unicode/plurrule.h
+++ b/icu4c/source/i18n/unicode/plurrule.h
@@ -51,9 +51,14 @@
class PluralKeywordEnumeration;
class AndConstraint;
class SharedPluralRules;
+class StandardPluralRanges;
namespace number {
class FormattedNumber;
+class FormattedNumberRange;
+namespace impl {
+class UFormattedNumberRangeData;
+}
}
/**
@@ -367,11 +372,35 @@
*/
UnicodeString select(const number::FormattedNumber& number, UErrorCode& status) const;
+#ifndef U_HIDE_DRAFT_API
+ /**
+ * Given a formatted number range, returns the overall plural form of the
+ * range. For example, "3-5" returns "other" in English.
+ *
+ * To get a FormattedNumberRange, see NumberRangeFormatter.
+ *
+ * This method only works if PluralRules was created with a locale. If it was created
+ * from PluralRules::createRules(), this method sets status code U_UNSUPPORTED_ERROR.
+ *
+ * @param range The number range onto which the rules will be applied.
+ * @param status Set if an error occurs while selecting plural keyword.
+ * This could happen if the FormattedNumberRange is invalid,
+ * or if plural ranges data is unavailable.
+ * @return The keyword of the selected rule.
+ * @draft ICU 68
+ */
+ UnicodeString select(const number::FormattedNumberRange& range, UErrorCode& status) const;
+#endif // U_HIDE_DRAFT_API
+
#ifndef U_HIDE_INTERNAL_API
/**
- * @internal
- */
+ * @internal
+ */
UnicodeString select(const IFixedDecimal &number) const;
+ /**
+ * @internal
+ */
+ UnicodeString select(const number::impl::UFormattedNumberRangeData* urange, UErrorCode& status) const;
#endif /* U_HIDE_INTERNAL_API */
/**
@@ -513,12 +542,14 @@
private:
RuleChain *mRules;
+ StandardPluralRanges *mStandardPluralRanges;
PluralRules(); // default constructor not implemented
void parseDescription(const UnicodeString& ruleData, UErrorCode &status);
int32_t getNumberValue(const UnicodeString& token) const;
UnicodeString getRuleFromResource(const Locale& locale, UPluralType type, UErrorCode& status);
RuleChain *rulesForKeyword(const UnicodeString &keyword) const;
+ PluralRules *clone(UErrorCode& status) const;
/**
* An internal status variable used to indicate that the object is in an 'invalid' state.
diff --git a/icu4c/source/i18n/unicode/upluralrules.h b/icu4c/source/i18n/unicode/upluralrules.h
index a94c9d9..71a45dd 100644
--- a/icu4c/source/i18n/unicode/upluralrules.h
+++ b/icu4c/source/i18n/unicode/upluralrules.h
@@ -26,6 +26,7 @@
// Forward-declaration
struct UFormattedNumber;
+struct UFormattedNumberRange;
/**
* \file
@@ -167,7 +168,7 @@
* @param uplrules The UPluralRules object specifying the rules.
* @param number The formatted number for which the rule has to be determined.
* @param keyword The destination buffer for the keyword of the rule that
- * applies to number.
+ * applies to the number.
* @param capacity The capacity of the keyword buffer.
* @param status A pointer to a UErrorCode to receive any errors.
* @return The length of the keyword.
@@ -179,6 +180,29 @@
UChar *keyword, int32_t capacity,
UErrorCode *status);
+#ifndef U_HIDE_DRAFT_API
+/**
+ * Given a formatted number range, returns the overall plural form of the
+ * range. For example, "3-5" returns "other" in English.
+ *
+ * To get a UFormattedNumberRange, see UNumberRangeFormatter.
+ *
+ * @param uplrules The UPluralRules object specifying the rules.
+ * @param urange The number range onto which the rules will be applied.
+ * @param keyword The destination buffer for the keyword of the rule that
+ * applies to the number range.
+ * @param capacity The capacity of the keyword buffer.
+ * @param status A pointer to a UErrorCode to receive any errors.
+ * @return The length of the keyword.
+ * @draft ICU 68
+ */
+U_CAPI int32_t U_EXPORT2
+uplrules_selectForRange(const UPluralRules *uplrules,
+ const struct UFormattedNumberRange* urange,
+ UChar *keyword, int32_t capacity,
+ UErrorCode *status);
+#endif // U_HIDE_DRAFT_API
+
#ifndef U_HIDE_INTERNAL_API
/**
* Given a number, returns the keyword of the first rule that applies to the
diff --git a/icu4c/source/i18n/upluralrules.cpp b/icu4c/source/i18n/upluralrules.cpp
index 5119257..73e59a7 100644
--- a/icu4c/source/i18n/upluralrules.cpp
+++ b/icu4c/source/i18n/upluralrules.cpp
@@ -20,6 +20,7 @@
#include "unicode/unumberformatter.h"
#include "number_decimalquantity.h"
#include "number_utypes.h"
+#include "numrange_impl.h"
U_NAMESPACE_USE
@@ -116,6 +117,25 @@
}
U_CAPI int32_t U_EXPORT2
+uplrules_selectForRange(const UPluralRules *uplrules,
+ const UFormattedNumberRange* urange,
+ UChar *keyword, int32_t capacity,
+ UErrorCode *status)
+{
+ if (U_FAILURE(*status)) {
+ return 0;
+ }
+ if (keyword == NULL ? capacity != 0 : capacity < 0) {
+ *status = U_ILLEGAL_ARGUMENT_ERROR;
+ return 0;
+ }
+ const number::impl::UFormattedNumberRangeData* impl =
+ number::impl::validateUFormattedNumberRange(urange, *status);
+ UnicodeString result = ((PluralRules*)uplrules)->select(impl, *status);
+ return result.extract(keyword, capacity, *status);
+}
+
+U_CAPI int32_t U_EXPORT2
uplrules_selectWithFormat(const UPluralRules *uplrules,
double number,
const UNumberFormat *fmt,
diff --git a/icu4c/source/test/cintltst/cpluralrulestest.c b/icu4c/source/test/cintltst/cpluralrulestest.c
index b0ac873..e695154 100644
--- a/icu4c/source/test/cintltst/cpluralrulestest.c
+++ b/icu4c/source/test/cintltst/cpluralrulestest.c
@@ -14,6 +14,7 @@
#include "unicode/ustring.h"
#include "unicode/uenum.h"
#include "unicode/unumberformatter.h"
+#include "unicode/unumberrangeformatter.h"
#include "cintltst.h"
#include "cmemory.h"
#include "cstring.h"
@@ -22,6 +23,7 @@
static void TestOrdinalRules(void);
static void TestGetKeywords(void);
static void TestFormatted(void);
+static void TestSelectRange(void);
void addPluralRulesTest(TestNode** root);
@@ -33,6 +35,7 @@
TESTCASE(TestOrdinalRules);
TESTCASE(TestGetKeywords);
TESTCASE(TestFormatted);
+ TESTCASE(TestSelectRange);
}
typedef struct {
@@ -295,4 +298,56 @@
unumf_closeResult(uresult);
}
+static void TestSelectRange() {
+ UErrorCode ec = U_ZERO_ERROR;
+ UNumberRangeFormatter* unumrf = NULL;
+ UFormattedNumberRange* uresult = NULL;
+ UPluralRules* uplrules = NULL;
+
+ int32_t d1 = 102;
+ int32_t d2 = 201;
+
+ // Locale sl has interesting data: one + two => few
+ uplrules = uplrules_open("sl", &ec);
+ if (!assertSuccess("open plural rules", &ec)) {
+ goto cleanup;
+ }
+
+ unumrf = unumrf_openForSkeletonWithCollapseAndIdentityFallback(
+ u"",
+ 0,
+ UNUM_RANGE_COLLAPSE_AUTO,
+ UNUM_IDENTITY_FALLBACK_APPROXIMATELY,
+ "sl",
+ NULL,
+ &ec);
+ if (!assertSuccess("open unumrf", &ec)) {
+ goto cleanup;
+ }
+
+ uresult = unumrf_openResult(&ec);
+ if (!assertSuccess("open result", &ec)) {
+ goto cleanup;
+ }
+
+ unumrf_formatDoubleRange(unumrf, d1, d2, uresult, &ec);
+ if (!assertSuccess("format", &ec)) {
+ goto cleanup;
+ }
+
+ UChar buffer[40];
+ int32_t len = uplrules_selectForRange(uplrules, uresult, buffer, 40, &ec);
+ if (!assertSuccess("select", &ec)) {
+ goto cleanup;
+ }
+
+ assertUEquals("102-201 is plural category 'few' in sl", u"few", buffer);
+ assertIntEquals("Length should be as expected", u_strlen(buffer), len);
+
+cleanup:
+ uplrules_close(uplrules);
+ unumrf_close(unumrf);
+ unumrf_closeResult(uresult);
+}
+
#endif /* #if !UCONFIG_NO_FORMATTING */
diff --git a/icu4c/source/test/depstest/dependencies.txt b/icu4c/source/test/depstest/dependencies.txt
index bee3766..fda37de 100644
--- a/icu4c/source/test/depstest/dependencies.txt
+++ b/icu4c/source/test/depstest/dependencies.txt
@@ -974,7 +974,7 @@
group: number_output
# PluralRules and FormattedNumber
number_output.o
- standardplural.o plurrule.o
+ standardplural.o plurrule.o pluralranges.o
deps
# FormattedNumber internals:
number_representation format formatted_value_sbimpl units
diff --git a/icu4c/source/test/intltest/plurults.cpp b/icu4c/source/test/intltest/plurults.cpp
index b8a6c77..10f5251 100644
--- a/icu4c/source/test/intltest/plurults.cpp
+++ b/icu4c/source/test/intltest/plurults.cpp
@@ -23,6 +23,7 @@
#include "unicode/plurrule.h"
#include "unicode/stringpiece.h"
#include "unicode/numberformatter.h"
+#include "unicode/numberrangeformatter.h"
#include "cmemory.h"
#include "plurrule_impl.h"
@@ -53,6 +54,7 @@
TESTCASE_AUTO(testCompactDecimalPluralKeyword);
TESTCASE_AUTO(testOrdinal);
TESTCASE_AUTO(testSelect);
+ TESTCASE_AUTO(testSelectRange);
TESTCASE_AUTO(testAvailbleLocales);
TESTCASE_AUTO(testParseErrors);
TESTCASE_AUTO(testFixedDecimal);
@@ -925,6 +927,50 @@
}
+void PluralRulesTest::testSelectRange() {
+ IcuTestErrorCode status(*this, "testSelectRange");
+
+ int32_t d1 = 102;
+ int32_t d2 = 201;
+ Locale locale("sl");
+
+ // Locale sl has interesting data: one + two => few
+ auto range = NumberRangeFormatter::withLocale(locale).formatFormattableRange(d1, d2, status);
+ auto rules = LocalPointer<PluralRules>(PluralRules::forLocale(locale, status), status);
+ if (status.errIfFailureAndReset()) {
+ return;
+ }
+
+ // For testing: get plural form of first and second numbers
+ auto a = NumberFormatter::withLocale(locale).formatDouble(d1, status);
+ auto b = NumberFormatter::withLocale(locale).formatDouble(d2, status);
+ assertEquals("First plural", u"two", rules->select(a, status));
+ assertEquals("Second plural", u"one", rules->select(b, status));
+
+ // Check the range plural now:
+ auto form = rules->select(range, status);
+ assertEquals("Range plural", u"few", form);
+
+ // Test after copying:
+ PluralRules copy(*rules);
+ form = copy.select(range, status);
+ assertEquals("Range plural after copying", u"few", form);
+
+ // Test when plural ranges data is unavailable:
+ auto bare = LocalPointer<PluralRules>(
+ PluralRules::createRules(u"a: i = 0,1", status), status);
+ if (status.errIfFailureAndReset()) { return; }
+ form = bare->select(range, status);
+ status.expectErrorAndReset(U_UNSUPPORTED_ERROR);
+
+ // However, they should not set an error when no data is available for a language.
+ auto xyz = LocalPointer<PluralRules>(
+ PluralRules::forLocale("xyz", status));
+ form = xyz->select(range, status);
+ assertEquals("Fallback form", u"other", form);
+}
+
+
void PluralRulesTest::testAvailbleLocales() {
// Hash set of (char *) strings.
diff --git a/icu4c/source/test/intltest/plurults.h b/icu4c/source/test/intltest/plurults.h
index 301f852..2d7920d 100644
--- a/icu4c/source/test/intltest/plurults.h
+++ b/icu4c/source/test/intltest/plurults.h
@@ -35,6 +35,7 @@
void testCompactDecimalPluralKeyword();
void testOrdinal();
void testSelect();
+ void testSelectRange();
void testAvailbleLocales();
void testParseErrors();
void testFixedDecimal();
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/PluralRulesLoader.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/PluralRulesLoader.java
index c01d5be..e4d073a 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/impl/PluralRulesLoader.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/PluralRulesLoader.java
@@ -18,7 +18,7 @@
import java.util.Set;
import java.util.TreeMap;
-import com.ibm.icu.text.PluralRanges;
+import com.ibm.icu.impl.number.range.StandardPluralRanges;
import com.ibm.icu.text.PluralRules;
import com.ibm.icu.text.PluralRules.PluralType;
import com.ibm.icu.util.ULocale;
@@ -28,19 +28,19 @@
* Loader for plural rules data.
*/
public class PluralRulesLoader extends PluralRules.Factory {
- private final Map<String, PluralRules> rulesIdToRules;
+ // Key is rules set + ranges set
+ private final Map<String, PluralRules> pluralRulesCache;
// lazy init, use getLocaleIdToRulesIdMap to access
private Map<String, String> localeIdToCardinalRulesId;
private Map<String, String> localeIdToOrdinalRulesId;
private Map<String, ULocale> rulesIdToEquivalentULocale;
- private static Map<String, PluralRanges> localeIdToPluralRanges;
/**
* Access through singleton.
*/
private PluralRulesLoader() {
- rulesIdToRules = new HashMap<String, PluralRules>();
+ pluralRulesCache = new HashMap<String, PluralRules>();
}
/**
@@ -179,14 +179,20 @@
* Gets the rule from the rulesId. If there is no rule for this rulesId,
* return null.
*/
- public PluralRules getRulesForRulesId(String rulesId) {
+ public PluralRules getOrCreateRulesForLocale(ULocale locale, PluralRules.PluralType type) {
+ String rulesId = getRulesIdForLocale(locale, type);
+ if (rulesId == null || rulesId.trim().length() == 0) {
+ return null;
+ }
+ String rangesId = StandardPluralRanges.getSetForLocale(locale);
+ String cacheKey = rulesId + "/" + rangesId; // could end with "/null" (this is OK)
// synchronize on the map. release the lock temporarily while we build the rules.
PluralRules rules = null;
boolean hasRules; // Separate boolean because stored rules can be null.
- synchronized (rulesIdToRules) {
- hasRules = rulesIdToRules.containsKey(rulesId);
+ synchronized (pluralRulesCache) {
+ hasRules = pluralRulesCache.containsKey(cacheKey);
if (hasRules) {
- rules = rulesIdToRules.get(rulesId); // can be null
+ rules = pluralRulesCache.get(cacheKey); // can be null
}
}
if (!hasRules) {
@@ -205,15 +211,16 @@
sb.append(": ");
sb.append(b.getString());
}
- rules = PluralRules.parseDescription(sb.toString());
+ StandardPluralRanges ranges = StandardPluralRanges.forSet(rangesId);
+ rules = PluralRules.newInternal(sb.toString(), ranges);
} catch (ParseException e) {
} catch (MissingResourceException e) {
}
- synchronized (rulesIdToRules) {
- if (rulesIdToRules.containsKey(rulesId)) {
- rules = rulesIdToRules.get(rulesId);
+ synchronized (pluralRulesCache) {
+ if (pluralRulesCache.containsKey(cacheKey)) {
+ rules = pluralRulesCache.get(cacheKey);
} else {
- rulesIdToRules.put(rulesId, rules); // can be null
+ pluralRulesCache.put(cacheKey, rules); // can be null
}
}
}
@@ -235,11 +242,7 @@
* com.ibm.icu.text.PluralRules.DEFAULT is returned.
*/
public PluralRules forLocale(ULocale locale, PluralRules.PluralType type) {
- String rulesId = getRulesIdForLocale(locale, type);
- if (rulesId == null || rulesId.trim().length() == 0) {
- return PluralRules.DEFAULT;
- }
- PluralRules rules = getRulesForRulesId(rulesId);
+ PluralRules rules = getOrCreateRulesForLocale(locale, type);
if (rules == null) {
rules = PluralRules.DEFAULT;
}
@@ -258,245 +261,4 @@
public boolean hasOverride(ULocale locale) {
return false;
}
-
- private static final PluralRanges UNKNOWN_RANGE = new PluralRanges().freeze();
-
- public PluralRanges getPluralRanges(ULocale locale) {
- // TODO markdavis Fix the bad fallback, here and elsewhere in this file.
- String localeId = ULocale.canonicalize(locale.getBaseName());
- PluralRanges result;
- while (null == (result = localeIdToPluralRanges.get(localeId))) {
- int ix = localeId.lastIndexOf("_");
- if (ix == -1) {
- result = UNKNOWN_RANGE;
- break;
- }
- localeId = localeId.substring(0, ix);
- }
- return result;
- }
-
- public boolean isPluralRangesAvailable(ULocale locale) {
- return getPluralRanges(locale) == UNKNOWN_RANGE;
- }
-
- // TODO markdavis FIX HARD-CODED HACK once we have data from CLDR in the bundles
- static {
- String[][] pluralRangeData = {
- {"locales", "id ja km ko lo ms my th vi zh"},
- {"other", "other", "other"},
-
- {"locales", "am bn fr gu hi hy kn mr pa zu"},
- {"one", "one", "one"},
- {"one", "other", "other"},
- {"other", "other", "other"},
-
- {"locales", "fa"},
- {"one", "one", "other"},
- {"one", "other", "other"},
- {"other", "other", "other"},
-
- {"locales", "ka"},
- {"one", "other", "one"},
- {"other", "one", "other"},
- {"other", "other", "other"},
-
- {"locales", "az de el gl hu it kk ky ml mn ne nl pt sq sw ta te tr ug uz"},
- {"one", "other", "other"},
- {"other", "one", "one"},
- {"other", "other", "other"},
-
- {"locales", "af bg ca en es et eu fi nb sv ur"},
- {"one", "other", "other"},
- {"other", "one", "other"},
- {"other", "other", "other"},
-
- {"locales", "da fil is"},
- {"one", "one", "one"},
- {"one", "other", "other"},
- {"other", "one", "one"},
- {"other", "other", "other"},
-
- {"locales", "si"},
- {"one", "one", "one"},
- {"one", "other", "other"},
- {"other", "one", "other"},
- {"other", "other", "other"},
-
- {"locales", "mk"},
- {"one", "one", "other"},
- {"one", "other", "other"},
- {"other", "one", "other"},
- {"other", "other", "other"},
-
- {"locales", "lv"},
- {"zero", "zero", "other"},
- {"zero", "one", "one"},
- {"zero", "other", "other"},
- {"one", "zero", "other"},
- {"one", "one", "one"},
- {"one", "other", "other"},
- {"other", "zero", "other"},
- {"other", "one", "one"},
- {"other", "other", "other"},
-
- {"locales", "ro"},
- {"one", "few", "few"},
- {"one", "other", "other"},
- {"few", "one", "few"},
- {"few", "few", "few"},
- {"few", "other", "other"},
- {"other", "few", "few"},
- {"other", "other", "other"},
-
- {"locales", "hr sr bs"},
- {"one", "one", "one"},
- {"one", "few", "few"},
- {"one", "other", "other"},
- {"few", "one", "one"},
- {"few", "few", "few"},
- {"few", "other", "other"},
- {"other", "one", "one"},
- {"other", "few", "few"},
- {"other", "other", "other"},
-
- {"locales", "sl"},
- {"one", "one", "few"},
- {"one", "two", "two"},
- {"one", "few", "few"},
- {"one", "other", "other"},
- {"two", "one", "few"},
- {"two", "two", "two"},
- {"two", "few", "few"},
- {"two", "other", "other"},
- {"few", "one", "few"},
- {"few", "two", "two"},
- {"few", "few", "few"},
- {"few", "other", "other"},
- {"other", "one", "few"},
- {"other", "two", "two"},
- {"other", "few", "few"},
- {"other", "other", "other"},
-
- {"locales", "he"},
- {"one", "two", "other"},
- {"one", "many", "many"},
- {"one", "other", "other"},
- {"two", "many", "other"},
- {"two", "other", "other"},
- {"many", "many", "many"},
- {"many", "other", "many"},
- {"other", "one", "other"},
- {"other", "two", "other"},
- {"other", "many", "many"},
- {"other", "other", "other"},
-
- {"locales", "cs pl sk"},
- {"one", "few", "few"},
- {"one", "many", "many"},
- {"one", "other", "other"},
- {"few", "few", "few"},
- {"few", "many", "many"},
- {"few", "other", "other"},
- {"many", "one", "one"},
- {"many", "few", "few"},
- {"many", "many", "many"},
- {"many", "other", "other"},
- {"other", "one", "one"},
- {"other", "few", "few"},
- {"other", "many", "many"},
- {"other", "other", "other"},
-
- {"locales", "lt ru uk"},
- {"one", "one", "one"},
- {"one", "few", "few"},
- {"one", "many", "many"},
- {"one", "other", "other"},
- {"few", "one", "one"},
- {"few", "few", "few"},
- {"few", "many", "many"},
- {"few", "other", "other"},
- {"many", "one", "one"},
- {"many", "few", "few"},
- {"many", "many", "many"},
- {"many", "other", "other"},
- {"other", "one", "one"},
- {"other", "few", "few"},
- {"other", "many", "many"},
- {"other", "other", "other"},
-
- {"locales", "cy"},
- {"zero", "one", "one"},
- {"zero", "two", "two"},
- {"zero", "few", "few"},
- {"zero", "many", "many"},
- {"zero", "other", "other"},
- {"one", "two", "two"},
- {"one", "few", "few"},
- {"one", "many", "many"},
- {"one", "other", "other"},
- {"two", "few", "few"},
- {"two", "many", "many"},
- {"two", "other", "other"},
- {"few", "many", "many"},
- {"few", "other", "other"},
- {"many", "other", "other"},
- {"other", "one", "one"},
- {"other", "two", "two"},
- {"other", "few", "few"},
- {"other", "many", "many"},
- {"other", "other", "other"},
-
- {"locales", "ar"},
- {"zero", "one", "zero"},
- {"zero", "two", "zero"},
- {"zero", "few", "few"},
- {"zero", "many", "many"},
- {"zero", "other", "other"},
- {"one", "two", "other"},
- {"one", "few", "few"},
- {"one", "many", "many"},
- {"one", "other", "other"},
- {"two", "few", "few"},
- {"two", "many", "many"},
- {"two", "other", "other"},
- {"few", "few", "few"},
- {"few", "many", "many"},
- {"few", "other", "other"},
- {"many", "few", "few"},
- {"many", "many", "many"},
- {"many", "other", "other"},
- {"other", "one", "other"},
- {"other", "two", "other"},
- {"other", "few", "few"},
- {"other", "many", "many"},
- {"other", "other", "other"},
- };
- PluralRanges pr = null;
- String[] locales = null;
- HashMap<String, PluralRanges> tempLocaleIdToPluralRanges = new HashMap<String, PluralRanges>();
- for (String[] row : pluralRangeData) {
- if (row[0].equals("locales")) {
- if (pr != null) {
- pr.freeze();
- for (String locale : locales) {
- tempLocaleIdToPluralRanges.put(locale, pr);
- }
- }
- locales = row[1].split(" ");
- pr = new PluralRanges();
- } else {
- pr.add(
- StandardPlural.fromString(row[0]),
- StandardPlural.fromString(row[1]),
- StandardPlural.fromString(row[2]));
- }
- }
- // do last one
- for (String locale : locales) {
- tempLocaleIdToPluralRanges.put(locale, pr);
- }
- // now make whole thing immutable
- localeIdToPluralRanges = Collections.unmodifiableMap(tempLocaleIdToPluralRanges);
- }
}
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/range/StandardPluralRanges.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/range/StandardPluralRanges.java
index 3fb8119..78c5e7a 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/range/StandardPluralRanges.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/range/StandardPluralRanges.java
@@ -2,7 +2,9 @@
// License & terms of use: http://www.unicode.org/copyright.html
package com.ibm.icu.impl.number.range;
-import java.util.MissingResourceException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
import com.ibm.icu.impl.ICUData;
import com.ibm.icu.impl.ICUResourceBundle;
@@ -10,6 +12,7 @@
import com.ibm.icu.impl.UResource;
import com.ibm.icu.util.ULocale;
import com.ibm.icu.util.UResourceBundle;
+import com.ibm.icu.util.UResourceTypeMismatchException;
/**
* @author sffc
@@ -20,8 +23,54 @@
StandardPlural[] flatTriples;
int numTriples = 0;
+ /**
+ * An immutable map from language codes to set IDs.
+ * Pre-computed and cached in Java since it is used as a cache key for PluralRules.
+ */
+ private static volatile Map<String, String> languageToSet;
+
+ /** An empty StandardPluralRanges instance. */
+ public static final StandardPluralRanges DEFAULT = new StandardPluralRanges();
+
////////////////////
+ private static final class PluralRangeSetsDataSink extends UResource.Sink {
+
+ Map<String, String> output;
+
+ PluralRangeSetsDataSink(Map<String, String> output) {
+ this.output = output;
+ }
+
+ @Override
+ public void put(UResource.Key key, UResource.Value value, boolean noFallback) {
+ UResource.Table table = value.getTable();
+ for (int i = 0; table.getKeyAndValue(i, key, value); ++i) {
+ // The data has only languages; no regions/scripts. If this changes, this
+ // code and languageToSet will need to change.
+ assert key.toString().equals(new ULocale(key.toString()).getLanguage());
+ output.put(key.toString(), value.toString());
+ }
+ }
+ }
+
+ private static Map<String, String> getLanguageToSet() {
+ Map<String, String> candidate = languageToSet;
+ if (candidate == null) {
+ Map<String, String> map = new HashMap<String, String>();
+ PluralRangeSetsDataSink sink = new PluralRangeSetsDataSink(map);
+ ICUResourceBundle resource = (ICUResourceBundle)
+ UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME, "pluralRanges");
+ resource.getAllItemsWithFallback("locales", sink);
+ candidate = Collections.unmodifiableMap(map);
+ }
+ // Check if another thread set languageToSet in the mean time
+ if (languageToSet == null) {
+ languageToSet = candidate;
+ }
+ return languageToSet;
+ }
+
private static final class PluralRangesDataSink extends UResource.Sink {
StandardPluralRanges output;
@@ -36,6 +85,10 @@
output.setCapacity(entriesArray.getSize());
for (int i = 0; entriesArray.getValue(i, value); ++i) {
UResource.Array pluralFormsArray = value.getArray();
+ if (pluralFormsArray.getSize() != 3) {
+ throw new UResourceTypeMismatchException(
+ "Expected 3 elements in pluralRanges.txt array");
+ }
pluralFormsArray.getValue(0, value);
StandardPlural first = StandardPlural.fromString(value.getString());
pluralFormsArray.getValue(1, value);
@@ -48,34 +101,43 @@
}
private static void getPluralRangesData(
- ULocale locale,
+ String set,
StandardPluralRanges out) {
StringBuilder sb = new StringBuilder();
ICUResourceBundle resource;
resource = (ICUResourceBundle) UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME, "pluralRanges");
- sb.append("locales/");
- sb.append(locale.getLanguage());
- String key = sb.toString();
- String set;
- try {
- set = resource.getStringWithFallback(key);
- } catch (MissingResourceException e) {
- // Not all languages are covered: fail gracefully
- return;
- }
-
sb.setLength(0);
sb.append("rules/");
sb.append(set);
- key = sb.toString();
+ String key = sb.toString();
PluralRangesDataSink sink = new PluralRangesDataSink(out);
resource.getAllItemsWithFallback(key, sink);
}
////////////////////
- public StandardPluralRanges(ULocale locale) {
- getPluralRangesData(locale, this);
+ /** Create a StandardPluralRanges based on locale. */
+ public static StandardPluralRanges forLocale(ULocale locale) {
+ return forSet(getSetForLocale(locale));
+ }
+
+ /** Create a StandardPluralRanges based on set name. */
+ public static StandardPluralRanges forSet(String set) {
+ StandardPluralRanges result = new StandardPluralRanges();
+ if (set == null) {
+ // Not all languages are covered: fail gracefully
+ return DEFAULT;
+ }
+ getPluralRangesData(set, result);
+ return result;
+ }
+
+ /** Get the set name from the locale. */
+ public static String getSetForLocale(ULocale locale) {
+ return getLanguageToSet().get(locale.getLanguage());
+ }
+
+ private StandardPluralRanges() {
}
/** Used for data loading. */
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/FormattedNumberRange.java b/icu4j/main/classes/core/src/com/ibm/icu/number/FormattedNumberRange.java
index 9180589..90d23c3 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/number/FormattedNumberRange.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/number/FormattedNumberRange.java
@@ -13,6 +13,7 @@
import com.ibm.icu.number.NumberRangeFormatter.RangeIdentityResult;
import com.ibm.icu.text.ConstrainedFieldPosition;
import com.ibm.icu.text.FormattedValue;
+import com.ibm.icu.text.PluralRules.IFixedDecimal;
import com.ibm.icu.util.ICUUncheckedIOException;
/**
@@ -189,4 +190,22 @@
&& quantity1.toBigDecimal().equals(_other.quantity1.toBigDecimal())
&& quantity2.toBigDecimal().equals(_other.quantity2.toBigDecimal());
}
+
+ /**
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ @Deprecated
+ public IFixedDecimal getFirstFixedDecimal() {
+ return quantity1;
+ }
+
+ /**
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ @Deprecated
+ public IFixedDecimal getSecondFixedDecimal() {
+ return quantity2;
+ }
}
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberRangeFormatterImpl.java b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberRangeFormatterImpl.java
index 38bd806..503344a 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberRangeFormatterImpl.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberRangeFormatterImpl.java
@@ -148,7 +148,7 @@
getNumberRangeData(macros.loc, nsName, this);
// TODO: Get locale from PluralRules instead?
- fPluralRanges = new StandardPluralRanges(macros.loc);
+ fPluralRanges = StandardPluralRanges.forLocale(macros.loc);
}
public FormattedNumberRange format(DecimalQuantity quantity1, DecimalQuantity quantity2, boolean equalBeforeRounding) {
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/PluralRanges.java b/icu4j/main/classes/core/src/com/ibm/icu/text/PluralRanges.java
deleted file mode 100644
index fbb8afe..0000000
--- a/icu4j/main/classes/core/src/com/ibm/icu/text/PluralRanges.java
+++ /dev/null
@@ -1,366 +0,0 @@
-// © 2016 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html
-/*
- *******************************************************************************
- * Copyright (C) 2008-2015, Google, International Business Machines Corporation and
- * others. All Rights Reserved.
- *******************************************************************************
- */
-package com.ibm.icu.text;
-
-import java.util.Arrays;
-import java.util.EnumSet;
-
-import com.ibm.icu.impl.StandardPlural;
-import com.ibm.icu.util.Freezable;
-import com.ibm.icu.util.Output;
-
-/**
- * Utility class for returning the plural category for a range of numbers, such as 1–5, so that appropriate messages can
- * be chosen. The rules for determining this value vary widely across locales.
- *
- * @author markdavis
- * @internal
- * @deprecated This API is ICU internal only.
- */
-@Deprecated
-public final class PluralRanges implements Freezable<PluralRanges>, Comparable<PluralRanges> {
-
- private volatile boolean isFrozen;
- private Matrix matrix = new Matrix();
- private boolean[] explicit = new boolean[StandardPlural.COUNT];
-
- /**
- * Constructor
- *
- * @internal
- * @deprecated This API is ICU internal only.
- */
- @Deprecated
- public PluralRanges() {
- }
-
- /**
- * Internal class for mapping from two StandardPluralCategories values to another.
- */
- private static final class Matrix implements Comparable<Matrix>, Cloneable {
- private byte[] data = new byte[StandardPlural.COUNT * StandardPlural.COUNT];
- {
- for (int i = 0; i < data.length; ++i) {
- data[i] = -1;
- }
- }
-
- Matrix() {
- }
-
- /**
- * Internal method for setting.
- */
- @SuppressWarnings("unused")
- void set(StandardPlural start, StandardPlural end, StandardPlural result) {
- data[start.ordinal() * StandardPlural.COUNT + end.ordinal()] = result == null ? (byte) -1
- : (byte) result.ordinal();
- }
-
- /**
- * Internal method for setting; throws exception if already set.
- */
- void setIfNew(StandardPlural start, StandardPlural end,
- StandardPlural result) {
- byte old = data[start.ordinal() * StandardPlural.COUNT + end.ordinal()];
- if (old >= 0) {
- throw new IllegalArgumentException("Previously set value for <" + start + ", " + end + ", "
- + StandardPlural.VALUES.get(old) + ">");
- }
- data[start.ordinal() * StandardPlural.COUNT + end.ordinal()] = result == null ? (byte) -1
- : (byte) result.ordinal();
- }
-
- /**
- * Internal method for getting.
- */
- StandardPlural get(StandardPlural start, StandardPlural end) {
- byte result = data[start.ordinal() * StandardPlural.COUNT + end.ordinal()];
- return result < 0 ? null : StandardPlural.VALUES.get(result);
- }
-
- /**
- * Internal method to see if <*,end> values are all the same.
- */
- @SuppressWarnings("unused")
- StandardPlural endSame(StandardPlural end) {
- StandardPlural first = null;
- for (StandardPlural start : StandardPlural.VALUES) {
- StandardPlural item = get(start, end);
- if (item == null) {
- continue;
- }
- if (first == null) {
- first = item;
- continue;
- }
- if (first != item) {
- return null;
- }
- }
- return first;
- }
-
- /**
- * Internal method to see if <start,*> values are all the same.
- */
- @SuppressWarnings("unused")
- StandardPlural startSame(StandardPlural start,
- EnumSet<StandardPlural> endDone, Output<Boolean> emit) {
- emit.value = false;
- StandardPlural first = null;
- for (StandardPlural end : StandardPlural.VALUES) {
- StandardPlural item = get(start, end);
- if (item == null) {
- continue;
- }
- if (first == null) {
- first = item;
- continue;
- }
- if (first != item) {
- return null;
- }
- // only emit if we didn't cover with the 'end' values
- if (!endDone.contains(end)) {
- emit.value = true;
- }
- }
- return first;
- }
-
- @Override
- public int hashCode() {
- int result = 0;
- for (int i = 0; i < data.length; ++i) {
- result = result * 37 + data[i];
- }
- return result;
- }
-
- @Override
- public boolean equals(Object other) {
- if (!(other instanceof Matrix)) {
- return false;
- }
- return 0 == compareTo((Matrix) other);
- }
-
- @Override
- public int compareTo(Matrix o) {
- for (int i = 0; i < data.length; ++i) {
- int diff = data[i] - o.data[i];
- if (diff != 0) {
- return diff;
- }
- }
- return 0;
- }
-
- @Override
- public Matrix clone() {
- Matrix result = new Matrix();
- result.data = data.clone();
- return result;
- }
-
- @Override
- public String toString() {
- StringBuilder result = new StringBuilder();
- for (StandardPlural i : StandardPlural.values()) {
- for (StandardPlural j : StandardPlural.values()) {
- StandardPlural x = get(i, j);
- if (x != null) {
- result.append(i + " & " + j + " → " + x + ";\n");
- }
- }
- }
- return result.toString();
- }
- }
-
- /**
- * Internal method for building. If the start or end are null, it means everything of that type.
- *
- * @param rangeStart
- * plural category for the start of the range
- * @param rangeEnd
- * plural category for the end of the range
- * @param result
- * the resulting plural category
- * @internal
- * @deprecated This API is ICU internal only.
- */
- @Deprecated
- public void add(StandardPlural rangeStart, StandardPlural rangeEnd,
- StandardPlural result) {
- if (isFrozen) {
- throw new UnsupportedOperationException();
- }
- explicit[result.ordinal()] = true;
- if (rangeStart == null) {
- for (StandardPlural rs : StandardPlural.values()) {
- if (rangeEnd == null) {
- for (StandardPlural re : StandardPlural.values()) {
- matrix.setIfNew(rs, re, result);
- }
- } else {
- explicit[rangeEnd.ordinal()] = true;
- matrix.setIfNew(rs, rangeEnd, result);
- }
- }
- } else if (rangeEnd == null) {
- explicit[rangeStart.ordinal()] = true;
- for (StandardPlural re : StandardPlural.values()) {
- matrix.setIfNew(rangeStart, re, result);
- }
- } else {
- explicit[rangeStart.ordinal()] = true;
- explicit[rangeEnd.ordinal()] = true;
- matrix.setIfNew(rangeStart, rangeEnd, result);
- }
- }
-
- /**
- * Returns the appropriate plural category for a range from start to end. If there is no available data, then
- * 'end' is returned as an implicit value. (Such an implicit value can be tested for with {@link #isExplicit}.)
- *
- * @param start
- * plural category for the start of the range
- * @param end
- * plural category for the end of the range
- * @return the resulting plural category, or 'end' if there is no data.
- * @internal
- * @deprecated This API is ICU internal only.
- */
- @Deprecated
- public StandardPlural get(StandardPlural start, StandardPlural end) {
- StandardPlural result = matrix.get(start, end);
- return result == null ? end : result;
- }
-
- /**
- * Returns whether the appropriate plural category for a range from start to end
- * is explicitly in the data (vs given an implicit value). See also {@link #get}.
- *
- * @param start
- * plural category for the start of the range
- * @param end
- * plural category for the end of the range
- * @return whether the value for (start,end) is explicit or not.
- * @internal
- * @deprecated This API is ICU internal only.
- */
- @Deprecated
- public boolean isExplicit(StandardPlural start, StandardPlural end) {
- return matrix.get(start, end) != null;
- }
-
- /**
- * Internal method to determines whether the StandardPluralCategories was explicitly used in any add statement.
- *
- * @param count
- * plural category to test
- * @return true if set
- * @internal
- * @deprecated This API is ICU internal only.
- */
- @Deprecated
- public boolean isExplicitlySet(StandardPlural count) {
- return explicit[count.ordinal()];
- }
-
- /**
- * {@inheritDoc}
- * @internal
- * @deprecated This API is ICU internal only.
- */
- @Deprecated
- @Override
- public boolean equals(Object other) {
- if (this == other) {
- return true;
- }
- if (!(other instanceof PluralRanges)) {
- return false;
- }
- PluralRanges otherPR = (PluralRanges)other;
- return matrix.equals(otherPR.matrix) && Arrays.equals(explicit, otherPR.explicit);
- }
-
- /**
- * {@inheritDoc}
- * @internal
- * @deprecated This API is ICU internal only.
- */
- @Override
- @Deprecated
- public int hashCode() {
- return matrix.hashCode();
- }
-
- /**
- * {@inheritDoc}
- * @internal
- * @deprecated This API is ICU internal only.
- */
- @Override
- @Deprecated
- public int compareTo(PluralRanges that) {
- return matrix.compareTo(that.matrix);
- }
-
- /**
- * {@inheritDoc}
- * @internal
- * @deprecated This API is ICU internal only.
- */
- @Override
- @Deprecated
- public boolean isFrozen() {
- return isFrozen;
- }
-
- /**
- * {@inheritDoc}
- * @internal
- * @deprecated This API is ICU internal only.
- */
- @Override
- @Deprecated
- public PluralRanges freeze() {
- isFrozen = true;
- return this;
- }
-
- /**
- * {@inheritDoc}
- * @internal
- * @deprecated This API is ICU internal only.
- */
- @Override
- @Deprecated
- public PluralRanges cloneAsThawed() {
- PluralRanges result = new PluralRanges();
- result.explicit = explicit.clone();
- result.matrix = matrix.clone();
- return result;
- }
-
- /**
- * {@inheritDoc}
- * @internal
- * @deprecated This API is ICU internal only.
- */
- @Override
- @Deprecated
- public String toString() {
- return matrix.toString();
- }
-}
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/PluralRules.java b/icu4j/main/classes/core/src/com/ibm/icu/text/PluralRules.java
index dfce204..0c1a405 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/text/PluralRules.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/text/PluralRules.java
@@ -29,7 +29,10 @@
import java.util.regex.Pattern;
import com.ibm.icu.impl.PluralRulesLoader;
+import com.ibm.icu.impl.StandardPlural;
+import com.ibm.icu.impl.number.range.StandardPluralRanges;
import com.ibm.icu.number.FormattedNumber;
+import com.ibm.icu.number.FormattedNumberRange;
import com.ibm.icu.number.NumberFormatter;
import com.ibm.icu.util.Output;
import com.ibm.icu.util.ULocale;
@@ -181,6 +184,7 @@
private final RuleList rules;
private final transient Set<String> keywords;
+ private final transient StandardPluralRanges standardPluralRanges;
/**
* Provides a factory for returning plural rules
@@ -377,9 +381,7 @@
*/
public static PluralRules parseDescription(String description)
throws ParseException {
-
- description = description.trim();
- return description.length() == 0 ? DEFAULT : new PluralRules(parseRuleChain(description));
+ return newInternal(description, null);
}
/**
@@ -398,11 +400,24 @@
}
/**
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ public static PluralRules newInternal(String description, StandardPluralRanges ranges)
+ throws ParseException {
+ description = description.trim();
+ return description.length() == 0
+ ? DEFAULT
+ : new PluralRules(parseRuleChain(description), ranges);
+ }
+
+ /**
* The default rules that accept any number and return
* {@link #KEYWORD_OTHER}.
* @stable ICU 3.8
*/
- public static final PluralRules DEFAULT = new PluralRules(new RuleList().addRule(DEFAULT_RULE));
+ public static final PluralRules DEFAULT = new PluralRules(
+ new RuleList().addRule(DEFAULT_RULE), StandardPluralRanges.DEFAULT);
/**
* @internal CLDR
@@ -2016,9 +2031,10 @@
/*
* Creates a new <code>PluralRules</code> object. Immutable.
*/
- private PluralRules(RuleList rules) {
+ private PluralRules(RuleList rules, StandardPluralRanges standardPluralRanges) {
this.rules = rules;
this.keywords = Collections.unmodifiableSet(rules.getKeywords());
+ this.standardPluralRanges = standardPluralRanges;
}
/**
@@ -2059,6 +2075,34 @@
}
/**
+ * Given a formatted number range, returns the overall plural form of the
+ * range. For example, "3-5" returns "other" in English.
+ *
+ * To get a FormattedNumberRange, see {@link com.ibm.icu.number.NumberRangeFormatter}.
+ *
+ * This method only works if PluralRules was created with a locale. If it was created
+ * from PluralRules.createRules(), or if it was deserialized, this method throws
+ * UnsupportedOperationException.
+ *
+ * @param range The number range onto which the rules will be applied.
+ * @return The keyword of the selected rule.
+ * @throws UnsupportedOperationException If called on an instance without plural ranges data.
+ * @draft ICU 68
+ * @provisional This API might change or be removed in a future release.
+ */
+ public String select(FormattedNumberRange range) {
+ if (standardPluralRanges == null) {
+ throw new UnsupportedOperationException("Plural ranges are unavailable on this instance");
+ }
+ StandardPlural form1 = StandardPlural.fromString(
+ select(range.getFirstFixedDecimal()));
+ StandardPlural form2 = StandardPlural.fromString(
+ select(range.getSecondFixedDecimal()));
+ StandardPlural result = standardPluralRanges.resolve(form1, form2);
+ return result.getKeyword();
+ }
+
+ /**
* Given a number, returns the keyword of the first rule that applies to
* the number.
*
diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/PluralRangesTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/PluralRangesTest.java
index ffdaf98..2d6d667 100644
--- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/PluralRangesTest.java
+++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/PluralRangesTest.java
@@ -10,7 +10,6 @@
import java.util.Arrays;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -18,12 +17,14 @@
import com.ibm.icu.dev.test.TestFmwk;
import com.ibm.icu.impl.SimpleFormatterImpl;
import com.ibm.icu.impl.StandardPlural;
+import com.ibm.icu.impl.number.range.StandardPluralRanges;
+import com.ibm.icu.number.FormattedNumberRange;
+import com.ibm.icu.number.NumberFormatter;
+import com.ibm.icu.number.NumberFormatter.UnitWidth;
+import com.ibm.icu.number.NumberRangeFormatter;
import com.ibm.icu.text.MeasureFormat;
import com.ibm.icu.text.MeasureFormat.FormatWidth;
-import com.ibm.icu.text.PluralRanges;
-import com.ibm.icu.text.PluralRules.Factory;
import com.ibm.icu.util.Currency;
-import com.ibm.icu.util.Measure;
import com.ibm.icu.util.MeasureUnit;
import com.ibm.icu.util.ULocale;
@@ -37,7 +38,7 @@
public void TestLocaleData() {
String[][] tests = {
{"de", "other", "one", "one"},
- {"xxx", "few", "few", "few" },
+ {"xxx", "other", "other", "other" },
{"de", "one", "other", "other"},
{"de", "other", "one", "one"},
{"de", "other", "other", "other"},
@@ -50,9 +51,9 @@
final StandardPlural start = StandardPlural.fromString(test[1]);
final StandardPlural end = StandardPlural.fromString(test[2]);
final StandardPlural expected = StandardPlural.fromString(test[3]);
- final PluralRanges pluralRanges = Factory.getDefaultFactory().getPluralRanges(locale);
+ final StandardPluralRanges pluralRanges = StandardPluralRanges.forLocale(locale);
- StandardPlural actual = pluralRanges.get(start, end);
+ StandardPlural actual = pluralRanges.resolve(start, end);
assertEquals("Deriving range category", expected, actual);
}
}
@@ -73,30 +74,28 @@
}
}
- // TODO: Re-enable this test when #12454 is fixed.
- @Ignore("http://bugs.icu-project.org/trac/ticket/12454")
@Test
public void TestFormatting() {
Object[][] tests = {
- {0.0, 1.0, ULocale.FRANCE, FormatWidth.WIDE, MeasureUnit.FAHRENHEIT, "0–1 degré Fahrenheit"},
- {1.0, 2.0, ULocale.FRANCE, FormatWidth.WIDE, MeasureUnit.FAHRENHEIT, "1–2 degrés Fahrenheit"},
- {3.1, 4.25, ULocale.FRANCE, FormatWidth.SHORT, MeasureUnit.FAHRENHEIT, "3,1–4,25 °F"},
- {3.1, 4.25, ULocale.ENGLISH, FormatWidth.SHORT, MeasureUnit.FAHRENHEIT, "3.1–4.25°F"},
- {3.1, 4.25, ULocale.CHINESE, FormatWidth.WIDE, MeasureUnit.INCH, "3.1-4.25英寸"},
- {0.0, 1.0, ULocale.ENGLISH, FormatWidth.WIDE, MeasureUnit.INCH, "0–1 inches"},
+ {0.0, 1.0, ULocale.FRANCE, UnitWidth.FULL_NAME, MeasureUnit.FAHRENHEIT, "0–1\u00A0degré Fahrenheit"},
+ {1.0, 2.0, ULocale.FRANCE, UnitWidth.FULL_NAME, MeasureUnit.FAHRENHEIT, "1–2\u00A0degrés Fahrenheit"},
+ {3.1, 4.25, ULocale.FRANCE, UnitWidth.SHORT, MeasureUnit.FAHRENHEIT, "3,1–4,25\u202F°F"},
+ {3.1, 4.25, ULocale.ENGLISH, UnitWidth.SHORT, MeasureUnit.FAHRENHEIT, "3.1–4.25°F"},
+ {3.1, 4.25, ULocale.CHINESE, UnitWidth.FULL_NAME, MeasureUnit.INCH, "3.1-4.25英寸"},
+ {0.0, 1.0, ULocale.ENGLISH, UnitWidth.FULL_NAME, MeasureUnit.INCH, "0–1 inches"},
- {0.0, 1.0, ULocale.ENGLISH, FormatWidth.NARROW, Currency.getInstance("EUR"), "€0.00–1.00"},
- {0.0, 1.0, ULocale.FRENCH, FormatWidth.NARROW, Currency.getInstance("EUR"), "0,00–1,00 €"},
- {0.0, 100.0, ULocale.FRENCH, FormatWidth.NARROW, Currency.getInstance("JPY"), "0–100\u00a0JPY"},
+ {0.0, 1.0, ULocale.ENGLISH, UnitWidth.NARROW, Currency.getInstance("EUR"), "€0.00 – €1.00"},
+ {0.0, 1.0, ULocale.FRENCH, UnitWidth.NARROW, Currency.getInstance("EUR"), "0,00–1,00 €"},
+ {0.0, 100.0, ULocale.FRENCH, UnitWidth.NARROW, Currency.getInstance("JPY"), "0–100\u00a0¥"},
- {0.0, 1.0, ULocale.ENGLISH, FormatWidth.SHORT, Currency.getInstance("EUR"), "EUR0.00–1.00"},
- {0.0, 1.0, ULocale.FRENCH, FormatWidth.SHORT, Currency.getInstance("EUR"), "0,00–1,00\u00a0EUR"},
- {0.0, 100.0, ULocale.FRENCH, FormatWidth.SHORT, Currency.getInstance("JPY"), "0–100\u00a0JPY"},
+ {0.0, 1.0, ULocale.ENGLISH, UnitWidth.SHORT, Currency.getInstance("EUR"), "€0.00 – €1.00"},
+ {0.0, 1.0, ULocale.FRENCH, UnitWidth.SHORT, Currency.getInstance("EUR"), "0,00–1,00\u00a0€"},
+ {0.0, 100.0, ULocale.FRENCH, UnitWidth.SHORT, Currency.getInstance("JPY"), "0–100\u00a0JPY"},
- {0.0, 1.0, ULocale.ENGLISH, FormatWidth.WIDE, Currency.getInstance("EUR"), "0.00–1.00 euros"},
- {0.0, 1.0, ULocale.FRENCH, FormatWidth.WIDE, Currency.getInstance("EUR"), "0,00–1,00 euro"},
- {0.0, 2.0, ULocale.FRENCH, FormatWidth.WIDE, Currency.getInstance("EUR"), "0,00–2,00 euros"},
- {0.0, 100.0, ULocale.FRENCH, FormatWidth.WIDE, Currency.getInstance("JPY"), "0–100 yens japonais"},
+ {0.0, 1.0, ULocale.ENGLISH, UnitWidth.FULL_NAME, Currency.getInstance("EUR"), "0.00–1.00 euros"},
+ {0.0, 1.0, ULocale.FRENCH, UnitWidth.FULL_NAME, Currency.getInstance("EUR"), "0,00–1,00 euro"},
+ {0.0, 2.0, ULocale.FRENCH, UnitWidth.FULL_NAME, Currency.getInstance("EUR"), "0,00–2,00 euros"},
+ {0.0, 100.0, ULocale.FRENCH, UnitWidth.FULL_NAME, Currency.getInstance("JPY"), "0–100 yens japonais"},
};
int i = 0;
for (Object[] test : tests) {
@@ -104,34 +103,15 @@
double low = (Double) test[0];
double high = (Double) test[1];
final ULocale locale = (ULocale) test[2];
- final FormatWidth width = (FormatWidth) test[3];
+ final UnitWidth unitWidth = (UnitWidth) test[3];
final MeasureUnit unit = (MeasureUnit) test[4];
- final Object expected = test[5];
+ final String expected = (String) test[5];
- MeasureFormat mf = MeasureFormat.getInstance(locale, width);
- Object actual;
- try {
- // TODO: Fix this when range formatting is added again.
- // To let the code compile, the following line does list formatting.
- actual = mf.formatMeasures(new Measure(low, unit), new Measure(high, unit));
- } catch (Exception e) {
- actual = e.getClass();
- }
- assertEquals(i + " Formatting unit", expected, actual);
- }
- }
-
- @Test
- public void TestBasic() {
- PluralRanges a = new PluralRanges();
- a.add(StandardPlural.ONE, StandardPlural.OTHER, StandardPlural.ONE);
- StandardPlural actual = a.get(StandardPlural.ONE, StandardPlural.OTHER);
- assertEquals("range", StandardPlural.ONE, actual);
- a.freeze();
- try {
- a.add(StandardPlural.ONE, StandardPlural.ONE, StandardPlural.ONE);
- errln("Failed to cause exception on frozen instance");
- } catch (UnsupportedOperationException e) {
+ FormattedNumberRange actual = NumberRangeFormatter.with()
+ .numberFormatterBoth(NumberFormatter.with().unit(unit).unitWidth(unitWidth))
+ .locale(locale)
+ .formatRange(low, high);
+ assertEquals(i + " Formatting unit", expected, actual.toString());
}
}
}
diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/PluralRulesTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/PluralRulesTest.java
index 6461d4a..5c2c303 100644
--- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/PluralRulesTest.java
+++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/PluralRulesTest.java
@@ -42,8 +42,10 @@
import com.ibm.icu.impl.Relation;
import com.ibm.icu.impl.Utility;
import com.ibm.icu.number.FormattedNumber;
+import com.ibm.icu.number.FormattedNumberRange;
import com.ibm.icu.number.LocalizedNumberFormatter;
import com.ibm.icu.number.NumberFormatter;
+import com.ibm.icu.number.NumberRangeFormatter;
import com.ibm.icu.number.Precision;
import com.ibm.icu.number.UnlocalizedNumberFormatter;
import com.ibm.icu.text.NumberFormat;
@@ -1279,4 +1281,37 @@
Locale.setDefault(Locale.GERMAN);
assertEquals("FixedDecimal toString", expected, fd.toString());
}
+
+ @Test
+ public void testSelectRange() {
+ int d1 = 102;
+ int d2 = 201;
+ ULocale locale = new ULocale("sl");
+
+ // Locale sl has interesting data: one + two => few
+ FormattedNumberRange range = NumberRangeFormatter.withLocale(locale).formatRange(d1, d2);
+ PluralRules rules = PluralRules.forLocale(locale);
+
+ // For testing: get plural form of first and second numbers
+ FormattedNumber a = NumberFormatter.withLocale(locale).format(d1);
+ FormattedNumber b = NumberFormatter.withLocale(locale).format(d2);
+ assertEquals("First plural", "two", rules.select(a));
+ assertEquals("Second plural", "one", rules.select(b));
+
+ // Check the range plural now:
+ String form = rules.select(range);
+ assertEquals("Range plural", "few", form);
+
+ // Test when plural ranges data is unavailable:
+ PluralRules bare = PluralRules.createRules("a: i = 0,1");
+ try {
+ form = bare.select(range);
+ fail("Expected exception");
+ } catch (UnsupportedOperationException e) {}
+
+ // However, they should not throw when no data is available for a language.
+ PluralRules xyz = PluralRules.forLocale(new ULocale("xyz"));
+ form = xyz.select(range);
+ assertEquals("Fallback form", "other", form);
+ }
}