ICU-21190 Adding PluralRules select for number ranges

See #1309
This commit is contained in:
Shane F. Carr 2020-09-22 00:02:27 +00:00
parent 12dc3772b1
commit 6b3a7a64ab
28 changed files with 762 additions and 859 deletions

View file

@ -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" />

View file

@ -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>

View file

@ -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" />

View file

@ -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 @@ void FormattedNumber::getDecimalQuantity(impl::DecimalQuantity& output, UErrorCo
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

View file

@ -59,8 +59,8 @@ UFormattedNumberRangeImpl::~UFormattedNumberRangeImpl() {
fImpl.fData = nullptr;
}
}
}
} // namespace impl
} // namespace number
U_NAMESPACE_END
@ -71,6 +71,16 @@ UPRV_FORMATTED_VALUE_CAPI_NO_IMPLTYPE_AUTO_IMPL(
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,

View file

@ -376,36 +376,4 @@ LocalizedNumberRangeFormatter::getFormatter(UErrorCode& status) const {
}
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 */

View file

@ -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 @@ void getNumberRangeData(const char* localeName, const char* nsName, NumberRangeD
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 @@ NumberRangeFormatterImpl::NumberRangeFormatterImpl(const RangeMacroProps& macros
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; }
}

View file

@ -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 @@ public:
};
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 @@ class NumberRangeFormatterImpl : public UMemory {
};
/** Helper function used in upluralrules.cpp */
const UFormattedNumberRangeData* validateUFormattedNumberRange(
const UFormattedNumberRange* uresult, UErrorCode& status);
} // namespace impl
} // namespace number
U_NAMESPACE_END

View file

@ -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 */

View file

@ -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__

View file

