ICU-13742 Implementing number skeletons in MessageFormat.

X-SVN-Rev: 41377
This commit is contained in:
Shane Carr 2018-05-15 00:05:04 +00:00
parent c3fa4e91b5
commit b347a140ec
16 changed files with 529 additions and 29 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -68,6 +68,7 @@ class NumberFormatterApiTest : public IntlTest {
void locale();
void formatTypes();
void fieldPosition();
void toFormat();
void errors();
void validRanges();
void copyMove();

View file

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

View file

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

View file

@ -121,6 +121,7 @@ public:
void TestSelectOrdinal();
void TestDecimals();
void TestArgIsPrefixOfAnother();
void TestMessageFormatNumberSkeleton();
private:
UnicodeString GetPatternAndSkipSyntax(const MessagePattern& pattern);

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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