ICU-9132 Java support for MessageFormat selectordinal and PluralRules.PluralType.ORDINAL

X-SVN-Rev: 31705
This commit is contained in:
Markus Scherer 2012-04-13 20:59:37 +00:00
parent adcdc1c20e
commit 3e084c0f60
11 changed files with 298 additions and 85 deletions

View File

@ -16,6 +16,7 @@ import java.util.Set;
import java.util.TreeMap; import java.util.TreeMap;
import com.ibm.icu.text.PluralRules; import com.ibm.icu.text.PluralRules;
import com.ibm.icu.text.PluralRules.PluralType;
import com.ibm.icu.util.ULocale; import com.ibm.icu.util.ULocale;
import com.ibm.icu.util.UResourceBundle; import com.ibm.icu.util.UResourceBundle;
@ -24,12 +25,10 @@ import com.ibm.icu.util.UResourceBundle;
*/ */
public class PluralRulesLoader { public class PluralRulesLoader {
private final Map<String, PluralRules> rulesIdToRules; private final Map<String, PluralRules> rulesIdToRules;
private Map<String, String> localeIdToRulesId; // lazy init, use // lazy init, use getLocaleIdToRulesIdMap to access
// getLocaleIdToRulesIdMap to private Map<String, String> localeIdToCardinalRulesId;
// access private Map<String, String> localeIdToOrdinalRulesId;
private Map<String, ULocale> rulesIdToEquivalentULocale; // lazy init, use private Map<String, ULocale> rulesIdToEquivalentULocale;
// getRulesIdToEquivalentULocaleMap
// to access
/** /**
* Access through singleton. * Access through singleton.
@ -42,7 +41,7 @@ public class PluralRulesLoader {
* Returns the locales for which we have plurals data. Utility for testing. * Returns the locales for which we have plurals data. Utility for testing.
*/ */
public ULocale[] getAvailableULocales() { public ULocale[] getAvailableULocales() {
Set<String> keys = getLocaleIdToRulesIdMap().keySet(); Set<String> keys = getLocaleIdToRulesIdMap(PluralType.CARDINAL).keySet();
ULocale[] locales = new ULocale[keys.size()]; ULocale[] locales = new ULocale[keys.size()];
int n = 0; int n = 0;
for (Iterator<String> iter = keys.iterator(); iter.hasNext();) { for (Iterator<String> iter = keys.iterator(); iter.hasNext();) {
@ -57,11 +56,11 @@ public class PluralRulesLoader {
public ULocale getFunctionalEquivalent(ULocale locale, boolean[] isAvailable) { public ULocale getFunctionalEquivalent(ULocale locale, boolean[] isAvailable) {
if (isAvailable != null && isAvailable.length > 0) { if (isAvailable != null && isAvailable.length > 0) {
String localeId = ULocale.canonicalize(locale.getBaseName()); String localeId = ULocale.canonicalize(locale.getBaseName());
Map<String, String> idMap = getLocaleIdToRulesIdMap(); Map<String, String> idMap = getLocaleIdToRulesIdMap(PluralType.CARDINAL);
isAvailable[0] = idMap.containsKey(localeId); isAvailable[0] = idMap.containsKey(localeId);
} }
String rulesId = getRulesIdForLocale(locale); String rulesId = getRulesIdForLocale(locale, PluralType.CARDINAL);
if (rulesId == null || rulesId.trim().length() == 0) { if (rulesId == null || rulesId.trim().length() == 0) {
return ULocale.ROOT; // ultimate fallback return ULocale.ROOT; // ultimate fallback
} }
@ -78,9 +77,9 @@ public class PluralRulesLoader {
/** /**
* Returns the lazily-constructed map. * Returns the lazily-constructed map.
*/ */
private Map<String, String> getLocaleIdToRulesIdMap() { private Map<String, String> getLocaleIdToRulesIdMap(PluralType type) {
checkBuildRulesIdMaps(); checkBuildRulesIdMaps();
return localeIdToRulesId; return (type == PluralType.CARDINAL) ? localeIdToCardinalRulesId : localeIdToOrdinalRulesId;
} }
/** /**
@ -99,39 +98,53 @@ public class PluralRulesLoader {
private void checkBuildRulesIdMaps() { private void checkBuildRulesIdMaps() {
boolean haveMap; boolean haveMap;
synchronized (this) { synchronized (this) {
haveMap = localeIdToRulesId != null; haveMap = localeIdToCardinalRulesId != null;
} }
if (!haveMap) { if (!haveMap) {
Map<String, String> tempLocaleIdToRulesId; Map<String, String> tempLocaleIdToCardinalRulesId;
Map<String, String> tempLocaleIdToOrdinalRulesId;
Map<String, ULocale> tempRulesIdToEquivalentULocale; Map<String, ULocale> tempRulesIdToEquivalentULocale;
try { try {
UResourceBundle pluralb = getPluralBundle(); UResourceBundle pluralb = getPluralBundle();
// Read cardinal-number rules.
UResourceBundle localeb = pluralb.get("locales"); UResourceBundle localeb = pluralb.get("locales");
// sort for convenience of getAvailableULocales // sort for convenience of getAvailableULocales
tempLocaleIdToRulesId = new TreeMap<String, String>(); tempLocaleIdToCardinalRulesId = new TreeMap<String, String>();
// not visible // not visible
tempRulesIdToEquivalentULocale = new HashMap<String, ULocale>(); tempRulesIdToEquivalentULocale = new HashMap<String, ULocale>();
for (int i = 0; i < localeb.getSize(); ++i) { for (int i = 0; i < localeb.getSize(); ++i) {
UResourceBundle b = localeb.get(i); UResourceBundle b = localeb.get(i);
String id = b.getKey(); String id = b.getKey();
String value = b.getString().intern(); String value = b.getString().intern();
tempLocaleIdToRulesId.put(id, value); tempLocaleIdToCardinalRulesId.put(id, value);
if (!tempRulesIdToEquivalentULocale.containsKey(value)) { if (!tempRulesIdToEquivalentULocale.containsKey(value)) {
tempRulesIdToEquivalentULocale.put(value, new ULocale(id)); tempRulesIdToEquivalentULocale.put(value, new ULocale(id));
} }
} }
// Read ordinal-number rules.
localeb = pluralb.get("locales_ordinals");
tempLocaleIdToOrdinalRulesId = new TreeMap<String, String>();
for (int i = 0; i < localeb.getSize(); ++i) {
UResourceBundle b = localeb.get(i);
String id = b.getKey();
String value = b.getString().intern();
tempLocaleIdToOrdinalRulesId.put(id, value);
}
} catch (MissingResourceException e) { } catch (MissingResourceException e) {
// dummy so we don't try again // dummy so we don't try again
tempLocaleIdToRulesId = Collections.emptyMap(); tempLocaleIdToCardinalRulesId = Collections.emptyMap();
tempLocaleIdToOrdinalRulesId = Collections.emptyMap();
tempRulesIdToEquivalentULocale = Collections.emptyMap(); tempRulesIdToEquivalentULocale = Collections.emptyMap();
} }
synchronized(this) { synchronized(this) {
if (localeIdToRulesId == null) { if (localeIdToCardinalRulesId == null) {
localeIdToRulesId = tempLocaleIdToRulesId; localeIdToCardinalRulesId = tempLocaleIdToCardinalRulesId;
localeIdToOrdinalRulesId = tempLocaleIdToOrdinalRulesId;
rulesIdToEquivalentULocale = tempRulesIdToEquivalentULocale; rulesIdToEquivalentULocale = tempRulesIdToEquivalentULocale;
} }
} }
@ -143,8 +156,8 @@ public class PluralRulesLoader {
* rulesId, return null. The rulesId might be the empty string if the rule * rulesId, return null. The rulesId might be the empty string if the rule
* is the default rule. * is the default rule.
*/ */
public String getRulesIdForLocale(ULocale locale) { public String getRulesIdForLocale(ULocale locale, PluralType type) {
Map<String, String> idMap = getLocaleIdToRulesIdMap(); Map<String, String> idMap = getLocaleIdToRulesIdMap(type);
String localeId = ULocale.canonicalize(locale.getBaseName()); String localeId = ULocale.canonicalize(locale.getBaseName());
String rulesId = null; String rulesId = null;
while (null == (rulesId = idMap.get(localeId))) { while (null == (rulesId = idMap.get(localeId))) {
@ -216,8 +229,8 @@ public class PluralRulesLoader {
* Returns the plural rules for the the locale. If we don't have data, * Returns the plural rules for the the locale. If we don't have data,
* com.ibm.icu.text.PluralRules.DEFAULT is returned. * com.ibm.icu.text.PluralRules.DEFAULT is returned.
*/ */
public PluralRules forLocale(ULocale locale) { public PluralRules forLocale(ULocale locale, PluralRules.PluralType type) {
String rulesId = getRulesIdForLocale(locale); String rulesId = getRulesIdForLocale(locale, type);
if (rulesId == null || rulesId.trim().length() == 0) { if (rulesId == null || rulesId.trim().length() == 0) {
return PluralRules.DEFAULT; return PluralRules.DEFAULT;
} }

View File

@ -36,6 +36,8 @@ import com.ibm.icu.impl.PatternProps;
import com.ibm.icu.impl.Utility; import com.ibm.icu.impl.Utility;
import com.ibm.icu.text.MessagePattern.ArgType; import com.ibm.icu.text.MessagePattern.ArgType;
import com.ibm.icu.text.MessagePattern.Part; import com.ibm.icu.text.MessagePattern.Part;
import com.ibm.icu.text.PluralFormat.PluralSelector;
import com.ibm.icu.text.PluralRules.PluralType;
import com.ibm.icu.util.ULocale; import com.ibm.icu.util.ULocale;
import com.ibm.icu.util.ULocale.Category; import com.ibm.icu.util.ULocale.Category;
@ -99,13 +101,14 @@ import com.ibm.icu.util.ULocale.Category;
* <blockquote><pre> * <blockquote><pre>
* message = messageText (argument messageText)* * message = messageText (argument messageText)*
* argument = noneArg | simpleArg | complexArg * argument = noneArg | simpleArg | complexArg
* complexArg = choiceArg | pluralArg | selectArg * complexArg = choiceArg | pluralArg | selectArg | selectordinalArg
* *
* noneArg = '{' argNameOrNumber '}' * noneArg = '{' argNameOrNumber '}'
* simpleArg = '{' argNameOrNumber ',' argType [',' argStyle] '}' * simpleArg = '{' argNameOrNumber ',' argType [',' argStyle] '}'
* choiceArg = '{' argNameOrNumber ',' "choice" ',' choiceStyle '}' * choiceArg = '{' argNameOrNumber ',' "choice" ',' choiceStyle '}'
* pluralArg = '{' argNameOrNumber ',' "plural" ',' pluralStyle '}' * pluralArg = '{' argNameOrNumber ',' "plural" ',' pluralStyle '}'
* selectArg = '{' argNameOrNumber ',' "select" ',' selectStyle '}' * selectArg = '{' argNameOrNumber ',' "select" ',' selectStyle '}'
* selectordinalArg = '{' argNameOrNumber ',' "selectordinal" ',' pluralStyle '}'
* *
* choiceStyle: see {@link ChoiceFormat} * choiceStyle: see {@link ChoiceFormat}
* pluralStyle: see {@link PluralFormat} * pluralStyle: see {@link PluralFormat}
@ -408,6 +411,7 @@ public class MessageFormat extends UFormat {
// the locale has changed. // the locale has changed.
stockNumberFormatter = stockDateFormatter = null; stockNumberFormatter = stockDateFormatter = null;
pluralProvider = null; pluralProvider = null;
ordinalProvider = null;
applyPattern(existingPattern); /*ibm.3550*/ applyPattern(existingPattern); /*ibm.3550*/
} }
@ -1327,11 +1331,10 @@ public class MessageFormat extends UFormat {
argResult = choiceResult; argResult = choiceResult;
haveArgResult = true; haveArgResult = true;
sourceOffset = tempStatus.getIndex(); sourceOffset = tempStatus.getIndex();
} else if(argType==ArgType.PLURAL || argType==ArgType.SELECT) { } else if(argType.hasPluralStyle() || argType==ArgType.SELECT) {
// No can do! // No can do!
throw new UnsupportedOperationException(argType==ArgType.PLURAL ? throw new UnsupportedOperationException(
"Parsing of PluralFormat is not supported." : "Parsing of plural/select/selectordinal argument is not supported.");
"Parsing of SelectFormat is not supported.");
} else { } else {
// This should never happen. // This should never happen.
throw new IllegalStateException("unexpected argType "+argType); throw new IllegalStateException("unexpected argType "+argType);
@ -1441,6 +1444,7 @@ public class MessageFormat extends UFormat {
other.stockNumberFormatter = stockNumberFormatter == null ? null : (Format) stockNumberFormatter.clone(); other.stockNumberFormatter = stockNumberFormatter == null ? null : (Format) stockNumberFormatter.clone();
other.pluralProvider = null; other.pluralProvider = null;
other.ordinalProvider = null;
return other; return other;
} }
@ -1560,6 +1564,7 @@ public class MessageFormat extends UFormat {
private transient Format stockNumberFormatter; private transient Format stockNumberFormatter;
private transient PluralSelectorProvider pluralProvider; private transient PluralSelectorProvider pluralProvider;
private transient PluralSelectorProvider ordinalProvider;
// *Important*: All fields must be declared *transient*. // *Important*: All fields must be declared *transient*.
// See the longer comment above ulocale. // See the longer comment above ulocale.
@ -1697,15 +1702,24 @@ public class MessageFormat extends UFormat {
double number = ((Number)arg).doubleValue(); double number = ((Number)arg).doubleValue();
int subMsgStart=findChoiceSubMessage(msgPattern, i, number); int subMsgStart=findChoiceSubMessage(msgPattern, i, number);
formatComplexSubMessage(subMsgStart, 0, args, argsMap, dest); formatComplexSubMessage(subMsgStart, 0, args, argsMap, dest);
} else if(argType==ArgType.PLURAL) { } else if(argType.hasPluralStyle()) {
if (!(arg instanceof Number)) { if (!(arg instanceof Number)) {
throw new IllegalArgumentException("'" + arg + "' is not a Number"); throw new IllegalArgumentException("'" + arg + "' is not a Number");
} }
double number = ((Number)arg).doubleValue(); double number = ((Number)arg).doubleValue();
if (pluralProvider == null) { PluralSelector selector;
pluralProvider = new PluralSelectorProvider(ulocale); if(argType == ArgType.PLURAL) {
if (pluralProvider == null) {
pluralProvider = new PluralSelectorProvider(ulocale, PluralType.CARDINAL);
}
selector = pluralProvider;
} else {
if (ordinalProvider == null) {
ordinalProvider = new PluralSelectorProvider(ulocale, PluralType.ORDINAL);
}
selector = ordinalProvider;
} }
int subMsgStart=PluralFormat.findSubMessage(msgPattern, i, pluralProvider, number); int subMsgStart=PluralFormat.findSubMessage(msgPattern, i, selector, number);
double offset=msgPattern.getPluralOffset(i); double offset=msgPattern.getPluralOffset(i);
formatComplexSubMessage(subMsgStart, number-offset, args, argsMap, dest); formatComplexSubMessage(subMsgStart, number-offset, args, argsMap, dest);
} else if(argType==ArgType.SELECT) { } else if(argType==ArgType.SELECT) {
@ -1940,17 +1954,19 @@ public class MessageFormat extends UFormat {
* we do not need any PluralRules. * we do not need any PluralRules.
*/ */
private static final class PluralSelectorProvider implements PluralFormat.PluralSelector { private static final class PluralSelectorProvider implements PluralFormat.PluralSelector {
public PluralSelectorProvider(ULocale loc) { public PluralSelectorProvider(ULocale loc, PluralType type) {
locale=loc; locale=loc;
this.type=type;
} }
public String select(double number) { public String select(double number) {
if(rules == null) { if(rules == null) {
rules = PluralRules.forLocale(locale); rules = PluralRules.forLocale(locale, type);
} }
return rules.select(number); return rules.select(number);
} }
private ULocale locale; private ULocale locale;
private PluralRules rules; private PluralRules rules;
private PluralType type;
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")

View File

@ -10,6 +10,7 @@
package com.ibm.icu.text; package com.ibm.icu.text;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Locale;
import com.ibm.icu.impl.ICUConfig; import com.ibm.icu.impl.ICUConfig;
import com.ibm.icu.impl.PatternProps; import com.ibm.icu.impl.PatternProps;
@ -819,7 +820,7 @@ public final class MessagePattern implements Cloneable, Freezable<MessagePattern
*/ */
CHOICE, CHOICE,
/** /**
* The argument is a PluralFormat with an optional ARG_INT or ARG_DOUBLE offset * The argument is a cardinal-number PluralFormat with an optional ARG_INT or ARG_DOUBLE offset
* (e.g., offset:1) * (e.g., offset:1)
* and one or more (ARG_SELECTOR [explicit-value] message) tuples. * and one or more (ARG_SELECTOR [explicit-value] message) tuples.
* If the selector has an explicit value (e.g., =2), then * If the selector has an explicit value (e.g., =2), then
@ -832,7 +833,24 @@ public final class MessagePattern implements Cloneable, Freezable<MessagePattern
* The argument is a SelectFormat with one or more (ARG_SELECTOR, message) pairs. * The argument is a SelectFormat with one or more (ARG_SELECTOR, message) pairs.
* @stable ICU 4.8 * @stable ICU 4.8
*/ */
SELECT SELECT,
/**
* The argument is an ordinal-number PluralFormat
* with the same style parts sequence and semantics as {@link ArgType#PLURAL}.
* @draft ICU 50
* @provisional This API might change or be removed in a future release.
*/
SELECTORDINAL;
/**
* @return true if the argument type has a plural style part sequence and semantics,
* for example {@link ArgType#PLURAL} and {@link ArgType#SELECTORDINAL}.
* @draft ICU 50
* @provisional This API might change or be removed in a future release.
*/
public boolean hasPluralStyle() {
return this == PLURAL || this == SELECTORDINAL;
}
} }
/** /**
@ -931,7 +949,7 @@ public final class MessagePattern implements Cloneable, Freezable<MessagePattern
aposMode==ApostropheMode.DOUBLE_REQUIRED || aposMode==ApostropheMode.DOUBLE_REQUIRED ||
c=='{' || c=='}' || c=='{' || c=='}' ||
(parentType==ArgType.CHOICE && c=='|') || (parentType==ArgType.CHOICE && c=='|') ||
(parentType==ArgType.PLURAL && c=='#') (parentType.hasPluralStyle() && c=='#')
) { ) {
// skip the quote-starting apostrophe // skip the quote-starting apostrophe
addPart(Part.Type.SKIP_SYNTAX, index-1, 1, 0); addPart(Part.Type.SKIP_SYNTAX, index-1, 1, 0);
@ -964,7 +982,7 @@ public final class MessagePattern implements Cloneable, Freezable<MessagePattern
needsAutoQuoting=true; needsAutoQuoting=true;
} }
} }
} else if(parentType==ArgType.PLURAL && c=='#') { } else if(parentType.hasPluralStyle() && c=='#') {
// The unquoted # in a plural message fragment will be replaced // The unquoted # in a plural message fragment will be replaced
// with the (number-offset). // with the (number-offset).
addPart(Part.Type.REPLACE_NUMBER, index-1, 1, 0); addPart(Part.Type.REPLACE_NUMBER, index-1, 1, 0);
@ -1063,6 +1081,10 @@ public final class MessagePattern implements Cloneable, Freezable<MessagePattern
} else if(isSelect(typeIndex)) { } else if(isSelect(typeIndex)) {
argType=ArgType.SELECT; argType=ArgType.SELECT;
} }
} else if(length==13) {
if(isSelect(typeIndex) && isOrdinal(typeIndex+6)) {
argType=ArgType.SELECTORDINAL;
}
} }
// change the ARG_START type from NONE to argType // change the ARG_START type from NONE to argType
parts.get(argStart).value=(short)argType.ordinal(); parts.get(argStart).value=(short)argType.ordinal();
@ -1191,26 +1213,26 @@ public final class MessagePattern implements Cloneable, Freezable<MessagePattern
if(eos==inMessageFormatPattern(nestingLevel)) { if(eos==inMessageFormatPattern(nestingLevel)) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"Bad "+ "Bad "+
(argType==ArgType.PLURAL ? "plural" : "select")+ argType.toString().toLowerCase(Locale.ROOT)+
" pattern syntax: "+prefix(start)); " pattern syntax: "+prefix(start));
} }
if(!hasOther) { if(!hasOther) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"Missing 'other' keyword in "+ "Missing 'other' keyword in "+
(argType==ArgType.PLURAL ? "plural" : "select")+ argType.toString().toLowerCase(Locale.ROOT)+
" pattern in \""+prefix()+"\""); " pattern in \""+prefix()+"\"");
} }
return index; return index;
} }
int selectorIndex=index; int selectorIndex=index;
if(argType==ArgType.PLURAL && msg.charAt(selectorIndex)=='=') { if(argType.hasPluralStyle() && msg.charAt(selectorIndex)=='=') {
// explicit-value plural selector: =double // explicit-value plural selector: =double
index=skipDouble(index+1); index=skipDouble(index+1);
int length=index-selectorIndex; int length=index-selectorIndex;
if(length==1) { if(length==1) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"Bad "+ "Bad "+
(argType==ArgType.PLURAL ? "plural" : "select")+ argType.toString().toLowerCase(Locale.ROOT)+
" pattern syntax: "+prefix(start)); " pattern syntax: "+prefix(start));
} }
if(length>Part.MAX_LENGTH) { if(length>Part.MAX_LENGTH) {
@ -1225,11 +1247,11 @@ public final class MessagePattern implements Cloneable, Freezable<MessagePattern
if(length==0) { if(length==0) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"Bad "+ "Bad "+
(argType==ArgType.PLURAL ? "plural" : "select")+ argType.toString().toLowerCase(Locale.ROOT)+
" pattern syntax: "+prefix(start)); " pattern syntax: "+prefix(start));
} }
// Note: The ':' in "offset:" is just beyond the skipIdentifier() range. // Note: The ':' in "offset:" is just beyond the skipIdentifier() range.
if( argType==ArgType.PLURAL && length==6 && index<msg.length() && if( argType.hasPluralStyle() && length==6 && index<msg.length() &&
msg.regionMatches(selectorIndex, "offset:", 0, 7) msg.regionMatches(selectorIndex, "offset:", 0, 7)
) { ) {
// plural offset, not a selector // plural offset, not a selector
@ -1270,7 +1292,7 @@ public final class MessagePattern implements Cloneable, Freezable<MessagePattern
if(index==msg.length() || msg.charAt(index)!='{') { if(index==msg.length() || msg.charAt(index)!='{') {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"No message fragment after "+ "No message fragment after "+
(argType==ArgType.PLURAL ? "plural" : "select")+ argType.toString().toLowerCase(Locale.ROOT)+
" selector: "+prefix(selectorIndex)); " selector: "+prefix(selectorIndex));
} }
index=parseMessage(index, 1, nestingLevel+1, argType); index=parseMessage(index, 1, nestingLevel+1, argType);
@ -1479,6 +1501,18 @@ public final class MessagePattern implements Cloneable, Freezable<MessagePattern
((c=msg.charAt(index))=='t' || c=='T'); ((c=msg.charAt(index))=='t' || c=='T');
} }
private boolean isOrdinal(int index) {
char c;
return
((c=msg.charAt(index++))=='o' || c=='O') &&
((c=msg.charAt(index++))=='r' || c=='R') &&
((c=msg.charAt(index++))=='d' || c=='D') &&
((c=msg.charAt(index++))=='i' || c=='I') &&
((c=msg.charAt(index++))=='n' || c=='N') &&
((c=msg.charAt(index++))=='a' || c=='A') &&
((c=msg.charAt(index))=='l' || c=='L');
}
/** /**
* @return true if we are inside a MessageFormat (sub-)pattern, * @return true if we are inside a MessageFormat (sub-)pattern,
* as opposed to inside a top-level choice/plural/select pattern. * as opposed to inside a top-level choice/plural/select pattern.

View File

@ -1,6 +1,6 @@
/* /*
******************************************************************************* *******************************************************************************
* Copyright (C) 2011, International Business Machines * Copyright (C) 2011-2012, International Business Machines
* Corporation and others. All Rights Reserved. * Corporation and others. All Rights Reserved.
******************************************************************************* *******************************************************************************
* created on: 2011jul14 * created on: 2011jul14
@ -547,12 +547,16 @@ public final class MessagePatternUtil {
break; break;
case PLURAL: case PLURAL:
node.typeName = "plural"; node.typeName = "plural";
node.complexStyle = buildPluralStyleNode(pattern, start, limit); node.complexStyle = buildPluralStyleNode(pattern, start, limit, argType);
break; break;
case SELECT: case SELECT:
node.typeName = "select"; node.typeName = "select";
node.complexStyle = buildSelectStyleNode(pattern, start, limit); node.complexStyle = buildSelectStyleNode(pattern, start, limit);
break; break;
case SELECTORDINAL:
node.typeName = "selectordinal";
node.complexStyle = buildPluralStyleNode(pattern, start, limit, argType);
break;
default: default:
// NONE type, nothing else to do // NONE type, nothing else to do
break; break;
@ -580,8 +584,9 @@ public final class MessagePatternUtil {
} }
private static ComplexArgStyleNode buildPluralStyleNode(MessagePattern pattern, private static ComplexArgStyleNode buildPluralStyleNode(MessagePattern pattern,
int start, int limit) { int start, int limit,
ComplexArgStyleNode node = new ComplexArgStyleNode(MessagePattern.ArgType.PLURAL); MessagePattern.ArgType argType) {
ComplexArgStyleNode node = new ComplexArgStyleNode(argType);
MessagePattern.Part offset = pattern.getPart(start); MessagePattern.Part offset = pattern.getPart(start);
if (offset.getType().hasNumericValue()) { if (offset.getType().hasNumericValue()) {
node.explicitOffset = true; node.explicitOffset = true;

View File

@ -1,7 +1,7 @@
/* /*
******************************************************************************* *******************************************************************************
* Copyright (C) 2007-2011, International Business Machines Corporation and * * Copyright (C) 2007-2012, International Business Machines Corporation and
* others. All Rights Reserved. * * others. All Rights Reserved.
******************************************************************************* *******************************************************************************
*/ */
@ -14,6 +14,7 @@ import java.text.ParsePosition;
import java.util.Map; import java.util.Map;
import com.ibm.icu.impl.Utility; import com.ibm.icu.impl.Utility;
import com.ibm.icu.text.PluralRules.PluralType;
import com.ibm.icu.util.ULocale; import com.ibm.icu.util.ULocale;
import com.ibm.icu.util.ULocale.Category; import com.ibm.icu.util.ULocale.Category;
@ -176,29 +177,29 @@ public class PluralFormat extends UFormat {
transient private double offset = 0; transient private double offset = 0;
/** /**
* Creates a new <code>PluralFormat</code> for the default <code>FORMAT</code> locale. * Creates a new cardinal-number <code>PluralFormat</code> for the default <code>FORMAT</code> locale.
* This locale will be used to get the set of plural rules and for standard * This locale will be used to get the set of plural rules and for standard
* number formatting. * number formatting.
* @see Category#FORMAT * @see Category#FORMAT
* @stable ICU 3.8 * @stable ICU 3.8
*/ */
public PluralFormat() { public PluralFormat() {
init(null, ULocale.getDefault(Category.FORMAT)); init(null, PluralType.CARDINAL, ULocale.getDefault(Category.FORMAT));
} }
/** /**
* Creates a new <code>PluralFormat</code> for a given locale. * Creates a new cardinal-number <code>PluralFormat</code> for a given locale.
* @param ulocale the <code>PluralFormat</code> will be configured with * @param ulocale the <code>PluralFormat</code> will be configured with
* rules for this locale. This locale will also be used for standard * rules for this locale. This locale will also be used for standard
* number formatting. * number formatting.
* @stable ICU 3.8 * @stable ICU 3.8
*/ */
public PluralFormat(ULocale ulocale) { public PluralFormat(ULocale ulocale) {
init(null, ulocale); init(null, PluralType.CARDINAL, ulocale);
} }
/** /**
* Creates a new <code>PluralFormat</code> for a given set of rules. * Creates a new cardinal-number <code>PluralFormat</code> for a given set of rules.
* The standard number formatting will be done using the default <code>FORMAT</code> locale. * The standard number formatting will be done using the default <code>FORMAT</code> locale.
* @param rules defines the behavior of the <code>PluralFormat</code> * @param rules defines the behavior of the <code>PluralFormat</code>
* object. * object.
@ -206,11 +207,11 @@ public class PluralFormat extends UFormat {
* @stable ICU 3.8 * @stable ICU 3.8
*/ */
public PluralFormat(PluralRules rules) { public PluralFormat(PluralRules rules) {
init(rules, ULocale.getDefault(Category.FORMAT)); init(rules, PluralType.CARDINAL, ULocale.getDefault(Category.FORMAT));
} }
/** /**
* Creates a new <code>PluralFormat</code> for a given set of rules. * Creates a new cardinal-number <code>PluralFormat</code> for a given set of rules.
* The standard number formatting will be done using the given locale. * The standard number formatting will be done using the given locale.
* @param ulocale the default number formatting will be done using this * @param ulocale the default number formatting will be done using this
* locale. * locale.
@ -219,11 +220,24 @@ public class PluralFormat extends UFormat {
* @stable ICU 3.8 * @stable ICU 3.8
*/ */
public PluralFormat(ULocale ulocale, PluralRules rules) { public PluralFormat(ULocale ulocale, PluralRules rules) {
init(rules, ulocale); init(rules, PluralType.CARDINAL, ulocale);
} }
/** /**
* Creates a new <code>PluralFormat</code> for a given pattern string. * Creates a new <code>PluralFormat</code> for the plural type.
* The standard number formatting will be done using the given locale.
* @param ulocale the default number formatting will be done using this
* locale.
* @param type The plural type (e.g., cardinal or ordinal).
* @draft ICU 50
* @provisional This API might change or be removed in a future release.
*/
public PluralFormat(ULocale ulocale, PluralType type) {
init(null, type, ulocale);
}
/**
* Creates a new cardinal-number <code>PluralFormat</code> for a given pattern string.
* The default <code>FORMAT</code> locale will be used to get the set of plural rules and for * The default <code>FORMAT</code> locale will be used to get the set of plural rules and for
* standard number formatting. * standard number formatting.
* @param pattern the pattern for this <code>PluralFormat</code>. * @param pattern the pattern for this <code>PluralFormat</code>.
@ -232,12 +246,12 @@ public class PluralFormat extends UFormat {
* @stable ICU 3.8 * @stable ICU 3.8
*/ */
public PluralFormat(String pattern) { public PluralFormat(String pattern) {
init(null, ULocale.getDefault(Category.FORMAT)); init(null, PluralType.CARDINAL, ULocale.getDefault(Category.FORMAT));
applyPattern(pattern); applyPattern(pattern);
} }
/** /**
* Creates a new <code>PluralFormat</code> for a given pattern string and * Creates a new cardinal-number <code>PluralFormat</code> for a given pattern string and
* locale. * locale.
* The locale will be used to get the set of plural rules and for * The locale will be used to get the set of plural rules and for
* standard number formatting. * standard number formatting.
@ -249,12 +263,12 @@ public class PluralFormat extends UFormat {
* @stable ICU 3.8 * @stable ICU 3.8
*/ */
public PluralFormat(ULocale ulocale, String pattern) { public PluralFormat(ULocale ulocale, String pattern) {
init(null, ulocale); init(null, PluralType.CARDINAL, ulocale);
applyPattern(pattern); applyPattern(pattern);
} }
/** /**
* Creates a new <code>PluralFormat</code> for a given set of rules and a * Creates a new cardinal-number <code>PluralFormat</code> for a given set of rules and a
* pattern. * pattern.
* The standard number formatting will be done using the default <code>FORMAT</code> locale. * The standard number formatting will be done using the default <code>FORMAT</code> locale.
* @param rules defines the behavior of the <code>PluralFormat</code> * @param rules defines the behavior of the <code>PluralFormat</code>
@ -265,12 +279,12 @@ public class PluralFormat extends UFormat {
* @stable ICU 3.8 * @stable ICU 3.8
*/ */
public PluralFormat(PluralRules rules, String pattern) { public PluralFormat(PluralRules rules, String pattern) {
init(rules, ULocale.getDefault(Category.FORMAT)); init(rules, PluralType.CARDINAL, ULocale.getDefault(Category.FORMAT));
applyPattern(pattern); applyPattern(pattern);
} }
/** /**
* Creates a new <code>PluralFormat</code> for a given set of rules, a * Creates a new cardinal-number <code>PluralFormat</code> for a given set of rules, a
* pattern and a locale. * pattern and a locale.
* @param ulocale the <code>PluralFormat</code> will be configured with * @param ulocale the <code>PluralFormat</code> will be configured with
* rules for this locale. This locale will also be used for standard * rules for this locale. This locale will also be used for standard
@ -282,7 +296,24 @@ public class PluralFormat extends UFormat {
* @stable ICU 3.8 * @stable ICU 3.8
*/ */
public PluralFormat(ULocale ulocale, PluralRules rules, String pattern) { public PluralFormat(ULocale ulocale, PluralRules rules, String pattern) {
init(rules, ulocale); init(rules, PluralType.CARDINAL, ulocale);
applyPattern(pattern);
}
/**
* Creates a new <code>PluralFormat</code> for a plural type, a
* pattern and a locale.
* @param ulocale the <code>PluralFormat</code> will be configured with
* rules for this locale. This locale will also be used for standard
* number formatting.
* @param type The plural type (e.g., cardinal or ordinal).
* @param pattern the pattern for this <code>PluralFormat</code>.
* @throws IllegalArgumentException if the pattern is invalid.
* @draft ICU 50
* @provisional This API might change or be removed in a future release.
*/
public PluralFormat(ULocale ulocale, PluralType type, String pattern) {
init(null, type, ulocale);
applyPattern(pattern); applyPattern(pattern);
} }
@ -299,9 +330,9 @@ public class PluralFormat extends UFormat {
* <code>numberFormat</code>: a <code>NumberFormat</code> for the locale * <code>numberFormat</code>: a <code>NumberFormat</code> for the locale
* <code>ulocale</code>. * <code>ulocale</code>.
*/ */
private void init(PluralRules rules, ULocale locale) { private void init(PluralRules rules, PluralType type, ULocale locale) {
ulocale = locale; ulocale = locale;
pluralRules = (rules == null) ? PluralRules.forLocale(ulocale) pluralRules = (rules == null) ? PluralRules.forLocale(ulocale, type)
: rules; : rules;
resetPattern(); resetPattern();
numberFormat = NumberFormat.getInstance(ulocale); numberFormat = NumberFormat.getInstance(ulocale);
@ -590,7 +621,8 @@ public class PluralFormat extends UFormat {
* i.e., a pattern that was applied previously will be removed, * i.e., a pattern that was applied previously will be removed,
* and the NumberFormat is set to the default number format for * and the NumberFormat is set to the default number format for
* the locale. The resulting format behaves the same as one * the locale. The resulting format behaves the same as one
* constructed from {@link #PluralFormat(ULocale)}. * constructed from {@link #PluralFormat(ULocale, PluralType)}
* with PluralType.CARDINAL.
* @param ulocale the <code>ULocale</code> used to configure the * @param ulocale the <code>ULocale</code> used to configure the
* formatter. If <code>ulocale</code> is <code>null</code>, the * formatter. If <code>ulocale</code> is <code>null</code>, the
* default <code>FORMAT</code> locale will be used. * default <code>FORMAT</code> locale will be used.
@ -601,7 +633,7 @@ public class PluralFormat extends UFormat {
if (ulocale == null) { if (ulocale == null) {
ulocale = ULocale.getDefault(Category.FORMAT); ulocale = ULocale.getDefault(Category.FORMAT);
} }
init(null, ulocale); init(null, PluralType.CARDINAL, ulocale);
} }
/** /**

View File

@ -148,6 +148,26 @@ public class PluralRules implements Serializable {
*/ */
public static final double NO_UNIQUE_VALUE = -0.00123456777; public static final double NO_UNIQUE_VALUE = -0.00123456777;
/**
* Type of plurals and PluralRules.
* @draft ICU 50
* @provisional This API might change or be removed in a future release.
*/
public enum PluralType {
/**
* Plural rules for cardinal numbers: 1 file vs. 2 files.
* @draft ICU 50
* @provisional This API might change or be removed in a future release.
*/
CARDINAL,
/**
* Plural rules for ordinal numbers: 1st file, 2nd file, 3rd file, 4th file, etc.
* @draft ICU 50
* @provisional This API might change or be removed in a future release.
*/
ORDINAL
};
/* /*
* The default constraint that is always satisfied. * The default constraint that is always satisfied.
*/ */
@ -798,9 +818,11 @@ public class PluralRules implements Serializable {
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
/** /**
* Provides access to the predefined <code>PluralRules</code> for a given * Provides access to the predefined cardinal-number <code>PluralRules</code> for a given
* locale. * locale.
* ICU defines plural rules for many locales based on CLDR <i>Language Plural Rules</i>. * Same as forLocale(locale, PluralType.CARDINAL).
*
* <p>ICU defines plural rules for many locales based on CLDR <i>Language Plural Rules</i>.
* For these predefined rules, see CLDR page at * For these predefined rules, see CLDR page at
* http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html * http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
* *
@ -814,7 +836,30 @@ public class PluralRules implements Serializable {
* @stable ICU 3.8 * @stable ICU 3.8
*/ */
public static PluralRules forLocale(ULocale locale) { public static PluralRules forLocale(ULocale locale) {
return PluralRulesLoader.loader.forLocale(locale); return PluralRulesLoader.loader.forLocale(locale, PluralType.CARDINAL);
}
/**
* Provides access to the predefined <code>PluralRules</code> for a given
* locale and the plural type.
*
* <p>ICU defines plural rules for many locales based on CLDR <i>Language Plural Rules</i>.
* For these predefined rules, see CLDR page at
* http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
*
* @param locale The locale for which a <code>PluralRules</code> object is
* returned.
* @param type The plural type (e.g., cardinal or ordinal).
* @return The predefined <code>PluralRules</code> object for this locale.
* If there's no predefined rules for this locale, the rules
* for the closest parent in the locale hierarchy that has one will
* be returned. The final fallback always returns the default
* rules.
* @draft ICU 50
* @provisional This API might change or be removed in a future release.
*/
public static PluralRules forLocale(ULocale locale, PluralType type) {
return PluralRulesLoader.loader.forLocale(locale, type);
} }
/* /*

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:aa91eade60c4302ef379eab96b02b98f08781bcc728c6059616545c63b83ba2a oid sha256:d933870a3235e23ce8bc66928d928bc0a0fb28d510b17f52eb74d7a99949bc92
size 7927279 size 7927279

View File

@ -1,6 +1,6 @@
/* /*
******************************************************************************* *******************************************************************************
* Copyright (C) 2011, International Business Machines * Copyright (C) 2011-2012, International Business Machines
* Corporation and others. All Rights Reserved. * Corporation and others. All Rights Reserved.
******************************************************************************* *******************************************************************************
* created on: 2011aug12 * created on: 2011aug12
@ -12,6 +12,7 @@ package com.ibm.icu.dev.test.format;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Locale;
import com.ibm.icu.text.MessagePattern; import com.ibm.icu.text.MessagePattern;
import com.ibm.icu.text.MessagePatternUtil; import com.ibm.icu.text.MessagePatternUtil;
@ -65,6 +66,9 @@ public final class MessagePatternUtilTest extends com.ibm.icu.dev.test.TestFmwk
private ExpectComplexArgNode expectSelectArg(Object name) { private ExpectComplexArgNode expectSelectArg(Object name) {
return expectComplexArg(name, MessagePattern.ArgType.SELECT); return expectComplexArg(name, MessagePattern.ArgType.SELECT);
} }
private ExpectComplexArgNode expectSelectOrdinalArg(Object name) {
return expectComplexArg(name, MessagePattern.ArgType.SELECTORDINAL);
}
private ExpectComplexArgNode expectComplexArg(Object name, MessagePattern.ArgType argType) { private ExpectComplexArgNode expectComplexArg(Object name, MessagePattern.ArgType argType) {
ExpectComplexArgNode complexArg = new ExpectComplexArgNode(this, name, argType); ExpectComplexArgNode complexArg = new ExpectComplexArgNode(this, name, argType);
contents.add(complexArg); contents.add(complexArg);
@ -180,9 +184,7 @@ public final class MessagePatternUtilTest extends com.ibm.icu.dev.test.TestFmwk
private class ExpectComplexArgNode extends ExpectArgNode { private class ExpectComplexArgNode extends ExpectArgNode {
private ExpectComplexArgNode(ExpectMessageNode parent, private ExpectComplexArgNode(ExpectMessageNode parent,
Object name, MessagePattern.ArgType argType) { Object name, MessagePattern.ArgType argType) {
super(name, super(name, argType.toString().toLowerCase(Locale.ROOT));
argType == MessagePattern.ArgType.CHOICE ? "choice" :
argType == MessagePattern.ArgType.PLURAL ? "plural" : "select");
this.argType = argType; this.argType = argType;
this.parent = parent; this.parent = parent;
} }
@ -394,6 +396,22 @@ public final class MessagePatternUtilTest extends com.ibm.icu.dev.test.TestFmwk
expect.checkMatches(msg); expect.checkMatches(msg);
} }
public void TestSelectOrdinalArg() {
MessageNode msg = MessagePatternUtil.buildMessageNode(
"abc{num, selectordinal, offset:17 =0{null} few{fff} other {oooo}}xyz");
ExpectMessageNode expect = new ExpectMessageNode().
expectTextThatContains("abc").
expectSelectOrdinalArg("num").
expectOffset(17).
expectVariant("=0", 0).expectTextThatContains("null").finishVariant().
expectVariant("few").expectTextThatContains("fff").finishVariant().
expectVariant("other").expectTextThatContains("oooo").finishVariant().
finishComplexArg().
expectTextThatContains("xyz");
expect.checkMatches(msg);
}
public void TestChoiceArg() { public void TestChoiceArg() {
MessageNode msg = MessagePatternUtil.buildMessageNode( MessageNode msg = MessagePatternUtil.buildMessageNode(
"a_{0,choice,-∞ #-inf| 5≤ five | 99 # ninety'|'nine }_z"); "a_{0,choice,-∞ #-inf| 5≤ five | 99 # ninety'|'nine }_z");

View File

@ -1,7 +1,7 @@
/* /*
******************************************************************************* *******************************************************************************
* Copyright (C) 2007-2011, International Business Machines Corporation and * * Copyright (C) 2007-2012, International Business Machines Corporation and
* others. All Rights Reserved. * * others. All Rights Reserved.
******************************************************************************* *******************************************************************************
*/ */
@ -19,6 +19,7 @@ import com.ibm.icu.text.MessageFormat;
import com.ibm.icu.text.NumberFormat; import com.ibm.icu.text.NumberFormat;
import com.ibm.icu.text.PluralFormat; import com.ibm.icu.text.PluralFormat;
import com.ibm.icu.text.PluralRules; import com.ibm.icu.text.PluralRules;
import com.ibm.icu.text.PluralRules.PluralType;
import com.ibm.icu.util.ULocale; import com.ibm.icu.util.ULocale;
/** /**
@ -324,4 +325,18 @@ public class PluralFormatUnitTest extends TestFmwk {
} }
} }
} }
public void TestOrdinalFormat() {
String pattern = "one{#st file}two{#nd file}few{#rd file}other{#th file}";
PluralFormat pf = new PluralFormat(ULocale.ENGLISH, PluralType.ORDINAL, pattern);
assertEquals("PluralFormat.format(321)", "321st file", pf.format(321));
assertEquals("PluralFormat.format(22)", "22nd file", pf.format(22));
assertEquals("PluralFormat.format(3)", "3rd file", pf.format(3));
// Code coverage: Use the other new-for-PluralType constructor as well.
pf = new PluralFormat(ULocale.ENGLISH, PluralType.ORDINAL);
pf.applyPattern(pattern);
assertEquals("PluralFormat.format(456)", "456th file", pf.format(456));
assertEquals("PluralFormat.format(111)", "111th file", pf.format(111));
}
} }

View File

@ -1,7 +1,7 @@
/* /*
******************************************************************************* *******************************************************************************
* Copyright (C) 2007-2011, International Business Machines Corporation and * * Copyright (C) 2007-2012, International Business Machines Corporation and
* others. All Rights Reserved. * * others. All Rights Reserved.
******************************************************************************* *******************************************************************************
*/ */
package com.ibm.icu.dev.test.format; package com.ibm.icu.dev.test.format;
@ -18,6 +18,7 @@ import java.util.Set;
import com.ibm.icu.dev.test.TestFmwk; import com.ibm.icu.dev.test.TestFmwk;
import com.ibm.icu.impl.Utility; import com.ibm.icu.impl.Utility;
import com.ibm.icu.text.PluralRules; import com.ibm.icu.text.PluralRules;
import com.ibm.icu.text.PluralRules.PluralType;
import com.ibm.icu.util.ULocale; import com.ibm.icu.util.ULocale;
/** /**
@ -386,4 +387,9 @@ public class PluralRulesTest extends TestFmwk {
} }
} }
} }
public void TestOrdinal() {
PluralRules pr = PluralRules.forLocale(ULocale.ENGLISH, PluralType.ORDINAL);
assertEquals("PluralRules(en-ordinal).select(2)", "two", pr.select(2));
}
} }

View File

@ -1,6 +1,6 @@
/* /*
********************************************************************** **********************************************************************
* Copyright (c) 2004-2011, International Business Machines * Copyright (c) 2004-2012, International Business Machines
* Corporation and others. All Rights Reserved. * Corporation and others. All Rights Reserved.
********************************************************************** **********************************************************************
* Author: Alan Liu * Author: Alan Liu
@ -1832,4 +1832,33 @@ public class TestMessageFormat extends com.ibm.icu.dev.test.TestFmwk {
assertEquals("trim-named-arg format() failed", "x 3 y", assertEquals("trim-named-arg format() failed", "x 3 y",
m.format(map, result, new FieldPosition(0)).toString()); m.format(map, result, new FieldPosition(0)).toString());
} }
public void TestSelectOrdinal() {
// Test plural & ordinal together,
// to make sure that we get the correct cached PluralSelector for each.
MessageFormat m = new MessageFormat(
"{0,plural,one{1 file}other{# files}}, " +
"{0,selectordinal,one{#st file}two{#nd file}few{#rd file}other{#th file}}",
ULocale.ENGLISH);
Object[] args = new Object[] { 21 };
FieldPosition ignore = null;
StringBuffer result = new StringBuffer();
assertEquals("plural-and-ordinal format(21)", "21 files, 21st file",
m.format(args, result, ignore).toString());
args[0] = 2;
result.delete(0, result.length());
assertEquals("plural-and-ordinal format(2) failed", "2 files, 2nd file",
m.format(args, result, ignore).toString());
args[0] = 1;
result.delete(0, result.length());
assertEquals("plural-and-ordinal format(1) failed", "1 file, 1st file",
m.format(args, result, ignore).toString());
args[0] = 3;
result.delete(0, result.length());
assertEquals("plural-and-ordinal format(3) failed", "3 files, 3rd file",
m.format(args, result, ignore).toString());
}
} }