boolean isNegative, boolean isInteger, boolean parseAttr) {
if (currencySignCount == CURRENCY_SIGN_COUNT_IN_PLURAL_FORMAT) {
// compute the plural category from the digitList plus other settings
- return subformat(getPluralCategory(number), result, fieldPosition, isNegative,
+ return subformat(currencyPluralInfo.select(getFixedDecimal(number)),
+ result, fieldPosition, isNegative,
isInteger, parseAttr);
} else {
return subformat(result, fieldPosition, isNegative, isInteger, parseAttr);
/**
* This is ugly, but don't see a better way to do it without major restructuring of the code.
*/
- private String getPluralCategory(double number) {
+ /*package*/ FixedDecimal getFixedDecimal(double number) {
// get the visible fractions and the number of fraction digits.
int fractionalDigitsInDigitList = digitList.count - digitList.decimalAt;
int v;
f *= 10;
}
}
- return currencyPluralInfo.select(new FixedDecimal(number, v, f));
+ return new FixedDecimal(number, v, f);
}
private StringBuffer subformat(double number, StringBuffer result, FieldPosition fieldPosition,
boolean isInteger, boolean parseAttr) {
if (currencySignCount == CURRENCY_SIGN_COUNT_IN_PLURAL_FORMAT) {
// compute the plural category from the digitList plus other settings
- return subformat(getPluralCategory(number), result, fieldPosition, isNegative,
+ return subformat(currencyPluralInfo.select(getFixedDecimal(number)),
+ result, fieldPosition, isNegative,
isInteger, parseAttr);
} else {
return subformat(result, fieldPosition, isNegative, isInteger, parseAttr);
/*
**********************************************************************
-* Copyright (c) 2004-2012, International Business Machines
+* Copyright (c) 2004-2013, International Business Machines
* Corporation and others. All Rights Reserved.
**********************************************************************
* Author: Alan Liu
import com.ibm.icu.impl.Utility;
import com.ibm.icu.text.MessagePattern.ArgType;
import com.ibm.icu.text.MessagePattern.Part;
-import com.ibm.icu.text.PluralFormat.PluralSelector;
+import com.ibm.icu.text.PluralRules.FixedDecimal;
import com.ibm.icu.text.PluralRules.PluralType;
import com.ibm.icu.util.ULocale;
import com.ibm.icu.util.ULocale.Category;
this.ulocale = locale;
// Invalidate all stock formatters. They are no longer valid since
// the locale has changed.
- stockNumberFormatter = stockDateFormatter = null;
+ stockDateFormatter = null;
+ stockNumberFormatter = null;
pluralProvider = null;
ordinalProvider = null;
applyPattern(existingPattern); /*ibm.3550*/
}
other.msgPattern = msgPattern == null ? null : (MessagePattern)msgPattern.clone();
- other.stockDateFormatter = stockDateFormatter == null ? null : (Format) stockDateFormatter.clone();
- other.stockNumberFormatter = stockNumberFormatter == null ? null : (Format) stockNumberFormatter.clone();
+ other.stockDateFormatter =
+ stockDateFormatter == null ? null : (DateFormat) stockDateFormatter.clone();
+ other.stockNumberFormatter =
+ stockNumberFormatter == null ? null : (NumberFormat) stockNumberFormatter.clone();
other.pluralProvider = null;
other.ordinalProvider = null;
* Stock formatters. Those are used when a format is not explicitly mentioned in
* the message. The format is inferred from the argument.
*/
- private transient Format stockDateFormatter;
- private transient Format stockNumberFormatter;
+ private transient DateFormat stockDateFormatter;
+ private transient NumberFormat stockNumberFormatter;
private transient PluralSelectorProvider pluralProvider;
private transient PluralSelectorProvider ordinalProvider;
+ private DateFormat getStockDateFormatter() {
+ if (stockDateFormatter == null) {
+ stockDateFormatter = DateFormat.getDateTimeInstance(
+ DateFormat.SHORT, DateFormat.SHORT, ulocale);//fix
+ }
+ return stockDateFormatter;
+ }
+ private NumberFormat getStockNumberFormatter() {
+ if (stockNumberFormatter == null) {
+ stockNumberFormatter = NumberFormat.getInstance(ulocale);
+ }
+ return stockNumberFormatter;
+ }
+
// *Important*: All fields must be declared *transient*.
// See the longer comment above ulocale.
* <p>Exactly one of args and argsMap must be null, the other non-null.
*
* @param msgStart Index to msgPattern part to start formatting from.
- * @param pluralNumber Zero except when formatting a plural argument sub-message
+ * @param pluralNumber null except when formatting a plural argument sub-message.
* where a '#' is replaced by the format string for this number.
* @param args The formattable objects array. Non-null iff numbered values are used.
* @param argsMap The key-value map of formattable objects. Non-null iff named values are used.
* The result (string & attributes) is appended to existing contents.
* @param fp Field position status.
*/
- private void format(int msgStart, double pluralNumber,
+ private void format(int msgStart, PluralSelectorContext pluralNumber,
Object[] args, Map<String, Object> argsMap,
AppendableWrapper dest, FieldPosition fp) {
String msgString=msgPattern.getPatternString();
}
prevIndex=part.getLimit();
if(type==Part.Type.REPLACE_NUMBER) {
- if (stockNumberFormatter == null) {
- stockNumberFormatter = NumberFormat.getInstance(ulocale);
+ if(pluralNumber.forReplaceNumber) {
+ // number-offset was already formatted.
+ dest.formatAndAppend(pluralNumber.formatter,
+ pluralNumber.number, pluralNumber.numberString);
+ } else {
+ dest.formatAndAppend(getStockNumberFormatter(), pluralNumber.number);
}
- dest.formatAndAppend(stockNumberFormatter, pluralNumber);
continue;
}
if(type!=Part.Type.ARG_START) {
Object arg;
String noArg=null;
Object argId=null;
+ String argName=msgPattern.getSubstring(part);
if(args!=null) {
int argNumber=part.getValue(); // ARG_NUMBER
if (dest.attributes != null) {
noArg="{"+argNumber+"}";
}
} else {
- String key;
- if(part.getType()==MessagePattern.Part.Type.ARG_NAME) {
- key=msgPattern.getSubstring(part);
- } else /* ARG_NUMBER */ {
- key=Integer.toString(part.getValue());
- }
- argId = key;
- if(argsMap!=null && argsMap.containsKey(key)) {
- arg=argsMap.get(key);
+ argId = argName;
+ if(argsMap!=null && argsMap.containsKey(argName)) {
+ arg=argsMap.get(argName);
} else {
arg=null;
- noArg="{"+key+"}";
+ noArg="{"+argName+"}";
}
}
++i;
dest.append(noArg);
} else if (arg == null) {
dest.append("null");
+ } else if(pluralNumber!=null && pluralNumber.numberArgIndex==(i-2)) {
+ if(pluralNumber.offset == 0) {
+ // The number was already formatted with this formatter.
+ dest.formatAndAppend(pluralNumber.formatter, pluralNumber.number, pluralNumber.numberString);
+ } else {
+ // Do not use the formatted (number-offset) string for a named argument
+ // that formats the number without subtracting the offset.
+ dest.formatAndAppend(pluralNumber.formatter, arg);
+ }
} else if(cachedFormatters!=null && (formatter=cachedFormatters.get(i - 2))!=null) {
// Handles all ArgType.SIMPLE, and formatters from setFormat() and its siblings.
if ( formatter instanceof ChoiceFormat ||
if (subMsgString.indexOf('{') >= 0 ||
(subMsgString.indexOf('\'') >= 0 && !msgPattern.jdkAposMode())) {
MessageFormat subMsgFormat = new MessageFormat(subMsgString, ulocale);
- subMsgFormat.format(0, 0, args, argsMap, dest, null);
+ subMsgFormat.format(0, null, args, argsMap, dest, null);
} else if (dest.attributes == null) {
dest.append(subMsgString);
} else {
// any argument which got reset to null via setFormat() or its siblings.
if (arg instanceof Number) {
// format number if can
- if (stockNumberFormatter == null) {
- stockNumberFormatter = NumberFormat.getInstance(ulocale);
- }
- dest.formatAndAppend(stockNumberFormatter, arg);
+ dest.formatAndAppend(getStockNumberFormatter(), arg);
} else if (arg instanceof Date) {
// format a Date if can
- if (stockDateFormatter == null) {
- stockDateFormatter = DateFormat.getDateTimeInstance(
- DateFormat.SHORT, DateFormat.SHORT, ulocale);//fix
- }
- dest.formatAndAppend(stockDateFormatter, arg);
+ dest.formatAndAppend(getStockDateFormatter(), arg);
} else {
dest.append(arg.toString());
}
}
double number = ((Number)arg).doubleValue();
int subMsgStart=findChoiceSubMessage(msgPattern, i, number);
- formatComplexSubMessage(subMsgStart, 0, args, argsMap, dest);
+ formatComplexSubMessage(subMsgStart, null, args, argsMap, dest);
} else if(argType.hasPluralStyle()) {
if (!(arg instanceof Number)) {
throw new IllegalArgumentException("'" + arg + "' is not a Number");
}
- double number = ((Number)arg).doubleValue();
- PluralSelector selector;
+ PluralSelectorProvider selector;
if(argType == ArgType.PLURAL) {
if (pluralProvider == null) {
- pluralProvider = new PluralSelectorProvider(ulocale, PluralType.CARDINAL);
+ pluralProvider = new PluralSelectorProvider(this, PluralType.CARDINAL);
}
selector = pluralProvider;
} else {
if (ordinalProvider == null) {
- ordinalProvider = new PluralSelectorProvider(ulocale, PluralType.ORDINAL);
+ ordinalProvider = new PluralSelectorProvider(this, PluralType.ORDINAL);
}
selector = ordinalProvider;
}
- int subMsgStart=PluralFormat.findSubMessage(msgPattern, i, selector, number);
+ Number number = (Number)arg;
double offset=msgPattern.getPluralOffset(i);
- formatComplexSubMessage(subMsgStart, number-offset, args, argsMap, dest);
+ PluralSelectorContext context =
+ new PluralSelectorContext(i, argName, number, offset);
+ int subMsgStart=PluralFormat.findSubMessage(
+ msgPattern, i, selector, context, number.doubleValue());
+ formatComplexSubMessage(subMsgStart, context, args, argsMap, dest);
} else if(argType==ArgType.SELECT) {
int subMsgStart=SelectFormat.findSubMessage(msgPattern, i, arg.toString());
- formatComplexSubMessage(subMsgStart, 0, args, argsMap, dest);
+ formatComplexSubMessage(subMsgStart, null, args, argsMap, dest);
} else {
// This should never happen.
throw new IllegalStateException("unexpected argType "+argType);
}
private void formatComplexSubMessage(
- int msgStart, double pluralNumber,
+ int msgStart, PluralSelectorContext pluralNumber,
Object[] args, Map<String, Object> argsMap,
AppendableWrapper dest) {
if (!msgPattern.jdkAposMode()) {
}
sb.append(msgString, prevIndex, index);
if (type == Part.Type.REPLACE_NUMBER) {
- if (stockNumberFormatter == null) {
- stockNumberFormatter = NumberFormat.getInstance(ulocale);
+ if(pluralNumber.forReplaceNumber) {
+ // number-offset was already formatted.
+ sb.append(pluralNumber.numberString);
+ } else {
+ sb.append(getStockNumberFormatter().format(pluralNumber.number));
}
- sb.append(stockNumberFormatter.format(pluralNumber));
}
prevIndex = part.getLimit();
} else if (type == Part.Type.ARG_START) {
if (subMsgString.indexOf('{') >= 0) {
MessageFormat subMsgFormat = new MessageFormat("", ulocale);
subMsgFormat.applyPattern(subMsgString, MessagePattern.ApostropheMode.DOUBLE_REQUIRED);
- subMsgFormat.format(0, 0, args, argsMap, dest, null);
+ subMsgFormat.format(0, null, args, argsMap, dest, null);
} else {
dest.append(subMsgString);
}
}
}
+ /**
+ * Finds the "other" sub-message.
+ * @param partIndex the index of the first PluralFormat argument style part.
+ * @return the "other" sub-message start part index.
+ */
+ private int findOtherSubMessage(int partIndex) {
+ int count=msgPattern.countParts();
+ MessagePattern.Part part=msgPattern.getPart(partIndex);
+ if(part.getType().hasNumericValue()) {
+ ++partIndex;
+ }
+ // Iterate over (ARG_SELECTOR [ARG_INT|ARG_DOUBLE] message) tuples
+ // until ARG_LIMIT or end of plural-only pattern.
+ do {
+ part=msgPattern.getPart(partIndex++);
+ MessagePattern.Part.Type type=part.getType();
+ if(type==MessagePattern.Part.Type.ARG_LIMIT) {
+ break;
+ }
+ assert type==MessagePattern.Part.Type.ARG_SELECTOR;
+ // part is an ARG_SELECTOR followed by an optional explicit value, and then a message
+ if(msgPattern.partSubstringMatches(part, "other")) {
+ return partIndex;
+ }
+ if(msgPattern.getPartType(partIndex).hasNumericValue()) {
+ ++partIndex; // skip the numeric-value part of "=1" etc.
+ }
+ partIndex=msgPattern.getLimitPartIndex(partIndex);
+ } while(++partIndex<count);
+ return 0;
+ }
+
+ /**
+ * Returns the ARG_START index of the first occurrence of the plural number in a sub-message.
+ * Returns -1 if it is a REPLACE_NUMBER.
+ * Returns 0 if there is neither.
+ */
+ private int findFirstPluralNumberArg(int msgStart, String argName) {
+ for(int i=msgStart+1;; ++i) {
+ Part part=msgPattern.getPart(i);
+ Part.Type type=part.getType();
+ if(type==Part.Type.MSG_LIMIT) {
+ return 0;
+ }
+ if(type==Part.Type.REPLACE_NUMBER) {
+ return -1;
+ }
+ if(type==Part.Type.ARG_START) {
+ ArgType argType=part.getArgType();
+ if(argName.length()!=0 && (argType==ArgType.NONE || argType==ArgType.SIMPLE)) {
+ part=msgPattern.getPart(i+1); // ARG_NUMBER or ARG_NAME
+ if(msgPattern.partSubstringMatches(part, argName)) {
+ return i;
+ }
+ }
+ i=msgPattern.getLimitPartIndex(i);
+ }
+ }
+ }
+
+ /**
+ * Mutable input/output values for the PluralSelectorProvider.
+ * Separate so that it is possible to make MessageFormat Freezable.
+ */
+ private static final class PluralSelectorContext {
+ private PluralSelectorContext(int start, String name, Number num, double off) {
+ startIndex = start;
+ argName = name;
+ // number needs to be set even when select() is not called.
+ // Keep it as a Number/Formattable:
+ // For format() methods, and to preserve information (e.g., BigDecimal).
+ if(off == 0) {
+ number = num;
+ } else {
+ number = num.doubleValue() - off;
+ }
+ offset = off;
+ }
+ @Override
+ public String toString() {
+ throw new AssertionError("PluralSelectorContext being formatted, rather than its number");
+ }
+
+ // Input values for plural selection with decimals.
+ int startIndex;
+ String argName;
+ /** argument number - plural offset */
+ Number number;
+ double offset;
+ // Output values for plural selection with decimals.
+ /** -1 if REPLACE_NUMBER, 0 arg not found, >0 ARG_START index */
+ int numberArgIndex;
+ Format formatter;
+ /** formatted argument number - plural offset */
+ String numberString;
+ /** true if number-offset was formatted with the stock number formatter */
+ boolean forReplaceNumber;
+ }
+
/**
* This provider helps defer instantiation of a PluralRules object
* until we actually need to select a keyword.
* we do not need any PluralRules.
*/
private static final class PluralSelectorProvider implements PluralFormat.PluralSelector {
- public PluralSelectorProvider(ULocale loc, PluralType type) {
- locale=loc;
- this.type=type;
+ public PluralSelectorProvider(MessageFormat mf, PluralType type) {
+ msgFormat = mf;
+ this.type = type;
}
- public String select(double number) {
+ public String select(Object ctx, double number) {
if(rules == null) {
- rules = PluralRules.forLocale(locale, type);
+ rules = PluralRules.forLocale(msgFormat.ulocale, type);
+ }
+ // Select a sub-message according to how the number is formatted,
+ // which is specified in the selected sub-message.
+ // We avoid this circle by looking at how
+ // the number is formatted in the "other" sub-message
+ // which must always be present and usually contains the number.
+ // Message authors should be consistent across sub-messages.
+ PluralSelectorContext context = (PluralSelectorContext)ctx;
+ int otherIndex = msgFormat.findOtherSubMessage(context.startIndex);
+ context.numberArgIndex = msgFormat.findFirstPluralNumberArg(otherIndex, context.argName);
+ if(context.numberArgIndex > 0 && msgFormat.cachedFormatters != null) {
+ context.formatter = msgFormat.cachedFormatters.get(context.numberArgIndex);
+ }
+ if(context.formatter == null) {
+ context.formatter = msgFormat.getStockNumberFormatter();
+ context.forReplaceNumber = true;
+ }
+ assert context.number.doubleValue() == number; // argument number minus the offset
+ context.numberString = context.formatter.format(context.number);
+ if(context.formatter instanceof DecimalFormat) {
+ FixedDecimal dec = ((DecimalFormat)context.formatter).getFixedDecimal(number);
+ return rules.select(dec);
+ } else {
+ return rules.select(number);
}
- return rules.select(number);
}
- private ULocale locale;
+ private MessageFormat msgFormat;
private PluralRules rules;
private PluralType type;
}
"This method is not available in MessageFormat objects " +
"that use alphanumeric argument names.");
}
- format(0, 0, arguments, argsMap, dest, fp);
+ format(0, null, arguments, argsMap, dest, fp);
}
private void resetPattern() {
}
}
+ public void formatAndAppend(Format formatter, Object arg, String argString) {
+ if (attributes == null && argString != null) {
+ append(argString);
+ } else {
+ formatAndAppend(formatter, arg);
+ }
+ }
+
private Appendable app;
private int length;
private List<AttributeAndPosition> attributes;
import java.util.Map;
import com.ibm.icu.impl.Utility;
+import com.ibm.icu.text.PluralRules.FixedDecimal;
import com.ibm.icu.text.PluralRules.PluralType;
import com.ibm.icu.util.ULocale;
import com.ibm.icu.util.ULocale.Category;
* @param pattern A MessagePattern.
* @param partIndex the index of the first PluralFormat argument style part.
* @param selector the PluralSelector for mapping the number (minus offset) to a keyword.
+ * @param context worker object for the selector.
* @param number a number to be matched to one of the PluralFormat argument's explicit values,
* or mapped via the PluralSelector.
* @return the sub-message start part index.
*/
/*package*/ static int findSubMessage(
MessagePattern pattern, int partIndex,
- PluralSelector selector, double number) {
+ PluralSelector selector, Object context, double number) {
int count=pattern.countParts();
double offset;
MessagePattern.Part part=pattern.getPart(partIndex);
} else {
offset=0;
}
- // The keyword is null until we need to match against non-explicit, not-"other" value.
+ // The keyword is null until we need to match against a non-explicit, not-"other" value.
// Then we get the keyword from the selector.
// (In other words, we never call the selector if we match against an explicit value,
// or if the only non-explicit keyword is "other".)
}
} else {
if(keyword==null) {
- keyword=selector.select(number-offset);
+ keyword=selector.select(context, number-offset);
if(msgStart!=0 && keyword.equals("other")) {
// We have already seen an "other" sub-message.
// Do not match "other" again.
/**
* Given a number, returns the appropriate PluralFormat keyword.
*
+ * @param context worker object for the selector.
* @param number The number to be plural-formatted.
* @return The selected PluralFormat keyword.
*/
- public String select(double number);
+ public String select(Object context, double number);
}
// See PluralSelector:
// We could avoid this adapter class if we made PluralSelector public
// (or at least publicly visible) and had PluralRules implement PluralSelector.
private final class PluralSelectorAdapter implements PluralSelector {
- public String select(double number) {
- return pluralRules.select(number);
+ public String select(Object context, double number) {
+ FixedDecimal dec = (FixedDecimal) context;
+ assert dec.source == number;
+ return pluralRules.select(dec);
}
}
transient private PluralSelectorAdapter pluralRulesWrapper = new PluralSelectorAdapter();
* @stable ICU 4.0
*/
public final String format(double number) {
+ return format(number, number);
+ }
+
+ /**
+ * Formats a plural message for a given number and appends the formatted
+ * message to the given <code>StringBuffer</code>.
+ * @param number a number object (instance of <code>Number</code> for which
+ * the plural message should be formatted. If no pattern has been
+ * applied to this <code>PluralFormat</code> object yet, the
+ * formatted number will be returned.
+ * Note: If this object is not an instance of <code>Number</code>,
+ * the <code>toAppendTo</code> will not be modified.
+ * @param toAppendTo the formatted message will be appended to this
+ * <code>StringBuffer</code>.
+ * @param pos will be ignored by this method.
+ * @return the string buffer passed in as toAppendTo, with formatted text
+ * appended.
+ * @throws IllegalArgumentException if number is not an instance of Number
+ * @stable ICU 3.8
+ */
+ public StringBuffer format(Object number, StringBuffer toAppendTo,
+ FieldPosition pos) {
+ if (!(number instanceof Number)) {
+ throw new IllegalArgumentException("'" + number + "' is not a Number");
+ }
+ Number numberObject = (Number) number;
+ toAppendTo.append(format(numberObject, numberObject.doubleValue()));
+ return toAppendTo;
+ }
+
+ private final String format(Number numberObject, double number) {
// If no pattern was applied, return the formatted number.
if (msgPattern == null || msgPattern.countParts() == 0) {
- return numberFormat.format(number);
+ return numberFormat.format(numberObject);
}
// Get the appropriate sub-message.
- int partIndex = findSubMessage(msgPattern, 0, pluralRulesWrapper, number);
+ // Select it based on the formatted number-offset.
+ double numberMinusOffset = number - offset;
+ String numberString;
+ if (offset == 0) {
+ numberString = numberFormat.format(numberObject); // could be BigDecimal etc.
+ } else {
+ numberString = numberFormat.format(numberMinusOffset);
+ }
+ FixedDecimal dec;
+ if(numberFormat instanceof DecimalFormat) {
+ dec = ((DecimalFormat) numberFormat).getFixedDecimal(numberMinusOffset);
+ } else {
+ dec = new FixedDecimal(numberMinusOffset);
+ }
+ int partIndex = findSubMessage(msgPattern, 0, pluralRulesWrapper, dec, number);
// Replace syntactic # signs in the top level of this sub-message
// (not in nested arguments) with the formatted number-offset.
- number -= offset;
StringBuilder result = null;
int prevIndex = msgPattern.getPart(partIndex).getLimit();
for (;;) {
}
result.append(pattern, prevIndex, index);
if (type == MessagePattern.Part.Type.REPLACE_NUMBER) {
- result.append(numberFormat.format(number));
+ result.append(numberString);
}
prevIndex = part.getLimit();
} else if (type == MessagePattern.Part.Type.ARG_START) {
}
}
- /**
- * Formats a plural message for a given number and appends the formatted
- * message to the given <code>StringBuffer</code>.
- * @param number a number object (instance of <code>Number</code> for which
- * the plural message should be formatted. If no pattern has been
- * applied to this <code>PluralFormat</code> object yet, the
- * formatted number will be returned.
- * Note: If this object is not an instance of <code>Number</code>,
- * the <code>toAppendTo</code> will not be modified.
- * @param toAppendTo the formatted message will be appended to this
- * <code>StringBuffer</code>.
- * @param pos will be ignored by this method.
- * @return the string buffer passed in as toAppendTo, with formatted text
- * appended.
- * @throws IllegalArgumentException if number is not an instance of Number
- * @stable ICU 3.8
- */
- public StringBuffer format(Object number, StringBuffer toAppendTo,
- FieldPosition pos) {
- if (number instanceof Number) {
- toAppendTo.append(format(((Number) number).doubleValue()));
- return toAppendTo;
- }
- throw new IllegalArgumentException("'" + number + "' is not a Number");
- }
-
/**
* This method is not yet supported by <code>PluralFormat</code>.
* @param text the string to be parsed.
import java.util.regex.Pattern;
import com.ibm.icu.impl.PluralRulesLoader;
-import com.ibm.icu.impl.Utility;
import com.ibm.icu.util.Output;
import com.ibm.icu.util.ULocale;
import java.util.Set;
import com.ibm.icu.dev.test.TestFmwk;
+import com.ibm.icu.text.DecimalFormat;
+import com.ibm.icu.text.DecimalFormatSymbols;
import com.ibm.icu.text.MessageFormat;
import com.ibm.icu.text.NumberFormat;
import com.ibm.icu.text.PluralFormat;
import com.ibm.icu.text.PluralRules;
import com.ibm.icu.text.PluralRules.PluralType;
import com.ibm.icu.text.PluralRules.SampleType;
-import com.ibm.icu.text.UFieldPosition;
import com.ibm.icu.util.ULocale;
/**
PluralFormat pf = new PluralFormat(ULocale.ENGLISH, pluralStyle);
MessageFormat mf = new MessageFormat("{0,plural," + pluralStyle + "}", ULocale.ENGLISH);
Integer args[] = new Integer[1];
- for (int i = 0; i < 7; ++i) {
+ for (int i = 0; i <= 7; ++i) {
String result = pf.format(i);
assertEquals("PluralFormat.format(value " + i + ")", targets[i], result);
args[0] = i;
assertEquals("PluralFormat.format(456)", "456th file", pf.format(456));
assertEquals("PluralFormat.format(111)", "111th file", pf.format(111));
}
-
- public void TestBasicFraction() {
- String[][] tests = {
- {"en", "one: j is 1"},
- {"1", "0", "1", "one"},
- {"1", "2", "1.00", "other"},
- };
- ULocale locale = null;
- NumberFormat nf = null;
- PluralRules pr = null;
-
- for (String[] row : tests) {
- switch(row.length) {
- case 2:
- locale = ULocale.forLanguageTag(row[0]);
- nf = NumberFormat.getInstance(locale);
- pr = PluralRules.createRules(row[1]);
- break;
- case 4:
- double n = Double.parseDouble(row[0]);
- int minFracDigits = Integer.parseInt(row[1]);
- nf.setMinimumFractionDigits(minFracDigits);
- String expectedFormat = row[2];
- String expectedKeyword = row[3];
-
- UFieldPosition pos = new UFieldPosition();
- String formatted = nf.format(1.0, new StringBuffer(), pos).toString();
- int countVisibleFractionDigits = pos.getCountVisibleFractionDigits();
- long fractionDigits = pos.getFractionDigits();
- String keyword = pr.select(n, countVisibleFractionDigits, fractionDigits);
- assertEquals("Formatted " + n + "\t" + minFracDigits, expectedFormat, formatted);
- assertEquals("Keyword " + n + "\t" + minFracDigits, expectedKeyword, keyword);
- break;
- default:
- throw new RuntimeException();
- }
- }
- }
+ public void TestDecimals() {
+ // Simple number replacement.
+ PluralFormat pf = new PluralFormat(ULocale.ENGLISH, "one{one meter}other{# meters}");
+ assertEquals("simple format(1)", "one meter", pf.format(1));
+ assertEquals("simple format(1.5)", "1.5 meters", pf.format(1.5));
+
+ PluralFormat pf2 = new PluralFormat(ULocale.ENGLISH,
+ "offset:1 one{another meter}other{another # meters}");
+ pf2.setNumberFormat(new DecimalFormat("0.0", new DecimalFormatSymbols(ULocale.ENGLISH)));
+ assertEquals("offset-decimals format(1)", "another 0.0 meters", pf2.format(1));
+ assertEquals("offset-decimals format(2)", "another 1.0 meters", pf2.format(2));
+ assertEquals("offset-decimals format(2.5)", "another 1.5 meters", pf2.format(2.5));
+ }
}
import com.ibm.icu.dev.util.CollectionUtilities;
import com.ibm.icu.dev.util.Relation;
import com.ibm.icu.impl.Utility;
+import com.ibm.icu.text.NumberFormat;
import com.ibm.icu.text.PluralRules;
+import com.ibm.icu.text.UFieldPosition;
import com.ibm.icu.text.PluralRules.FixedDecimalRange;
import com.ibm.icu.text.PluralRules.FixedDecimalSamples;
import com.ibm.icu.text.PluralRules.KeywordStatus;
Class exception = shouldFailTest.length < 2 ? null : (Class) shouldFailTest[1];
Class actualException = null;
try {
- PluralRules test = PluralRules.parseDescription(rules);
+ PluralRules.parseDescription(rules);
} catch (Exception e) {
actualException = e.getClass();
}
integerSamples == null && decimalSamples != null && decimalSamples.samples.size() != 0);
} else {
if (!assertTrue(getAssertMessage("Test getSamples.isEmpty", locale, rules, keyword), !list.isEmpty())) {
- int debugHere = 0;
rules.getSamples(keyword);
}
if (rules.toString().contains(": j")) {
assertEquals("PluralRules(en-ordinal).select(2)", "two", pr.select(2));
}
+ public void TestBasicFraction() {
+ String[][] tests = {
+ {"en", "one: j is 1"},
+ {"1", "0", "1", "one"},
+ {"1", "2", "1.00", "other"},
+ };
+ ULocale locale = null;
+ NumberFormat nf = null;
+ PluralRules pr = null;
+
+ for (String[] row : tests) {
+ switch(row.length) {
+ case 2:
+ locale = ULocale.forLanguageTag(row[0]);
+ nf = NumberFormat.getInstance(locale);
+ pr = PluralRules.createRules(row[1]);
+ break;
+ case 4:
+ double n = Double.parseDouble(row[0]);
+ int minFracDigits = Integer.parseInt(row[1]);
+ nf.setMinimumFractionDigits(minFracDigits);
+ String expectedFormat = row[2];
+ String expectedKeyword = row[3];
+
+ UFieldPosition pos = new UFieldPosition();
+ String formatted = nf.format(1.0, new StringBuffer(), pos).toString();
+ int countVisibleFractionDigits = pos.getCountVisibleFractionDigits();
+ long fractionDigits = pos.getFractionDigits();
+ String keyword = pr.select(n, countVisibleFractionDigits, fractionDigits);
+ assertEquals("Formatted " + n + "\t" + minFracDigits, expectedFormat, formatted);
+ assertEquals("Keyword " + n + "\t" + minFracDigits, expectedKeyword, keyword);
+ break;
+ default:
+ throw new RuntimeException();
+ }
+ }
+ }
+
public void TestLimitedAndSamplesConsistency() {
for (ULocale locale : PluralRules.getAvailableULocales()) {
ULocale loc2 = PluralRules.getFunctionalEquivalent(locale, null);
assertEquals(getAssertMessage("computeLimited == isLimited", locale, rules, keyword), computeLimited, isLimited);
}
Collection<Double> samples = rules.getSamples(keyword, sampleType);
- FixedDecimalSamples decimalSamples = rules.getDecimalSamples(keyword, sampleType);
assertNotNull(getAssertMessage("Samples must not be null", locale, rules, keyword), samples);
+ /*FixedDecimalSamples decimalSamples = */ rules.getDecimalSamples(keyword, sampleType);
//assertNotNull(getAssertMessage("Decimal samples must be null if unlimited", locale, rules, keyword), decimalSamples);
}
}
}
logln("max \tsize:\t" + max);
}
-}
\ No newline at end of file
+}
assertEquals("plural-and-ordinal format(3) failed", "3 files, 3rd file",
m.format(args, result, ignore).toString());
}
+
+ public void TestDecimals() {
+ // Simple number replacement.
+ MessageFormat m = new MessageFormat(
+ "{0,plural,one{one meter}other{# meters}}",
+ ULocale.ENGLISH);
+ Object[] args = new Object[] { 1 };
+ FieldPosition ignore = null;
+ StringBuffer result = new StringBuffer();
+ assertEquals("simple format(1)", "one meter",
+ m.format(args, result, ignore).toString());
+
+ args[0] = 1.5;
+ result.delete(0, result.length());
+ assertEquals("simple format(1.5)", "1.5 meters",
+ m.format(args, result, ignore).toString());
+
+ // Simple but explicit.
+ MessageFormat m0 = new MessageFormat(
+ "{0,plural,one{one meter}other{{0} meters}}",
+ ULocale.ENGLISH);
+ args[0] = 1;
+ result.delete(0, result.length());
+ assertEquals("explicit format(1)", "one meter",
+ m0.format(args, result, ignore).toString());
+
+ args[0] = 1.5;
+ result.delete(0, result.length());
+ assertEquals("explicit format(1.5)", "1.5 meters",
+ m0.format(args, result, ignore).toString());
+
+ // With offset and specific simple format with optional decimals.
+ MessageFormat m1 = new MessageFormat(
+ "{0,plural,offset:1 one{another meter}other{{0,number,00.#} meters}}",
+ ULocale.ENGLISH);
+ args[0] = 1;
+ result.delete(0, result.length());
+ assertEquals("offset format(1)", "01 meters",
+ m1.format(args, result, ignore).toString());
+
+ args[0] = 2;
+ result.delete(0, result.length());
+ assertEquals("offset format(1)", "another meter",
+ m1.format(args, result, ignore).toString());
+
+ args[0] = 2.5;
+ result.delete(0, result.length());
+ assertEquals("offset format(1)", "02.5 meters",
+ m1.format(args, result, ignore).toString());
+
+ // With offset and specific simple format with forced decimals.
+ MessageFormat m2 = new MessageFormat(
+ "{0,plural,offset:1 one{another meter}other{{0,number,0.0} meters}}",
+ ULocale.ENGLISH);
+ args[0] = 1;
+ result.delete(0, result.length());
+ assertEquals("offset-decimals format(1)", "1.0 meters",
+ m2.format(args, result, ignore).toString());
+
+ args[0] = 2;
+ result.delete(0, result.length());
+ assertEquals("offset-decimals format(1)", "2.0 meters",
+ m2.format(args, result, ignore).toString());
+
+ args[0] = 2.5;
+ result.delete(0, result.length());
+ assertEquals("offset-decimals format(1)", "2.5 meters",
+ m2.format(args, result, ignore).toString());
+ }
}