@ -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 @@ UOBJECT_DEFINE_RTTI_IMPLEMENTATION(PluralKeywordEnumeration)
PluralRules::PluralRules(UErrorCode& /*status*/)
: UObject(),
mRules(nullptr),
mStandardPluralRanges(nullptr),
mInternalStatus(U_ZERO_ERROR)
{
}
@ -75,6 +79,7 @@ PluralRules::PluralRules(UErrorCode& /*status*/)
PluralRules::PluralRules(const PluralRules& other)
: UObject(other),
mRules(nullptr),
mStandardPluralRanges(nullptr),
mInternalStatus(U_ZERO_ERROR)
{
*this=other;
@ -82,6 +87,7 @@ PluralRules::PluralRules(const PluralRules& other)
PluralRules::~PluralRules() {
delete mRules;
delete mStandardPluralRanges;
}
SharedPluralRules::~SharedPluralRules() {
@ -90,14 +96,20 @@ SharedPluralRules::~SharedPluralRules() {
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 @@ PluralRules::operator=(const PluralRules& other) {
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 @@ PluralRules::operator=(const PluralRules& other) {
mInternalStatus = mRules->fInternalStatus;
}
}
if (other.mStandardPluralRanges != nullptr) {
mStandardPluralRanges = other.mStandardPluralRanges->copy(mInternalStatus)
.toPointer(mInternalStatus)
.orphan();
}
}
return *this;
}
@ -212,11 +231,8 @@ PluralRules::forLocale(const Locale& locale, UPluralType type, UErrorCode& statu
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 @@ PluralRules::internalForLocale(const Locale& locale, UPluralType type, UErrorCod
// 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 @@ PluralRules::select(const number::FormattedNumber& number, UErrorCode& status) c
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 @@ PluralRules::select(const IFixedDecimal &number) const {
}
}
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*

View file

@ -140,6 +140,7 @@ numrange_impl.cpp
numsys.cpp
olsontz.cpp
persncal.cpp
pluralranges.cpp
plurfmt.cpp
plurrule.cpp
quant.cpp

View file

@ -47,6 +47,9 @@
U_NAMESPACE_BEGIN
// Forward declarations:
class PluralRules;
namespace number { // icu::number
// Forward declarations:
@ -733,6 +736,11 @@ class U_I18N_API FormattedNumberRange : public UMemory, public FormattedValue {
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;

View file

@ -51,9 +51,14 @@ class PluralRuleParser;
class PluralKeywordEnumeration;
class AndConstraint;
class SharedPluralRules;
class StandardPluralRanges;
namespace number {
class FormattedNumber;
class FormattedNumberRange;
namespace impl {
class UFormattedNumberRangeData;
}
}
/**
@ -367,11 +372,35 @@ public:
*/
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 @@ public:
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.

View file

@ -26,6 +26,7 @@
// Forward-declaration
struct UFormattedNumber;
struct UFormattedNumberRange;
/**
* \file
@ -167,7 +168,7 @@ uplrules_select(const UPluralRules *uplrules,
* @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 @@ uplrules_selectFormatted(const UPluralRules *uplrules,
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

View file

@ -20,6 +20,7 @@
#include "unicode/unumberformatter.h"
#include "number_decimalquantity.h"
#include "number_utypes.h"
#include "numrange_impl.h"
U_NAMESPACE_USE
@ -115,6 +116,25 @@ uplrules_selectFormatted(const UPluralRules *uplrules,
return result.extract(keyword, capacity, *status);
}
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,

View file

@ -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 TestPluralRules(void);
static void TestOrdinalRules(void);
static void TestGetKeywords(void);
static void TestFormatted(void);
static void TestSelectRange(void);
void addPluralRulesTest(TestNode** root);
@ -33,6 +35,7 @@ void addPluralRulesTest(TestNode** root)
TESTCASE(TestOrdinalRules);
TESTCASE(TestGetKeywords);
TESTCASE(TestFormatted);
TESTCASE(TestSelectRange);
}
typedef struct {
@ -295,4 +298,56 @@ cleanup:
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 */

View file

@ -974,7 +974,7 @@ group: number_representation
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

View file

@ -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 @@ void PluralRulesTest::runIndexedTest( int32_t index, UBool exec, const char* &na
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::testSelect() {
}
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.

View file

@ -35,6 +35,7 @@ private:
void testCompactDecimalPluralKeyword();
void testOrdinal();
void testSelect();
void testSelectRange();
void testAvailbleLocales();
void testParseErrors();
void testFixedDecimal();

View file

@ -18,7 +18,7 @@ import java.util.MissingResourceException;
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 @@ import com.ibm.icu.util.UResourceBundle;
* 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 @@ public class PluralRulesLoader extends PluralRules.Factory {
* 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 @@ public class PluralRulesLoader extends PluralRules.Factory {
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 @@ public class PluralRulesLoader extends PluralRules.Factory {
* 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 class PluralRulesLoader extends PluralRules.Factory {
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);
}
}

View file

@ -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.StandardPlural;
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 @@ public class StandardPluralRanges {
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 @@ public class StandardPluralRanges {
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 @@ public class StandardPluralRanges {
}
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. */

View file

@ -13,6 +13,7 @@ import com.ibm.icu.impl.number.DecimalQuantity;
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 @@ public class FormattedNumberRange implements FormattedValue {
&& 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;
}
}

View file

@ -148,7 +148,7 @@ class NumberRangeFormatterImpl {
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) {

View file

@ -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 15, 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();
}
}

View file

@ -29,7 +29,10 @@ import java.util.TreeSet;
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 @@ public class PluralRules implements Serializable {
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 class PluralRules implements Serializable {
*/
public static PluralRules parseDescription(String description)
throws ParseException {
description = description.trim();
return description.length() == 0 ? DEFAULT : new PluralRules(parseRuleChain(description));
return newInternal(description, null);
}
/**
@ -397,12 +399,25 @@ public class PluralRules implements Serializable {
}
}
/**
* @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 @@ public class PluralRules implements Serializable {
/*
* 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;
}
/**
@ -2058,6 +2074,34 @@ public class PluralRules implements Serializable {
return rules.select(number.getFixedDecimal());
}
/**
* 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.

View file

@ -10,7 +10,6 @@ package com.ibm.icu.dev.test.format;
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 org.junit.runners.JUnit4;
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 class PluralRangesTest extends TestFmwk {
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 @@ public class PluralRangesTest extends TestFmwk {
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 @@ public class PluralRangesTest extends TestFmwk {
}
}
// 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, "01 degré Fahrenheit"},
{1.0, 2.0, ULocale.FRANCE, FormatWidth.WIDE, MeasureUnit.FAHRENHEIT, "12 degrés Fahrenheit"},
{3.1, 4.25, ULocale.FRANCE, FormatWidth.SHORT, MeasureUnit.FAHRENHEIT, "3,14,25 °F"},
{3.1, 4.25, ULocale.ENGLISH, FormatWidth.SHORT, MeasureUnit.FAHRENHEIT, "3.14.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, "01 inches"},
{0.0, 1.0, ULocale.FRANCE, UnitWidth.FULL_NAME, MeasureUnit.FAHRENHEIT, "01\u00A0degré Fahrenheit"},
{1.0, 2.0, ULocale.FRANCE, UnitWidth.FULL_NAME, MeasureUnit.FAHRENHEIT, "12\u00A0degrés Fahrenheit"},
{3.1, 4.25, ULocale.FRANCE, UnitWidth.SHORT, MeasureUnit.FAHRENHEIT, "3,14,25\u202F°F"},
{3.1, 4.25, ULocale.ENGLISH, UnitWidth.SHORT, MeasureUnit.FAHRENHEIT, "3.14.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, "01 inches"},
{0.0, 1.0, ULocale.ENGLISH, FormatWidth.NARROW, Currency.getInstance("EUR"), "€0.001.00"},
{0.0, 1.0, ULocale.FRENCH, FormatWidth.NARROW, Currency.getInstance("EUR"), "0,001,00 "},
{0.0, 100.0, ULocale.FRENCH, FormatWidth.NARROW, Currency.getInstance("JPY"), "0100\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,001,00 "},
{0.0, 100.0, ULocale.FRENCH, UnitWidth.NARROW, Currency.getInstance("JPY"), "0100\u00a0¥"},
{0.0, 1.0, ULocale.ENGLISH, FormatWidth.SHORT, Currency.getInstance("EUR"), "EUR0.001.00"},
{0.0, 1.0, ULocale.FRENCH, FormatWidth.SHORT, Currency.getInstance("EUR"), "0,001,00\u00a0EUR"},
{0.0, 100.0, ULocale.FRENCH, FormatWidth.SHORT, Currency.getInstance("JPY"), "0100\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,001,00\u00a0"},
{0.0, 100.0, ULocale.FRENCH, UnitWidth.SHORT, Currency.getInstance("JPY"), "0100\u00a0JPY"},
{0.0, 1.0, ULocale.ENGLISH, FormatWidth.WIDE, Currency.getInstance("EUR"), "0.001.00 euros"},
{0.0, 1.0, ULocale.FRENCH, FormatWidth.WIDE, Currency.getInstance("EUR"), "0,001,00 euro"},
{0.0, 2.0, ULocale.FRENCH, FormatWidth.WIDE, Currency.getInstance("EUR"), "0,002,00 euros"},
{0.0, 100.0, ULocale.FRENCH, FormatWidth.WIDE, Currency.getInstance("JPY"), "0100 yens japonais"},
{0.0, 1.0, ULocale.ENGLISH, UnitWidth.FULL_NAME, Currency.getInstance("EUR"), "0.001.00 euros"},
{0.0, 1.0, ULocale.FRENCH, UnitWidth.FULL_NAME, Currency.getInstance("EUR"), "0,001,00 euro"},
{0.0, 2.0, ULocale.FRENCH, UnitWidth.FULL_NAME, Currency.getInstance("EUR"), "0,002,00 euros"},
{0.0, 100.0, ULocale.FRENCH, UnitWidth.FULL_NAME, Currency.getInstance("JPY"), "0100 yens japonais"},
};
int i = 0;
for (Object[] test : tests) {
@ -104,34 +103,15 @@ public class PluralRangesTest extends TestFmwk {
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());
}
}
}

View file

@ -42,8 +42,10 @@ import com.ibm.icu.dev.util.CollectionUtilities;
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 @@ public class PluralRulesTest extends TestFmwk {
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);
}
}