mirror of
https://github.com/unicode-org/icu.git
synced 2025-04-06 05:55:35 +00:00
ICU-13742 Implementing number skeletons in MessageFormat.
X-SVN-Rev: 41377
This commit is contained in:
parent
c3fa4e91b5
commit
b347a140ec
16 changed files with 529 additions and 29 deletions
|
@ -31,6 +31,7 @@
|
|||
#include "unicode/decimfmt.h"
|
||||
#include "unicode/localpointer.h"
|
||||
#include "unicode/msgfmt.h"
|
||||
#include "unicode/numberformatter.h"
|
||||
#include "unicode/plurfmt.h"
|
||||
#include "unicode/rbnf.h"
|
||||
#include "unicode/selfmt.h"
|
||||
|
@ -1700,12 +1701,21 @@ Format* MessageFormat::createAppropriateFormat(UnicodeString& type, UnicodeStrin
|
|||
formattableType = Formattable::kLong;
|
||||
fmt = createIntegerFormat(fLocale, ec);
|
||||
break;
|
||||
default: // pattern
|
||||
fmt = NumberFormat::createInstance(fLocale, ec);
|
||||
if (fmt) {
|
||||
DecimalFormat* decfmt = dynamic_cast<DecimalFormat*>(fmt);
|
||||
if (decfmt != NULL) {
|
||||
decfmt->applyPattern(style,parseError,ec);
|
||||
default: // pattern or skeleton
|
||||
int32_t i = 0;
|
||||
for (; PatternProps::isWhiteSpace(style.charAt(i)); i++);
|
||||
if (style.compare(i, 2, u"::", 0, 2) == 0) {
|
||||
// Skeleton
|
||||
UnicodeString skeleton = style.tempSubString(i + 2);
|
||||
fmt = number::NumberFormatter::fromSkeleton(skeleton, ec).locale(fLocale).toFormat(ec);
|
||||
} else {
|
||||
// Pattern
|
||||
fmt = NumberFormat::createInstance(fLocale, ec);
|
||||
if (fmt) {
|
||||
auto* decfmt = dynamic_cast<DecimalFormat*>(fmt);
|
||||
if (decfmt != nullptr) {
|
||||
decfmt->applyPattern(style, parseError, ec);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include "number_formatimpl.h"
|
||||
#include "umutex.h"
|
||||
#include "number_skeletons.h"
|
||||
#include "number_utils.h"
|
||||
#include "number_utypes.h"
|
||||
#include "util.h"
|
||||
|
||||
|
@ -575,23 +576,6 @@ const NumberingSystem* SymbolsWrapper::getNumberingSystem() const {
|
|||
}
|
||||
|
||||
|
||||
FormattedNumber::FormattedNumber(FormattedNumber&& src) U_NOEXCEPT
|
||||
: fResults(src.fResults), fErrorCode(src.fErrorCode) {
|
||||
// Disown src.fResults to prevent double-deletion
|
||||
src.fResults = nullptr;
|
||||
src.fErrorCode = U_INVALID_STATE_ERROR;
|
||||
}
|
||||
|
||||
FormattedNumber& FormattedNumber::operator=(FormattedNumber&& src) U_NOEXCEPT {
|
||||
delete fResults;
|
||||
fResults = src.fResults;
|
||||
fErrorCode = src.fErrorCode;
|
||||
// Disown src.fResults to prevent double-deletion
|
||||
src.fResults = nullptr;
|
||||
src.fErrorCode = U_INVALID_STATE_ERROR;
|
||||
return *this;
|
||||
}
|
||||
|
||||
FormattedNumber LocalizedNumberFormatter::formatInt(int64_t value, UErrorCode& status) const {
|
||||
if (U_FAILURE(status)) { return FormattedNumber(U_ILLEGAL_ARGUMENT_ERROR); }
|
||||
auto results = new UFormattedNumberData();
|
||||
|
@ -745,6 +729,30 @@ int32_t LocalizedNumberFormatter::getCallCount() const {
|
|||
return umtx_loadAcquire(*callCount);
|
||||
}
|
||||
|
||||
Format* LocalizedNumberFormatter::toFormat(UErrorCode& status) const {
|
||||
LocalPointer<LocalizedNumberFormatterAsFormat> retval(
|
||||
new LocalizedNumberFormatterAsFormat(*this, fMacros.locale), status);
|
||||
return retval.orphan();
|
||||
}
|
||||
|
||||
|
||||
FormattedNumber::FormattedNumber(FormattedNumber&& src) U_NOEXCEPT
|
||||
: fResults(src.fResults), fErrorCode(src.fErrorCode) {
|
||||
// Disown src.fResults to prevent double-deletion
|
||||
src.fResults = nullptr;
|
||||
src.fErrorCode = U_INVALID_STATE_ERROR;
|
||||
}
|
||||
|
||||
FormattedNumber& FormattedNumber::operator=(FormattedNumber&& src) U_NOEXCEPT {
|
||||
delete fResults;
|
||||
fResults = src.fResults;
|
||||
fErrorCode = src.fErrorCode;
|
||||
// Disown src.fResults to prevent double-deletion
|
||||
src.fResults = nullptr;
|
||||
src.fErrorCode = U_INVALID_STATE_ERROR;
|
||||
return *this;
|
||||
}
|
||||
|
||||
UnicodeString FormattedNumber::toString() const {
|
||||
UErrorCode localStatus = U_ZERO_ERROR;
|
||||
return toString(localStatus);
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include "double-conversion.h"
|
||||
#include "uresimp.h"
|
||||
#include "ureslocs.h"
|
||||
#include "number_utypes.h"
|
||||
|
||||
using namespace icu;
|
||||
using namespace icu::number;
|
||||
|
@ -47,6 +48,85 @@ doGetPattern(UResourceBundle* res, const char* nsName, const char* patternKey, U
|
|||
}
|
||||
|
||||
|
||||
LocalizedNumberFormatterAsFormat::LocalizedNumberFormatterAsFormat(
|
||||
const LocalizedNumberFormatter& formatter, const Locale& locale)
|
||||
: fFormatter(formatter), fLocale(locale) {
|
||||
const char* localeName = locale.getName();
|
||||
setLocaleIDs(localeName, localeName);
|
||||
}
|
||||
|
||||
LocalizedNumberFormatterAsFormat::~LocalizedNumberFormatterAsFormat() = default;
|
||||
|
||||
UBool LocalizedNumberFormatterAsFormat::operator==(const Format& other) const {
|
||||
auto* _other = dynamic_cast<const LocalizedNumberFormatterAsFormat*>(&other);
|
||||
if (_other == nullptr) {
|
||||
return false;
|
||||
}
|
||||
// TODO: Change this to use LocalizedNumberFormatter::operator== if it is ever proposed.
|
||||
// This implementation is fine, but not particularly efficient.
|
||||
UErrorCode localStatus = U_ZERO_ERROR;
|
||||
return fFormatter.toSkeleton(localStatus) == _other->fFormatter.toSkeleton(localStatus);
|
||||
}
|
||||
|
||||
Format* LocalizedNumberFormatterAsFormat::clone() const {
|
||||
return new LocalizedNumberFormatterAsFormat(*this);
|
||||
}
|
||||
|
||||
UnicodeString& LocalizedNumberFormatterAsFormat::format(const Formattable& obj, UnicodeString& appendTo,
|
||||
FieldPosition& pos, UErrorCode& status) const {
|
||||
if (U_FAILURE(status)) { return appendTo; }
|
||||
UFormattedNumberData data;
|
||||
obj.populateDecimalQuantity(data.quantity, status);
|
||||
if (U_FAILURE(status)) {
|
||||
return appendTo;
|
||||
}
|
||||
fFormatter.formatImpl(&data, status);
|
||||
if (U_FAILURE(status)) {
|
||||
return appendTo;
|
||||
}
|
||||
// always return first occurrence:
|
||||
pos.setBeginIndex(0);
|
||||
pos.setEndIndex(0);
|
||||
bool found = data.string.nextFieldPosition(pos, status);
|
||||
if (found && appendTo.length() != 0) {
|
||||
pos.setBeginIndex(pos.getBeginIndex() + appendTo.length());
|
||||
pos.setEndIndex(pos.getEndIndex() + appendTo.length());
|
||||
}
|
||||
appendTo.append(data.string.toTempUnicodeString());
|
||||
return appendTo;
|
||||
}
|
||||
|
||||
UnicodeString& LocalizedNumberFormatterAsFormat::format(const Formattable& obj, UnicodeString& appendTo,
|
||||
FieldPositionIterator* posIter,
|
||||
UErrorCode& status) const {
|
||||
if (U_FAILURE(status)) { return appendTo; }
|
||||
UFormattedNumberData data;
|
||||
obj.populateDecimalQuantity(data.quantity, status);
|
||||
if (U_FAILURE(status)) {
|
||||
return appendTo;
|
||||
}
|
||||
fFormatter.formatImpl(&data, status);
|
||||
if (U_FAILURE(status)) {
|
||||
return appendTo;
|
||||
}
|
||||
appendTo.append(data.string.toTempUnicodeString());
|
||||
if (posIter != nullptr) {
|
||||
data.string.getAllFieldPositions(*posIter, status);
|
||||
}
|
||||
return appendTo;
|
||||
}
|
||||
|
||||
void LocalizedNumberFormatterAsFormat::parseObject(const UnicodeString&, Formattable&,
|
||||
ParsePosition& parse_pos) const {
|
||||
// Not supported.
|
||||
parse_pos.setErrorIndex(0);
|
||||
}
|
||||
|
||||
const LocalizedNumberFormatter& LocalizedNumberFormatterAsFormat::getNumberFormatter() const {
|
||||
return fFormatter;
|
||||
}
|
||||
|
||||
|
||||
const char16_t* utils::getPatternForStyle(const Locale& locale, const char* nsName, CldrPatternStyle style,
|
||||
UErrorCode& status) {
|
||||
const char* patternKey;
|
||||
|
|
|
@ -72,6 +72,82 @@ struct MicroProps : public MicroPropsGenerator {
|
|||
bool exhausted = false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A wrapper around LocalizedNumberFormatter implementing the Format interface, enabling improved
|
||||
* compatibility with other APIs.
|
||||
*
|
||||
* @draft ICU 62
|
||||
* @see NumberFormatter
|
||||
*/
|
||||
class U_I18N_API LocalizedNumberFormatterAsFormat : public Format {
|
||||
public:
|
||||
LocalizedNumberFormatterAsFormat(const LocalizedNumberFormatter& formatter, const Locale& locale);
|
||||
|
||||
/**
|
||||
* Destructor.
|
||||
*/
|
||||
~LocalizedNumberFormatterAsFormat() U_OVERRIDE;
|
||||
|
||||
/**
|
||||
* Equals operator.
|
||||
*/
|
||||
UBool operator==(const Format& other) const U_OVERRIDE;
|
||||
|
||||
/**
|
||||
* Creates a copy of this object.
|
||||
*/
|
||||
Format* clone() const U_OVERRIDE;
|
||||
|
||||
/**
|
||||
* Formats a Number using the wrapped LocalizedNumberFormatter. The provided formattable must be a
|
||||
* number type.
|
||||
*/
|
||||
UnicodeString& format(const Formattable& obj, UnicodeString& appendTo, FieldPosition& pos,
|
||||
UErrorCode& status) const U_OVERRIDE;
|
||||
|
||||
/**
|
||||
* Formats a Number using the wrapped LocalizedNumberFormatter. The provided formattable must be a
|
||||
* number type.
|
||||
*/
|
||||
UnicodeString& format(const Formattable& obj, UnicodeString& appendTo, FieldPositionIterator* posIter,
|
||||
UErrorCode& status) const U_OVERRIDE;
|
||||
|
||||
/**
|
||||
* Not supported: sets an error index and returns.
|
||||
*/
|
||||
void parseObject(const UnicodeString& source, Formattable& result,
|
||||
ParsePosition& parse_pos) const U_OVERRIDE;
|
||||
|
||||
/**
|
||||
* Gets the LocalizedNumberFormatter that this wrapper class uses to format numbers.
|
||||
*
|
||||
* For maximum efficiency, this function returns by const reference. You must copy the return value
|
||||
* into a local variable if you want to use it beyond the lifetime of the current object:
|
||||
*
|
||||
* <pre>
|
||||
* LocalizedNumberFormatter localFormatter = fmt->getNumberFormatter();
|
||||
* </pre>
|
||||
*
|
||||
* You can however use the return value directly when chaining:
|
||||
*
|
||||
* <pre>
|
||||
* FormattedNumber result = fmt->getNumberFormatter().formatDouble(514.23, status);
|
||||
* </pre>
|
||||
*
|
||||
* @return The unwrapped LocalizedNumberFormatter.
|
||||
*/
|
||||
const LocalizedNumberFormatter& getNumberFormatter() const;
|
||||
|
||||
private:
|
||||
LocalizedNumberFormatter fFormatter;
|
||||
|
||||
// Even though the locale is inside the LocalizedNumberFormatter, we have to keep it here, too, because
|
||||
// LocalizedNumberFormatter doesn't have a getLocale() method, and ICU-TC didn't want to add one.
|
||||
Locale fLocale;
|
||||
};
|
||||
|
||||
|
||||
enum CldrPatternStyle {
|
||||
CLDR_PATTERN_STYLE_DECIMAL,
|
||||
CLDR_PATTERN_STYLE_CURRENCY,
|
||||
|
|
|
@ -2265,6 +2265,21 @@ class U_I18N_API LocalizedNumberFormatter
|
|||
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Creates a representation of this LocalizedNumberFormat as an icu::Format, enabling the use
|
||||
* of this number formatter with APIs that need an object of that type, such as MessageFormat.
|
||||
*
|
||||
* This API is not intended to be used other than for enabling API compatibility. The formatDouble,
|
||||
* formatInt, and formatDecimal methods should normally be used when formatting numbers, not the Format
|
||||
* object returned by this method.
|
||||
*
|
||||
* The caller owns the returned object and must delete it when finished.
|
||||
*
|
||||
* @return A Format wrapping this LocalizedNumberFormatter.
|
||||
* @draft ICU 62
|
||||
*/
|
||||
Format* toFormat(UErrorCode& status) const;
|
||||
|
||||
/**
|
||||
* Default constructor: puts the formatter into a valid but undefined state.
|
||||
*
|
||||
|
|
|
@ -68,6 +68,7 @@ class NumberFormatterApiTest : public IntlTest {
|
|||
void locale();
|
||||
void formatTypes();
|
||||
void fieldPosition();
|
||||
void toFormat();
|
||||
void errors();
|
||||
void validRanges();
|
||||
void copyMove();
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include "unicode/unum.h"
|
||||
#include "unicode/numberformatter.h"
|
||||
#include "number_types.h"
|
||||
#include "number_utils.h"
|
||||
#include "numbertest.h"
|
||||
#include "unicode/utypes.h"
|
||||
|
||||
|
@ -82,6 +83,7 @@ void NumberFormatterApiTest::runIndexedTest(int32_t index, UBool exec, const cha
|
|||
TESTCASE_AUTO(locale);
|
||||
TESTCASE_AUTO(formatTypes);
|
||||
TESTCASE_AUTO(fieldPosition);
|
||||
TESTCASE_AUTO(toFormat);
|
||||
TESTCASE_AUTO(errors);
|
||||
TESTCASE_AUTO(validRanges);
|
||||
TESTCASE_AUTO(copyMove);
|
||||
|
@ -2155,6 +2157,30 @@ void NumberFormatterApiTest::fieldPosition() {
|
|||
assertFalse(u"No fraction part in an integer", fmtd.nextFieldPosition(actual, status));
|
||||
}
|
||||
|
||||
void NumberFormatterApiTest::toFormat() {
|
||||
IcuTestErrorCode status(*this, "icuFormat");
|
||||
LocalizedNumberFormatter lnf = NumberFormatter::withLocale("fr")
|
||||
.precision(Precision::fixedFraction(3));
|
||||
LocalPointer<Format> format(lnf.toFormat(status), status);
|
||||
FieldPosition fpos(UNUM_DECIMAL_SEPARATOR_FIELD);
|
||||
UnicodeString sb;
|
||||
format->format(514.23, sb, fpos, status);
|
||||
assertEquals("Should correctly format number", u"514,230", sb);
|
||||
assertEquals("Should find decimal separator", 3, fpos.getBeginIndex());
|
||||
assertEquals("Should find end of decimal separator", 4, fpos.getEndIndex());
|
||||
assertEquals(
|
||||
"ICU Format should round-trip",
|
||||
lnf.toSkeleton(status),
|
||||
dynamic_cast<LocalizedNumberFormatterAsFormat*>(format.getAlias())->getNumberFormatter()
|
||||
.toSkeleton(status));
|
||||
|
||||
FieldPositionIterator fpi1;
|
||||
lnf.formatDouble(514.23, status).getAllFieldPositions(fpi1, status);
|
||||
FieldPositionIterator fpi2;
|
||||
format->format(514.23, sb.remove(), &fpi2, status);
|
||||
assertTrue("Should produce same field position iterator", fpi1 == fpi2);
|
||||
}
|
||||
|
||||
void NumberFormatterApiTest::errors() {
|
||||
LocalizedNumberFormatter lnf = NumberFormatter::withLocale(Locale::getEnglish()).precision(
|
||||
Precision::fixedFraction(
|
||||
|
|
|
@ -71,6 +71,7 @@ TestMessageFormat::runIndexedTest(int32_t index, UBool exec,
|
|||
TESTCASE_AUTO(TestSelectOrdinal);
|
||||
TESTCASE_AUTO(TestDecimals);
|
||||
TESTCASE_AUTO(TestArgIsPrefixOfAnother);
|
||||
TESTCASE_AUTO(TestMessageFormatNumberSkeleton);
|
||||
TESTCASE_AUTO_END;
|
||||
}
|
||||
|
||||
|
@ -1992,4 +1993,38 @@ void TestMessageFormat::TestArgIsPrefixOfAnother() {
|
|||
assertEquals("aa aaa", "AB ABC", mf3.format(argNames + 1, args + 1, 2, result.remove(), errorCode));
|
||||
}
|
||||
|
||||
void TestMessageFormat::TestMessageFormatNumberSkeleton() {
|
||||
IcuTestErrorCode status(*this, "TestMessageFormatNumberSkeleton");
|
||||
|
||||
static const struct TestCase {
|
||||
const char16_t* messagePattern;
|
||||
const char* localeName;
|
||||
double arg;
|
||||
const char16_t* expected;
|
||||
} cases[] = {
|
||||
{ u"{0,number,::percent}", "en", 50, u"50%" },
|
||||
{ u"{0,number,::percent scale/100}", "en", 0.5, u"50%" },
|
||||
{ u"{0,number, :: percent scale/100 }", "en", 0.5, u"50%" },
|
||||
{ u"{0,number,::currency/USD}", "en", 23, u"$23.00" },
|
||||
{ u"{0,number,::precision-integer}", "en", 514.23, u"514" },
|
||||
{ u"{0,number,::.000}", "en", 514.23, u"514.230" },
|
||||
{ u"{0,number,::.}", "en", 514.23, u"514" },
|
||||
{ u"{0,number,::}", "fr", 514.23, u"514,23" },
|
||||
{ u"Cost: {0,number,::currency/EUR}.", "en", 4.3, u"Cost: €4.30." },
|
||||
{ u"{0,number,'::'0.00}", "en", 50, u"::50.00" }, // pattern literal
|
||||
};
|
||||
|
||||
for (auto& cas : cases) {
|
||||
status.setScope(cas.messagePattern);
|
||||
MessageFormat msgf(cas.messagePattern, cas.localeName, status);
|
||||
UnicodeString sb;
|
||||
FieldPosition fpos(0);
|
||||
Formattable argsArray[] = {{cas.arg}};
|
||||
Formattable args(argsArray, 1);
|
||||
msgf.format(args, sb, status);
|
||||
|
||||
assertEquals(cas.messagePattern, cas.expected, sb);
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* #if !UCONFIG_NO_FORMATTING */
|
||||
|
|
|
@ -121,6 +121,7 @@ public:
|
|||
void TestSelectOrdinal();
|
||||
void TestDecimals();
|
||||
void TestArgIsPrefixOfAnother();
|
||||
void TestMessageFormatNumberSkeleton();
|
||||
|
||||
private:
|
||||
UnicodeString GetPatternAndSkipSyntax(const MessagePattern& pattern);
|
||||
|
|
|
@ -0,0 +1,144 @@
|
|||
// © 2018 and later: Unicode, Inc. and others.
|
||||
// License & terms of use: http://www.unicode.org/copyright.html#License
|
||||
package com.ibm.icu.impl.number;
|
||||
|
||||
import java.io.Externalizable;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInput;
|
||||
import java.io.ObjectOutput;
|
||||
import java.io.ObjectStreamException;
|
||||
import java.text.AttributedCharacterIterator;
|
||||
import java.text.FieldPosition;
|
||||
import java.text.Format;
|
||||
import java.text.ParsePosition;
|
||||
|
||||
import com.ibm.icu.number.FormattedNumber;
|
||||
import com.ibm.icu.number.LocalizedNumberFormatter;
|
||||
import com.ibm.icu.number.NumberFormatter;
|
||||
import com.ibm.icu.util.ULocale;
|
||||
|
||||
/**
|
||||
* A wrapper around LocalizedNumberFormatter implementing the Format interface, enabling improved
|
||||
* compatibility with other APIs. This class is serializable.
|
||||
*/
|
||||
public class LocalizedNumberFormatterAsFormat extends Format {
|
||||
|
||||
private final transient LocalizedNumberFormatter formatter;
|
||||
|
||||
// Even though the locale is inside the LocalizedNumberFormatter, we have to keep it here, too, because
|
||||
// LocalizedNumberFormatter doesn't have a getLocale() method, and ICU-TC didn't want to add one.
|
||||
private final transient ULocale locale;
|
||||
|
||||
public LocalizedNumberFormatterAsFormat(LocalizedNumberFormatter formatter, ULocale locale) {
|
||||
this.formatter = formatter;
|
||||
this.locale = locale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a Number using the wrapped LocalizedNumberFormatter. The provided object must be a Number.
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) {
|
||||
if (!(obj instanceof Number)) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
FormattedNumber result = formatter.format((Number) obj);
|
||||
// always return first occurrence:
|
||||
pos.setBeginIndex(0);
|
||||
pos.setEndIndex(0);
|
||||
boolean found = result.nextFieldPosition(pos);
|
||||
if (found && toAppendTo.length() != 0) {
|
||||
pos.setBeginIndex(pos.getBeginIndex() + toAppendTo.length());
|
||||
pos.setEndIndex(pos.getEndIndex() + toAppendTo.length());
|
||||
}
|
||||
result.appendTo(toAppendTo);
|
||||
return toAppendTo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a Number using the wrapped LocalizedNumberFormatter. The provided object must be a Number.
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public AttributedCharacterIterator formatToCharacterIterator(Object obj) {
|
||||
if (!(obj instanceof Number)) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
return formatter.format((Number) obj).toCharacterIterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Not supported. This method will throw UnsupportedOperationException.
|
||||
*/
|
||||
@Override
|
||||
public Object parseObject(String source, ParsePosition pos) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the LocalizedNumberFormatter that this wrapper class uses to format numbers.
|
||||
*
|
||||
* @return The unwrapped LocalizedNumberFormatter.
|
||||
*/
|
||||
public LocalizedNumberFormatter getNumberFormatter() {
|
||||
return formatter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return formatter.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (this == other) {
|
||||
return true;
|
||||
}
|
||||
if (other == null) {
|
||||
return false;
|
||||
}
|
||||
if (!(other instanceof LocalizedNumberFormatterAsFormat)) {
|
||||
return false;
|
||||
}
|
||||
return formatter.equals(((LocalizedNumberFormatterAsFormat) other).getNumberFormatter());
|
||||
}
|
||||
|
||||
private Object writeReplace() throws ObjectStreamException {
|
||||
Proxy proxy = new Proxy();
|
||||
proxy.languageTag = locale.toLanguageTag();
|
||||
proxy.skeleton = formatter.toSkeleton();
|
||||
return proxy;
|
||||
}
|
||||
|
||||
static class Proxy implements Externalizable {
|
||||
String languageTag;
|
||||
String skeleton;
|
||||
|
||||
// Must have public constructor, to enable Externalizable
|
||||
public Proxy() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeExternal(ObjectOutput out) throws IOException {
|
||||
out.writeByte(0); // version
|
||||
out.writeUTF(languageTag);
|
||||
out.writeUTF(skeleton);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
|
||||
in.readByte(); // version
|
||||
languageTag = in.readUTF();
|
||||
skeleton = in.readUTF();
|
||||
}
|
||||
|
||||
private Object readResolve() throws ObjectStreamException {
|
||||
return NumberFormatter.fromSkeleton(skeleton)
|
||||
.locale(ULocale.forLanguageTag(languageTag))
|
||||
.toFormat();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,12 +3,14 @@
|
|||
package com.ibm.icu.number;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.text.Format;
|
||||
import java.util.concurrent.atomic.AtomicLongFieldUpdater;
|
||||
|
||||
import com.ibm.icu.impl.StandardPlural;
|
||||
import com.ibm.icu.impl.Utility;
|
||||
import com.ibm.icu.impl.number.DecimalQuantity;
|
||||
import com.ibm.icu.impl.number.DecimalQuantity_DualStorageBCD;
|
||||
import com.ibm.icu.impl.number.LocalizedNumberFormatterAsFormat;
|
||||
import com.ibm.icu.impl.number.MacroProps;
|
||||
import com.ibm.icu.impl.number.NumberStringBuilder;
|
||||
import com.ibm.icu.math.BigDecimal;
|
||||
|
@ -114,6 +116,23 @@ public class LocalizedNumberFormatter extends NumberFormatterSettings<LocalizedN
|
|||
return withUnit.format(number);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a representation of this LocalizedNumberFormat as a {@link java.text.Format}, enabling the
|
||||
* use of this number formatter with APIs that need an object of that type, such as MessageFormat.
|
||||
* <p>
|
||||
* This API is not intended to be used other than for enabling API compatibility. The {@link #format}
|
||||
* methods should normally be used when formatting numbers, not the Format object returned by this
|
||||
* method.
|
||||
*
|
||||
* @return A Format wrapping this LocalizedNumberFormatter.
|
||||
* @draft ICU 62
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
* @see NumberFormatter
|
||||
*/
|
||||
public Format toFormat() {
|
||||
return new LocalizedNumberFormatterAsFormat(this, resolve().loc);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the core entrypoint to the number formatting pipeline. It performs self-regulation: a
|
||||
* static code path for the first few calls, and compiling a more efficient data structure if called
|
||||
|
|
|
@ -36,6 +36,7 @@ import java.util.Set;
|
|||
|
||||
import com.ibm.icu.impl.PatternProps;
|
||||
import com.ibm.icu.impl.Utility;
|
||||
import com.ibm.icu.number.NumberFormatter;
|
||||
import com.ibm.icu.text.MessagePattern.ArgType;
|
||||
import com.ibm.icu.text.MessagePattern.Part;
|
||||
import com.ibm.icu.text.PluralRules.IFixedDecimal;
|
||||
|
@ -2204,9 +2205,17 @@ public class MessageFormat extends UFormat {
|
|||
case MODIFIER_INTEGER:
|
||||
newFormat = NumberFormat.getIntegerInstance(ulocale);
|
||||
break;
|
||||
default: // pattern
|
||||
newFormat = new DecimalFormat(style,
|
||||
new DecimalFormatSymbols(ulocale));
|
||||
default: // pattern or skeleton
|
||||
// Ignore leading whitespace when looking for "::", the skeleton signal sequence
|
||||
int i = 0;
|
||||
for (; PatternProps.isWhiteSpace(style.charAt(i)); i++);
|
||||
if (style.regionMatches(i, "::", 0, 2)) {
|
||||
// Skeleton
|
||||
newFormat = NumberFormatter.fromSkeleton(style.substring(i + 2)).locale(ulocale).toFormat();
|
||||
} else {
|
||||
// Pattern
|
||||
newFormat = new DecimalFormat(style, new DecimalFormatSymbols(ulocale));
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
|
|
@ -894,7 +894,7 @@ public class MessageRegressionTest extends TestFmwk {
|
|||
format2 = serializeAndDeserialize(format1);
|
||||
assertEquals("MessageFormats (empty pattern) before and after serialization are not equal", format1, format2);
|
||||
|
||||
format1.applyPattern("ab{1}cd{0,number}ef{3,date}gh");
|
||||
format1.applyPattern("ab{1}cd{0,number}ef{3,date}gh{4,number,::percent}ij");
|
||||
format1.setFormat(2, null);
|
||||
format1.setFormatByArgumentIndex(1, NumberFormat.getInstance(ULocale.ENGLISH));
|
||||
format2 = serializeAndDeserialize(format1);
|
||||
|
@ -902,7 +902,7 @@ public class MessageRegressionTest extends TestFmwk {
|
|||
assertEquals(
|
||||
"MessageFormat (with custom formats) does not "+
|
||||
"format correctly after serialization",
|
||||
"ab3.3cd4,4ef***gh",
|
||||
format2.format(new Object[] { 4.4, 3.3, "+++", "***" }));
|
||||
"ab3.3cd4,4ef***gh50\u00A0%ij",
|
||||
format2.format(new Object[] { 4.4, 3.3, "+++", "***", 50 }));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2092,4 +2092,34 @@ public class TestMessageFormat extends TestFmwk {
|
|||
int actualHashResult2 = testDF2.hashCode();
|
||||
assertNotEquals("DateFormat hashCode() test: really the same hashcode?", actualHashResult1, actualHashResult2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void TestMessageFormatNumberSkeleton() {
|
||||
Object[][] cases = new Object[][] {
|
||||
{ "{0,number,::percent}", ULocale.ENGLISH, 50, "50%" },
|
||||
{ "{0,number,::percent scale/100}", ULocale.ENGLISH, 0.5, "50%" },
|
||||
{ "{0,number, :: percent scale/100 }", ULocale.ENGLISH, 0.5, "50%" },
|
||||
{ "{0,number,::currency/USD}", ULocale.ENGLISH, 23, "$23.00" },
|
||||
{ "{0,number,::precision-integer}", ULocale.ENGLISH, 514.23, "514" },
|
||||
{ "{0,number,::.000}", ULocale.ENGLISH, 514.23, "514.230" },
|
||||
{ "{0,number,::.}", ULocale.ENGLISH, 514.23, "514" },
|
||||
{ "{0,number,::}", ULocale.FRENCH, 514.23, "514,23" },
|
||||
{ "Cost: {0,number,::currency/EUR}.", ULocale.ENGLISH, 4.3, "Cost: €4.30." },
|
||||
{ "{0,number,'::'0.00}", ULocale.ENGLISH, 50, "::50.00" }, // pattern literal
|
||||
};
|
||||
|
||||
for (Object[] cas : cases) {
|
||||
String messagePattern = (String) cas[0];
|
||||
ULocale locale = (ULocale) cas[1];
|
||||
Number arg = (Number) cas[2];
|
||||
String expected = (String) cas[3];
|
||||
|
||||
MessageFormat msgf = new MessageFormat(messagePattern, locale);
|
||||
StringBuffer sb = new StringBuffer();
|
||||
FieldPosition fpos = new FieldPosition(0);
|
||||
msgf.format(new Object[] { arg }, sb, fpos);
|
||||
|
||||
assertEquals(messagePattern, expected, sb.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import java.math.BigDecimal;
|
|||
import java.math.RoundingMode;
|
||||
import java.text.AttributedCharacterIterator;
|
||||
import java.text.FieldPosition;
|
||||
import java.text.Format;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Locale;
|
||||
|
@ -23,7 +24,9 @@ import java.util.Set;
|
|||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.ibm.icu.dev.test.serializable.SerializableTestUtility;
|
||||
import com.ibm.icu.impl.number.Grouper;
|
||||
import com.ibm.icu.impl.number.LocalizedNumberFormatterAsFormat;
|
||||
import com.ibm.icu.impl.number.MacroProps;
|
||||
import com.ibm.icu.impl.number.Padder;
|
||||
import com.ibm.icu.impl.number.Padder.PadPosition;
|
||||
|
@ -2118,6 +2121,47 @@ public class NumberFormatterApiTest {
|
|||
assertFalse("No fraction part in an integer", fmtd.nextFieldPosition(actual));
|
||||
}
|
||||
|
||||
/** Handler for serialization compatibility test suite. */
|
||||
public static class FormatHandler implements SerializableTestUtility.Handler {
|
||||
@Override
|
||||
public Object[] getTestObjects() {
|
||||
return new Object[] {
|
||||
NumberFormatter.withLocale(ULocale.FRENCH).toFormat(),
|
||||
NumberFormatter.fromSkeleton("percent").locale(ULocale.JAPANESE).toFormat(),
|
||||
NumberFormatter.fromSkeleton("scientific .000").locale(ULocale.ENGLISH).toFormat() };
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasSameBehavior(Object a, Object b) {
|
||||
LocalizedNumberFormatterAsFormat f1 = (LocalizedNumberFormatterAsFormat) a;
|
||||
LocalizedNumberFormatterAsFormat f2 = (LocalizedNumberFormatterAsFormat) b;
|
||||
String s1 = f1.format(514.23);
|
||||
String s2 = f1.format(514.23);
|
||||
String k1 = f1.getNumberFormatter().toSkeleton();
|
||||
String k2 = f2.getNumberFormatter().toSkeleton();
|
||||
return s1.equals(s2) && k1.equals(k2);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toFormat() {
|
||||
LocalizedNumberFormatter lnf = NumberFormatter.withLocale(ULocale.FRENCH)
|
||||
.precision(Precision.fixedFraction(3));
|
||||
Format format = lnf.toFormat();
|
||||
FieldPosition fpos = new FieldPosition(NumberFormat.Field.DECIMAL_SEPARATOR);
|
||||
StringBuffer sb = new StringBuffer();
|
||||
format.format(514.23, sb, fpos);
|
||||
assertEquals("Should correctly format number", "514,230", sb.toString());
|
||||
assertEquals("Should find decimal separator", 3, fpos.getBeginIndex());
|
||||
assertEquals("Should find end of decimal separator", 4, fpos.getEndIndex());
|
||||
assertEquals("LocalizedNumberFormatter should round-trip",
|
||||
lnf,
|
||||
((LocalizedNumberFormatterAsFormat) format).getNumberFormatter());
|
||||
assertEquals("Should produce same character iterator",
|
||||
lnf.format(514.23).toCharacterIterator().getAttributes(),
|
||||
format.formatToCharacterIterator(514.23).getAttributes());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void plurals() {
|
||||
// TODO: Expand this test.
|
||||
|
|
|
@ -30,6 +30,7 @@ import java.util.Locale;
|
|||
|
||||
import com.ibm.icu.dev.test.format.MeasureUnitTest;
|
||||
import com.ibm.icu.dev.test.format.PluralRulesTest;
|
||||
import com.ibm.icu.dev.test.number.NumberFormatterApiTest;
|
||||
import com.ibm.icu.dev.test.number.PropertiesTest;
|
||||
import com.ibm.icu.impl.JavaTimeZone;
|
||||
import com.ibm.icu.impl.OlsonTimeZone;
|
||||
|
@ -833,6 +834,7 @@ public class SerializableTestUtility {
|
|||
map.put("com.ibm.icu.impl.number.DecimalFormatProperties", new PropertiesTest.PropertiesHandler());
|
||||
map.put("com.ibm.icu.impl.number.CustomSymbolCurrency", new CurrencyHandler());
|
||||
map.put("com.ibm.icu.number.SkeletonSyntaxException", new ExceptionHandler.SkeletonSyntaxExceptionHandler());
|
||||
map.put("com.ibm.icu.impl.number.LocalizedNumberFormatterAsFormat", new NumberFormatterApiTest.FormatHandler());
|
||||
|
||||
map.put("com.ibm.icu.util.ICUException", new ICUExceptionHandler());
|
||||
map.put("com.ibm.icu.util.ICUUncheckedIOException", new ICUUncheckedIOExceptionHandler());
|
||||
|
|
Loading…
Add table
Reference in a new issue