ICU-10600 add plural ranges and unit formatting

X-SVN-Rev: 35997
This commit is contained in:
Mark Davis 2014-07-03 13:16:30 +00:00
parent 719e780320
commit adc2570f18
6 changed files with 879 additions and 94 deletions

View File

@ -1,6 +1,6 @@
/*
*******************************************************************************
* Copyright (C) 2008-2013, International Business Machines Corporation and *
* Copyright (C) 2008-2014, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
@ -10,13 +10,18 @@ import java.text.ParseException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import com.ibm.icu.text.NumberingSystem;
import com.ibm.icu.text.PluralRanges;
import com.ibm.icu.text.PluralRules;
import com.ibm.icu.text.PluralRules.PluralType;
import com.ibm.icu.text.PluralRules.StandardPluralCategories;
import com.ibm.icu.util.ULocale;
import com.ibm.icu.util.UResourceBundle;
@ -29,6 +34,8 @@ public class PluralRulesLoader extends PluralRules.Factory {
private Map<String, String> localeIdToCardinalRulesId;
private Map<String, String> localeIdToOrdinalRulesId;
private Map<String, ULocale> rulesIdToEquivalentULocale;
private static Map<String, PluralRanges> localeIdToPluralRanges;
/**
* Access through singleton.
@ -140,7 +147,7 @@ public class PluralRulesLoader extends PluralRules.Factory {
tempLocaleIdToOrdinalRulesId = Collections.emptyMap();
tempRulesIdToEquivalentULocale = Collections.emptyMap();
}
synchronized(this) {
if (localeIdToCardinalRulesId == null) {
localeIdToCardinalRulesId = tempLocaleIdToCardinalRulesId;
@ -253,4 +260,245 @@ public class PluralRulesLoader extends PluralRules.Factory {
public boolean hasOverride(ULocale locale) {
return false;
}
}
private static final PluralRanges UNKNOWN_RANGE = new PluralRanges().freeze();
public PluralRanges getPluralRanges(ULocale locale) {
// TODO markdavis Fix the bad fallback, here and elsewhere in this file.
String localeId = ULocale.canonicalize(locale.getBaseName());
PluralRanges result;
while (null == (result = localeIdToPluralRanges.get(localeId))) {
int ix = localeId.lastIndexOf("_");
if (ix == -1) {
result = UNKNOWN_RANGE;
break;
}
localeId = localeId.substring(0, ix);
}
return result;
}
public boolean isPluralRangesAvailable(ULocale locale) {
return getPluralRanges(locale) == UNKNOWN_RANGE;
}
// TODO markdavis FIX HARD-CODED HACK once we have data from CLDR in the bundles
static {
String[][] pluralRangeData = {
{"locales", "id ja km ko lo ms my th vi zh"},
{"other", "other", "other"},
{"locales", "am bn fr gu hi hy kn mr pa zu"},
{"one", "one", "one"},
{"one", "other", "other"},
{"other", "other", "other"},
{"locales", "fa"},
{"one", "one", "other"},
{"one", "other", "other"},
{"other", "other", "other"},
{"locales", "ka"},
{"one", "other", "one"},
{"other", "one", "other"},
{"other", "other", "other"},
{"locales", "az de el gl hu it kk ky ml mn ne nl pt sq sw ta te tr ug uz"},
{"one", "other", "other"},
{"other", "one", "one"},
{"other", "other", "other"},
{"locales", "af bg ca en es et eu fi nb sv ur"},
{"one", "other", "other"},
{"other", "one", "other"},
{"other", "other", "other"},
{"locales", "da fil is"},
{"one", "one", "one"},
{"one", "other", "other"},
{"other", "one", "one"},
{"other", "other", "other"},
{"locales", "si"},
{"one", "one", "one"},
{"one", "other", "other"},
{"other", "one", "other"},
{"other", "other", "other"},
{"locales", "mk"},
{"one", "one", "other"},
{"one", "other", "other"},
{"other", "one", "other"},
{"other", "other", "other"},
{"locales", "lv"},
{"zero", "zero", "other"},
{"zero", "one", "one"},
{"zero", "other", "other"},
{"one", "zero", "other"},
{"one", "one", "one"},
{"one", "other", "other"},
{"other", "zero", "other"},
{"other", "one", "one"},
{"other", "other", "other"},
{"locales", "ro"},
{"one", "few", "few"},
{"one", "other", "other"},
{"few", "one", "few"},
{"few", "few", "few"},
{"few", "other", "other"},
{"other", "few", "few"},
{"other", "other", "other"},
{"locales", "hr sr bs"},
{"one", "one", "one"},
{"one", "few", "few"},
{"one", "other", "other"},
{"few", "one", "one"},
{"few", "few", "few"},
{"few", "other", "other"},
{"other", "one", "one"},
{"other", "few", "few"},
{"other", "other", "other"},
{"locales", "sl"},
{"one", "one", "few"},
{"one", "two", "two"},
{"one", "few", "few"},
{"one", "other", "other"},
{"two", "one", "few"},
{"two", "two", "two"},
{"two", "few", "few"},
{"two", "other", "other"},
{"few", "one", "few"},
{"few", "two", "two"},
{"few", "few", "few"},
{"few", "other", "other"},
{"other", "one", "few"},
{"other", "two", "two"},
{"other", "few", "few"},
{"other", "other", "other"},
{"locales", "he"},
{"one", "two", "other"},
{"one", "many", "many"},
{"one", "other", "other"},
{"two", "many", "other"},
{"two", "other", "other"},
{"many", "many", "many"},
{"many", "other", "many"},
{"other", "one", "other"},
{"other", "two", "other"},
{"other", "many", "many"},
{"other", "other", "other"},
{"locales", "cs pl sk"},
{"one", "few", "few"},
{"one", "many", "many"},
{"one", "other", "other"},
{"few", "few", "few"},
{"few", "many", "many"},
{"few", "other", "other"},
{"many", "one", "one"},
{"many", "few", "few"},
{"many", "many", "many"},
{"many", "other", "other"},
{"other", "one", "one"},
{"other", "few", "few"},
{"other", "many", "many"},
{"other", "other", "other"},
{"locales", "lt ru uk"},
{"one", "one", "one"},
{"one", "few", "few"},
{"one", "many", "many"},
{"one", "other", "other"},
{"few", "one", "one"},
{"few", "few", "few"},
{"few", "many", "many"},
{"few", "other", "other"},
{"many", "one", "one"},
{"many", "few", "few"},
{"many", "many", "many"},
{"many", "other", "other"},
{"other", "one", "one"},
{"other", "few", "few"},
{"other", "many", "many"},
{"other", "other", "other"},
{"locales", "cy"},
{"zero", "one", "one"},
{"zero", "two", "two"},
{"zero", "few", "few"},
{"zero", "many", "many"},
{"zero", "other", "other"},
{"one", "two", "two"},
{"one", "few", "few"},
{"one", "many", "many"},
{"one", "other", "other"},
{"two", "few", "few"},
{"two", "many", "many"},
{"two", "other", "other"},
{"few", "many", "many"},
{"few", "other", "other"},
{"many", "other", "other"},
{"other", "one", "one"},
{"other", "two", "two"},
{"other", "few", "few"},
{"other", "many", "many"},
{"other", "other", "other"},
{"locales", "ar"},
{"zero", "one", "zero"},
{"zero", "two", "zero"},
{"zero", "few", "few"},
{"zero", "many", "many"},
{"zero", "other", "other"},
{"one", "two", "other"},
{"one", "few", "few"},
{"one", "many", "many"},
{"one", "other", "other"},
{"two", "few", "few"},
{"two", "many", "many"},
{"two", "other", "other"},
{"few", "few", "few"},
{"few", "many", "many"},
{"few", "other", "other"},
{"many", "few", "few"},
{"many", "many", "many"},
{"many", "other", "other"},
{"other", "one", "other"},
{"other", "two", "other"},
{"other", "few", "few"},
{"other", "many", "many"},
{"other", "other", "other"},
};
PluralRanges pr = null;
String[] locales = null;
HashMap<String, PluralRanges> tempLocaleIdToPluralRanges = new HashMap<String, PluralRanges>();
for (String[] row : pluralRangeData) {
if (row[0].equals("locales")) {
if (pr != null) {
pr.freeze();
for (String locale : locales) {
tempLocaleIdToPluralRanges.put(locale, pr);
}
}
locales = row[1].split(" ");
pr = new PluralRanges();
} else {
pr.add(
StandardPluralCategories.valueOf(row[0]),
StandardPluralCategories.valueOf(row[1]),
StandardPluralCategories.valueOf(row[2]));
}
}
// do last one
for (String locale : locales) {
tempLocaleIdToPluralRanges.put(locale, pr);
}
// now make whole thing immutable
localeIdToPluralRanges = Collections.unmodifiableMap(tempLocaleIdToPluralRanges);
}
}

View File

@ -1,13 +1,13 @@
/*
**********************************************************************
* Copyright (c) 2004-2014, International Business Machines
* Corporation and others. All Rights Reserved.
**********************************************************************
* Author: Alan Liu
* Created: April 20, 2004
* Since: ICU 3.0
**********************************************************************
*/
**********************************************************************
* Copyright (c) 2004-2014, International Business Machines
* Corporation and others. All Rights Reserved.
**********************************************************************
* Author: Alan Liu
* Created: April 20, 2004
* Since: ICU 3.0
**********************************************************************
*/
package com.ibm.icu.text;
import java.io.Externalizable;
@ -26,12 +26,14 @@ import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.concurrent.ConcurrentHashMap;
import com.ibm.icu.impl.DontCareFieldPosition;
import com.ibm.icu.impl.ICUResourceBundle;
import com.ibm.icu.impl.SimpleCache;
import com.ibm.icu.impl.SimplePatternFormatter;
import com.ibm.icu.math.BigDecimal;
import com.ibm.icu.text.PluralRules.StandardPluralCategories;
import com.ibm.icu.util.Currency;
import com.ibm.icu.util.CurrencyAmount;
import com.ibm.icu.util.Measure;
@ -104,47 +106,47 @@ import com.ibm.icu.util.UResourceBundle;
* @stable ICU 3.0
*/
public class MeasureFormat extends UFormat {
// Generated by serialver from JDK 1.4.1_01
static final long serialVersionUID = -7182021401701778240L;
private final transient ImmutableNumberFormat numberFormat;
private final transient FormatWidth formatWidth;
// PluralRules is documented as being immutable which implies thread-safety.
private final transient PluralRules rules;
// Measure unit -> format width -> plural form -> pattern ("{0} meters")
private final transient Map<MeasureUnit, EnumMap<FormatWidth, QuantityFormatter>> unitToStyleToCountToFormat;
private final transient NumericFormatters numericFormatters;
private final transient ImmutableNumberFormat currencyFormat;
private final transient ImmutableNumberFormat integerFormat;
private static final SimpleCache<ULocale,Map<MeasureUnit, EnumMap<FormatWidth, QuantityFormatter>>> localeToUnitToStyleToCountToFormat
= new SimpleCache<ULocale,Map<MeasureUnit, EnumMap<FormatWidth, QuantityFormatter>>>();
= new SimpleCache<ULocale,Map<MeasureUnit, EnumMap<FormatWidth, QuantityFormatter>>>();
private static final SimpleCache<ULocale, NumericFormatters> localeToNumericDurationFormatters
= new SimpleCache<ULocale,NumericFormatters>();
= new SimpleCache<ULocale,NumericFormatters>();
private static final Map<MeasureUnit, Integer> hmsTo012 =
new HashMap<MeasureUnit, Integer>();
static {
hmsTo012.put(MeasureUnit.HOUR, 0);
hmsTo012.put(MeasureUnit.MINUTE, 1);
hmsTo012.put(MeasureUnit.SECOND, 2);
}
// For serialization: sub-class types.
private static final int MEASURE_FORMAT = 0;
private static final int TIME_UNIT_FORMAT = 1;
private static final int CURRENCY_FORMAT = 2;
/**
* Formatting width enum.
*
@ -154,7 +156,7 @@ public class MeasureFormat extends UFormat {
// Be sure to update MeasureUnitTest.TestSerialFormatWidthEnum
// when adding an enum value.
public enum FormatWidth {
/**
* Spell out everything.
*
@ -162,7 +164,7 @@ public class MeasureFormat extends UFormat {
* @provisional This API might change or be removed in a future release.
*/
WIDE("units", ListFormatter.Style.DURATION, NumberFormat.PLURALCURRENCYSTYLE),
/**
* Abbreviate when possible.
*
@ -170,7 +172,7 @@ public class MeasureFormat extends UFormat {
* @provisional This API might change or be removed in a future release.
*/
SHORT("unitsShort", ListFormatter.Style.DURATION_SHORT, NumberFormat.ISOCURRENCYSTYLE),
/**
* Brief. Use only a symbol for the unit when possible.
*
@ -178,7 +180,7 @@ public class MeasureFormat extends UFormat {
* @provisional This API might change or be removed in a future release.
*/
NARROW("unitsNarrow", ListFormatter.Style.DURATION_NARROW, NumberFormat.CURRENCYSTYLE),
/**
* Identical to NARROW except when formatMeasures is called with
* an hour and minute; minute and second; or hour, minute, and second Measures.
@ -188,29 +190,29 @@ public class MeasureFormat extends UFormat {
* @provisional This API might change or be removed in a future release.
*/
NUMERIC("unitsNarrow", ListFormatter.Style.DURATION_NARROW, NumberFormat.CURRENCYSTYLE);
// Be sure to update the toFormatWidth and fromFormatWidth() functions
// when adding an enum value.
final String resourceKey;
private final ListFormatter.Style listFormatterStyle;
private final int currencyStyle;
private FormatWidth(String resourceKey, ListFormatter.Style style, int currencyStyle) {
this.resourceKey = resourceKey;
this.listFormatterStyle = style;
this.currencyStyle = currencyStyle;
}
ListFormatter.Style getListFormatterStyle() {
return listFormatterStyle;
}
int getCurrencyStyle() {
return currencyStyle;
}
}
/**
* Create a format from the locale, formatWidth, and format.
*
@ -223,7 +225,7 @@ public class MeasureFormat extends UFormat {
public static MeasureFormat getInstance(ULocale locale, FormatWidth formatWidth) {
return getInstance(locale, formatWidth, NumberFormat.getInstance(locale));
}
/**
* Create a format from the JDK locale, formatWidth, and format.
*
@ -276,9 +278,9 @@ public class MeasureFormat extends UFormat {
formatters,
new ImmutableNumberFormat(
NumberFormat.getInstance(locale, formatWidth.getCurrencyStyle())),
new ImmutableNumberFormat(intFormat));
new ImmutableNumberFormat(intFormat));
}
/**
* Create a format from the JDK locale, formatWidth, and format.
*
@ -341,7 +343,7 @@ public class MeasureFormat extends UFormat {
}
return toAppendTo;
}
/**
* Parses text from a string to produce a <code>Measure</code>.
* @see java.text.Format#parseObject(java.lang.String, java.text.ParsePosition)
@ -353,7 +355,7 @@ public class MeasureFormat extends UFormat {
public Measure parseObject(String source, ParsePosition pos) {
throw new UnsupportedOperationException();
}
/**
* Format a sequence of measures. Uses the ListFormatter unit lists.
* So, for example, one could format 3 feet, 2 inches.
@ -374,7 +376,48 @@ public class MeasureFormat extends UFormat {
DontCareFieldPosition.INSTANCE,
measures).toString();
}
/**
* Format a range of measures, such as "3.4-5.1 meters". It is the callers
* responsibility to have the appropriate values in appropriate order,
* and using the appropriate Number values. Typically the units should be
* in ascending order.
*
* @param lowValue low value in range
* @param highValue high value in range
* @return the formatted string.
* @internal
* @deprecated This API is ICU internal only.
*/
@Deprecated
public final String formatMeasureRange(Measure lowValue, Measure highValue) {
MeasureUnit unit = lowValue.getUnit();
if (!unit.equals(highValue.getUnit())) {
throw new IllegalArgumentException("Units must match: " + unit + "" + highValue.getUnit());
}
Number lowNumber = lowValue.getNumber();
Number highNumber = highValue.getNumber();
UFieldPosition fpos = new UFieldPosition();
StringBuffer lowFormatted = numberFormat.format(lowNumber, new StringBuffer(), fpos);
String keywordLow = rules.select(new PluralRules.FixedDecimal(lowNumber.doubleValue(), fpos.getCountVisibleFractionDigits(), fpos.getFractionDigits()));
StringBuffer highFormatted = numberFormat.format(highNumber, new StringBuffer(), fpos);
String keywordHigh = rules.select(new PluralRules.FixedDecimal(highNumber.doubleValue(), fpos.getCountVisibleFractionDigits(), fpos.getFractionDigits()));
StandardPluralCategories resolvedCategory = PluralRules.getRange(
getLocale(),
StandardPluralCategories.valueOf(keywordLow),
StandardPluralCategories.valueOf(keywordHigh));
Map<FormatWidth, QuantityFormatter> styleToCountToFormat = unitToStyleToCountToFormat.get(lowValue.getUnit());
QuantityFormatter countToFormat = styleToCountToFormat.get(formatWidth);
SimplePatternFormatter formatter = countToFormat.getByVariant(resolvedCategory.toString());
SimplePatternFormatter rangeFormatter = getRangeFormat(getLocale(), formatWidth);
String formattedNumber = rangeFormatter.format(lowFormatted, highFormatted);
return formatter.format(formattedNumber);
}
/**
* Formats a sequence of measures.
*
@ -399,7 +442,7 @@ public class MeasureFormat extends UFormat {
if (measures.length == 1) {
return formatMeasure(measures[0], numberFormat, appendTo, fieldPosition);
}
if (formatWidth == FormatWidth.NUMERIC) {
// If we have just hour, minute, or second follow the numeric
// track.
@ -408,7 +451,7 @@ public class MeasureFormat extends UFormat {
return formatNumeric(hms, appendTo);
}
}
ListFormatter listFormatter = ListFormatter.getInstance(
getLocale(), formatWidth.getListFormatterStyle());
if (fieldPosition != DontCareFieldPosition.INSTANCE) {
@ -422,9 +465,9 @@ public class MeasureFormat extends UFormat {
i == measures.length - 1 ? numberFormat : integerFormat);
}
return appendTo.append(listFormatter.format((Object[]) results));
}
/**
* Two MeasureFormats, a and b, are equal if and only if they have the same formatWidth,
* locale, and equal number formats.
@ -445,7 +488,7 @@ public class MeasureFormat extends UFormat {
&& getLocale().equals(rhs.getLocale())
&& getNumberFormat().equals(rhs.getNumberFormat());
}
/**
* {@inheritDoc}
* @draft ICU 53
@ -457,7 +500,7 @@ public class MeasureFormat extends UFormat {
return (getLocale().hashCode() * 31
+ getNumberFormat().hashCode()) * 31 + getWidth().hashCode();
}
/**
* Get the format width this instance is using.
* @draft ICU 53
@ -466,7 +509,7 @@ public class MeasureFormat extends UFormat {
public MeasureFormat.FormatWidth getWidth() {
return formatWidth;
}
/**
* Get the locale of this instance.
* @draft ICU 53
@ -475,7 +518,7 @@ public class MeasureFormat extends UFormat {
public final ULocale getLocale() {
return getLocale(ULocale.VALID_LOCALE);
}
/**
* Get a copy of the number format.
* @draft ICU 53
@ -518,7 +561,7 @@ public class MeasureFormat extends UFormat {
public static MeasureFormat getCurrencyFormat() {
return getCurrencyFormat(ULocale.getDefault(Category.FORMAT));
}
// This method changes the NumberFormat object as well to match the new locale.
MeasureFormat withLocale(ULocale locale) {
return MeasureFormat.getInstance(locale, getWidth());
@ -535,7 +578,7 @@ public class MeasureFormat extends UFormat {
this.currencyFormat,
this.integerFormat);
}
private MeasureFormat(
ULocale locale,
FormatWidth formatWidth,
@ -554,7 +597,7 @@ public class MeasureFormat extends UFormat {
this.currencyFormat = currencyFormat;
this.integerFormat = integerFormat;
}
MeasureFormat() {
// Make compiler happy by setting final fields to null.
this.formatWidth = null;
@ -565,12 +608,12 @@ public class MeasureFormat extends UFormat {
this.currencyFormat = null;
this.integerFormat = null;
}
static class NumericFormatters {
private DateFormat hourMinute;
private DateFormat minuteSecond;
private DateFormat hourMinuteSecond;
public NumericFormatters(
DateFormat hourMinute,
DateFormat minuteSecond,
@ -579,12 +622,12 @@ public class MeasureFormat extends UFormat {
this.minuteSecond = minuteSecond;
this.hourMinuteSecond = hourMinuteSecond;
}
public DateFormat getHourMinute() { return hourMinute; }
public DateFormat getMinuteSecond() { return minuteSecond; }
public DateFormat getHourMinuteSecond() { return hourMinuteSecond; }
}
private static NumericFormatters loadNumericFormatters(
ULocale locale) {
ICUResourceBundle r = (ICUResourceBundle)UResourceBundle.
@ -594,7 +637,7 @@ public class MeasureFormat extends UFormat {
loadNumericDurationFormat(r, "ms"),
loadNumericDurationFormat(r, "hms"));
}
/**
* Returns formatting data for all MeasureUnits except for currency ones.
*/
@ -602,7 +645,7 @@ public class MeasureFormat extends UFormat {
ULocale locale, PluralRules rules) {
QuantityFormatter.Builder builder = new QuantityFormatter.Builder();
Map<MeasureUnit, EnumMap<FormatWidth, QuantityFormatter>> unitToStyleToCountToFormat
= new HashMap<MeasureUnit, EnumMap<FormatWidth, QuantityFormatter>>();
= new HashMap<MeasureUnit, EnumMap<FormatWidth, QuantityFormatter>>();
ICUResourceBundle resource = (ICUResourceBundle)UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, locale);
for (MeasureUnit unit : MeasureUnit.getAvailable()) {
// Currency data cannot be found here. Skip.
@ -654,13 +697,13 @@ public class MeasureFormat extends UFormat {
}
return unitToStyleToCountToFormat;
}
private String formatMeasure(Measure measure, ImmutableNumberFormat nf) {
return formatMeasure(
measure, nf, new StringBuilder(),
DontCareFieldPosition.INSTANCE).toString();
}
private StringBuilder formatMeasure(
Measure measure,
ImmutableNumberFormat nf,
@ -669,10 +712,10 @@ public class MeasureFormat extends UFormat {
if (measure.getUnit() instanceof Currency) {
return appendTo.append(
currencyFormat.format(
new CurrencyAmount(measure.getNumber(), (Currency) measure.getUnit()),
new StringBuffer(),
fieldPosition));
new CurrencyAmount(measure.getNumber(), (Currency) measure.getUnit()),
new StringBuffer(),
fieldPosition));
}
Number n = measure.getNumber();
MeasureUnit unit = measure.getUnit();
@ -694,24 +737,24 @@ public class MeasureFormat extends UFormat {
}
return appendTo;
}
// Wrapper around NumberFormat that provides immutability and thread-safety.
private static final class ImmutableNumberFormat {
private NumberFormat nf;
public ImmutableNumberFormat(NumberFormat nf) {
this.nf = (NumberFormat) nf.clone();
}
public synchronized NumberFormat get() {
return (NumberFormat) nf.clone();
}
public synchronized StringBuffer format(
Number n, StringBuffer buffer, FieldPosition pos) {
return nf.format(n, buffer, pos);
}
public synchronized StringBuffer format(
CurrencyAmount n, StringBuffer buffer, FieldPosition pos) {
return nf.format(n, buffer, pos);
@ -722,7 +765,7 @@ public class MeasureFormat extends UFormat {
return nf.format(number);
}
}
static final class PatternData {
final String prefix;
final String suffix;
@ -741,26 +784,26 @@ public class MeasureFormat extends UFormat {
}
}
Object toTimeUnitProxy() {
return new MeasureProxy(getLocale(), formatWidth, numberFormat.get(), TIME_UNIT_FORMAT);
}
Object toCurrencyProxy() {
return new MeasureProxy(getLocale(), formatWidth, numberFormat.get(), CURRENCY_FORMAT);
}
private StringBuilder formatMeasuresSlowTrack(
ListFormatter listFormatter,
StringBuilder appendTo,
FieldPosition fieldPosition,
Measure... measures) {
String[] results = new String[measures.length];
// Zero out our field position so that we can tell when we find our field.
FieldPosition fpos = new FieldPosition(
fieldPosition.getFieldAttribute(), fieldPosition.getField());
int fieldPositionFoundIndex = -1;
for (int i = 0; i < measures.length; ++i) {
ImmutableNumberFormat nf = (i == measures.length - 1 ? numberFormat : integerFormat);
@ -775,7 +818,7 @@ public class MeasureFormat extends UFormat {
}
ListFormatter.FormattedListBuilder builder =
listFormatter.format(Arrays.asList(results), fieldPositionFoundIndex);
// Fix up FieldPosition indexes if our field is found.
if (builder.getOffset() != -1) {
fieldPosition.setBeginIndex(fpos.getBeginIndex() + builder.getOffset() + appendTo.length());
@ -783,7 +826,7 @@ public class MeasureFormat extends UFormat {
}
return appendTo.append(builder.toString());
}
// type is one of "hm", "ms" or "hms"
private static DateFormat loadNumericDurationFormat(
ICUResourceBundle r, String type) {
@ -793,7 +836,7 @@ public class MeasureFormat extends UFormat {
result.setTimeZone(TimeZone.GMT_ZONE);
return result;
}
// Returns hours in [0]; minutes in [1]; seconds in [2] out of measures array. If
// unsuccessful, e.g measures has other measurements besides hours, minutes, seconds;
// hours, minutes, seconds are out of order; or have negative values, returns null.
@ -820,11 +863,11 @@ public class MeasureFormat extends UFormat {
}
return result;
}
// Formats numeric time duration as 5:00:47 or 3:54. In the process, it replaces any null
// values in hms with 0.
private StringBuilder formatNumeric(Number[] hms, StringBuilder appendable) {
// find the start and end of non-nil values in hms array. We have to know if we
// have hour-minute; minute-second; or hour-minute-second.
int startIndex = -1;
@ -874,7 +917,7 @@ public class MeasureFormat extends UFormat {
}
throw new IllegalStateException();
}
// Formats a duration as 5:00:37 or 23:59.
// duration is a particular duration after epoch.
// formatter is a hour-minute-second, hour-minute, or minute-second formatter.
@ -892,7 +935,7 @@ public class MeasureFormat extends UFormat {
StringBuilder appendTo) {
// Format the smallest amount ahead of time.
String smallestAmountFormatted;
// Format the smallest amount using this object's number format, but keep track
// of the integer portion of this formatted amount. We have to replace just the
// integer part with the corresponding value from formatting the date. Otherwise
@ -909,23 +952,23 @@ public class MeasureFormat extends UFormat {
FieldPosition smallestFieldPosition = new FieldPosition(smallestField);
String draft = formatter.format(
duration, new StringBuffer(), smallestFieldPosition).toString();
// If we find the smallest field
if (smallestFieldPosition.getBeginIndex() != 0
|| smallestFieldPosition.getEndIndex() != 0) {
// add everything up to the start of the smallest field in duration.
appendTo.append(draft, 0, smallestFieldPosition.getBeginIndex());
// add everything in the smallest field up to the integer portion
appendTo.append(smallestAmountFormatted, 0, intFieldPosition.getBeginIndex());
// Add the smallest field in formatted duration in lieu of the integer portion
// of smallest field
appendTo.append(
draft,
smallestFieldPosition.getBeginIndex(),
smallestFieldPosition.getEndIndex());
// Add the rest of the smallest field
appendTo.append(
smallestAmountFormatted,
@ -938,15 +981,15 @@ public class MeasureFormat extends UFormat {
}
return appendTo;
}
private Object writeReplace() throws ObjectStreamException {
return new MeasureProxy(
getLocale(), formatWidth, numberFormat.get(), MEASURE_FORMAT);
}
static class MeasureProxy implements Externalizable {
private static final long serialVersionUID = -6033308329886716770L;
private ULocale locale;
private FormatWidth formatWidth;
private NumberFormat numberFormat;
@ -988,7 +1031,7 @@ public class MeasureFormat extends UFormat {
throw new InvalidObjectException("Missing number format.");
}
subClass = in.readByte() & 0xFF;
// This cast is safe because the serialized form of hashtable can have
// any object as the key and any object as the value.
keyValues = (HashMap<Object, Object>) in.readObject();
@ -996,7 +1039,7 @@ public class MeasureFormat extends UFormat {
throw new InvalidObjectException("Missing optional values map.");
}
}
private TimeUnitFormat createTimeUnitFormat() throws InvalidObjectException {
int style;
if (formatWidth == FormatWidth.WIDE) {
@ -1024,7 +1067,7 @@ public class MeasureFormat extends UFormat {
}
}
}
private static FormatWidth fromFormatWidthOrdinal(int ordinal) {
FormatWidth[] values = FormatWidth.values();
if (ordinal < 0 || ordinal >= values.length) {
@ -1032,4 +1075,53 @@ public class MeasureFormat extends UFormat {
}
return values[ordinal];
}
static final Map<ULocale, SimplePatternFormatter> localeIdToRangeFormat
= new ConcurrentHashMap<ULocale, SimplePatternFormatter>();
/**
* Return a simple pattern formatter for a range, such as "{0}{1}".
* @param forLocale locale to get the format for
* @param width the format width
* @return range formatter, such as "{0}{1}"
* @internal
* @deprecated This API is ICU internal only.
*/
@Deprecated
public SimplePatternFormatter getRangeFormat(ULocale forLocale, FormatWidth width) {
// TODO fix Hack for French
if (width != FormatWidth.WIDE && forLocale.getLanguage().equals("fr")) {
return getRangeFormat(ULocale.ROOT, width);
}
SimplePatternFormatter result = localeIdToRangeFormat.get(forLocale);
if (result == null) {
ICUResourceBundle rb = (ICUResourceBundle)UResourceBundle.
getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, forLocale);
ULocale realLocale = rb.getULocale();
if (!forLocale.equals(realLocale)) { // if the child would inherit, then add a cache entry for it.
result = localeIdToRangeFormat.get(forLocale);
if (result != null) {
localeIdToRangeFormat.put(forLocale, result);
return result;
}
}
// At this point, both the forLocale and the realLocale don't have an item
// So we have to make one.
NumberingSystem ns = NumberingSystem.getInstance(forLocale);
String resultString = null;
try {
resultString = rb.getStringWithFallback("NumberElements/" + ns.getName() + "/miscPatterns/range");
} catch ( MissingResourceException ex ) {
resultString = rb.getStringWithFallback("NumberElements/latn/patterns/range");
}
result = SimplePatternFormatter.compile(resultString);
localeIdToRangeFormat.put(forLocale, result);
if (!forLocale.equals(realLocale)) {
localeIdToRangeFormat.put(realLocale, result);
}
}
return result;
}
}

View File

@ -0,0 +1,307 @@
/*
*******************************************************************************
* Copyright (C) 2008-2014, Google, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
package com.ibm.icu.text;
import java.util.EnumSet;
import com.ibm.icu.text.PluralRules.StandardPluralCategories;
import com.ibm.icu.util.Freezable;
import com.ibm.icu.util.Output;
/**
* Utility class for returning the plural category for a range of numbers, such as 15, so that appropriate messages can
* be chosen. The rules for determining this value vary widely across locales.
*
* @author markdavis
* @internal
* @deprecated This API is ICU internal only.
*/
@Deprecated
public final class PluralRanges implements Freezable<PluralRanges>, Comparable<PluralRanges> {
private volatile boolean isFrozen;
private Matrix matrix = new Matrix();
private boolean[] explicit = new boolean[StandardPluralCategories.COUNT];
/**
* Internal class for mapping from two StandardPluralCategories values to another.
*
* @internal
* @deprecated This API is ICU internal only.
*/
public static final class Matrix implements Comparable<Matrix>, Cloneable {
private byte[] data = new byte[StandardPluralCategories.COUNT * StandardPluralCategories.COUNT];
{
for (int i = 0; i < data.length; ++i) {
data[i] = -1;
}
}
/**
* Internal method for setting.
*
* @internal
* @deprecated This API is ICU internal only.
*/
public void set(StandardPluralCategories start, StandardPluralCategories end, StandardPluralCategories result) {
data[start.ordinal() * StandardPluralCategories.COUNT + end.ordinal()] = result == null ? (byte) -1
: (byte) result.ordinal();
}
/**
* Internal method for setting; throws exception if already set.
*
* @internal
* @deprecated This API is ICU internal only.
*/
public void setIfNew(StandardPluralCategories start, StandardPluralCategories end,
StandardPluralCategories result) {
byte old = data[start.ordinal() * StandardPluralCategories.COUNT + end.ordinal()];
if (old >= 0) {
throw new IllegalArgumentException("Previously set value for <" + start + ", " + end + ", "
+ StandardPluralCategories.VALUES.get(old) + ">");
}
data[start.ordinal() * StandardPluralCategories.COUNT + end.ordinal()] = result == null ? (byte) -1
: (byte) result.ordinal();
}
/**
* Internal method for getting.
*
* @internal
* @deprecated This API is ICU internal only.
*/
public StandardPluralCategories get(StandardPluralCategories start, StandardPluralCategories end) {
byte result = data[start.ordinal() * StandardPluralCategories.COUNT + end.ordinal()];
return result < 0 ? null : StandardPluralCategories.VALUES.get(result);
}
/**
* Internal method to see if <*,end> values are all the same.
*
* @internal
* @deprecated This API is ICU internal only.
*/
public StandardPluralCategories endSame(StandardPluralCategories end) {
StandardPluralCategories first = null;
for (StandardPluralCategories start : StandardPluralCategories.VALUES) {
StandardPluralCategories item = get(start, end);
if (item == null) {
continue;
}
if (first == null) {
first = item;
continue;
}
if (first != item) {
return null;
}
}
return first;
}
/**
* Internal method to see if <start,*> values are all the same.
*
* @internal
* @deprecated This API is ICU internal only.
*/
public StandardPluralCategories startSame(StandardPluralCategories start,
EnumSet<StandardPluralCategories> endDone, Output<Boolean> emit) {
emit.value = false;
StandardPluralCategories first = null;
for (StandardPluralCategories end : StandardPluralCategories.VALUES) {
StandardPluralCategories item = get(start, end);
if (item == null) {
continue;
}
if (first == null) {
first = item;
continue;
}
if (first != item) {
return null;
}
// only emit if we didn't cover with the 'end' values
if (!endDone.contains(end)) {
emit.value = true;
}
}
return first;
}
@Override
public int hashCode() {
int result = 0;
for (int i = 0; i < data.length; ++i) {
result = result * 37 + data[i];
}
return result;
}
@Override
public boolean equals(Object other) {
if (!(other instanceof Matrix)) {
return false;
}
return 0 == compareTo((Matrix) other);
}
public int compareTo(Matrix o) {
for (int i = 0; i < data.length; ++i) {
int diff = data[i] - o.data[i];
if (diff != 0) {
return diff;
}
}
return 0;
}
@Override
public Matrix clone() {
Matrix result = new Matrix();
result.data = data.clone();
return result;
}
@Override
public String toString() {
StringBuilder result = new StringBuilder();
for (StandardPluralCategories i : StandardPluralCategories.values()) {
for (StandardPluralCategories j : StandardPluralCategories.values()) {
StandardPluralCategories x = get(i, j);
if (x != null) {
result.append(i + " & " + j + "" + x + ";\n");
}
}
}
return result.toString();
}
}
/**
* Internal method for building. If the start or end are null, it means everything of that type.
*
* @param rangeStart
* plural category for the start of the range
* @param rangeEnd
* plural category for the end of the range
* @param result
* the resulting plural category
* @internal
* @deprecated This API is ICU internal only.
*/
public void add(StandardPluralCategories rangeStart, StandardPluralCategories rangeEnd,
StandardPluralCategories result) {
if (isFrozen) {
throw new UnsupportedOperationException();
}
explicit[result.ordinal()] = true;
if (rangeStart == null) {
for (StandardPluralCategories rs : StandardPluralCategories.values()) {
if (rangeEnd == null) {
for (StandardPluralCategories re : StandardPluralCategories.values()) {
matrix.setIfNew(rs, re, result);
}
} else {
explicit[rangeEnd.ordinal()] = true;
matrix.setIfNew(rs, rangeEnd, result);
}
}
} else if (rangeEnd == null) {
explicit[rangeStart.ordinal()] = true;
for (StandardPluralCategories re : StandardPluralCategories.values()) {
matrix.setIfNew(rangeStart, re, result);
}
} else {
explicit[rangeStart.ordinal()] = true;
explicit[rangeEnd.ordinal()] = true;
matrix.setIfNew(rangeStart, rangeEnd, result);
}
}
/**
* Returns the appropriate plural category for a range from start to end. If there is no available data, then
* 'other' is returned.
*
* @param start
* plural category for the start of the range
* @param end
* plural category for the end of the range
* @return the resulting plural category, or 'end' if there is no data.
* @internal
* @deprecated This API is ICU internal only.
*/
public StandardPluralCategories get(StandardPluralCategories start, StandardPluralCategories end) {
StandardPluralCategories result = matrix.get(start, end);
return result == null ? end : result;
}
/**
* Returns the appropriate plural category for a range from start to end. If the combination does not explicitly
* occur in the data, returns null.
*
* @param start
* plural category for the start of the range
* @param end
* plural category for the end of the range
* @return the resulting plural category
* @internal
* @deprecated This API is ICU internal only.
*/
public boolean isExplicit(StandardPluralCategories start, StandardPluralCategories end) {
return matrix.get(start, end) != null;
}
/**
* Internal method to determines whether the StandardPluralCategories was explicitly used in any add statement.
*
* @param count
* plural category to test
* @return true if set
* @internal
* @deprecated This API is ICU internal only.
*/
public boolean isExplicitlySet(StandardPluralCategories count) {
return explicit[count.ordinal()];
}
@Override
public boolean equals(Object other) {
return other instanceof PluralRanges ? matrix.equals((PluralRanges) other) : false;
}
@Override
public int hashCode() {
return matrix.hashCode();
}
public int compareTo(PluralRanges that) {
return matrix.compareTo(that.matrix);
}
public boolean isFrozen() {
return isFrozen;
}
public PluralRanges freeze() {
isFrozen = true;
return this;
}
public PluralRanges cloneAsThawed() {
PluralRanges result = new PluralRanges();
result.explicit = explicit.clone();
result.matrix = matrix.clone();
return result;
}
@Override
public String toString() {
return matrix.toString();
}
}

View File

@ -15,6 +15,7 @@ import java.io.ObjectStreamException;
import java.io.Serializable;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
@ -1819,6 +1820,20 @@ public class PluralRules implements Serializable {
*/
@Deprecated
other;
/**
* @internal
* @deprecated This API is ICU internal only.
*/
@Deprecated
public static final List<StandardPluralCategories> VALUES
= Collections.unmodifiableList(Arrays.asList(values()));
/**
* @internal
* @deprecated This API is ICU internal only.
*/
@Deprecated
public static final int COUNT = values().length;
static StandardPluralCategories forString(String s) {
StandardPluralCategories a;
try {
@ -2431,4 +2446,41 @@ public class PluralRules implements Serializable {
public boolean computeLimited(String keyword, SampleType sampleType) {
return rules.computeLimited(keyword, sampleType);
}
/**
* Return the plural category for a range, such as 3.1-4.2.
* @param locale locale for the range
* @param startPluralCategory the plural category of the low end of the range.
* @param endPluralCategory the plural category of the high end of the range.
* @return the plural category of the range
* @internal
* @deprecated This API is ICU internal only.
*/
@Deprecated
static public StandardPluralCategories getRange(ULocale locale,
StandardPluralCategories startPluralCategory,
StandardPluralCategories endPluralCategory) {
final PluralRanges pluralRanges = Factory.getDefaultFactory().getPluralRanges(locale);
return pluralRanges.get(startPluralCategory, endPluralCategory);
}
/**
* Return the plural category for a range, such as 3.1-4.2.
* @param locale locale for the range
* @param startPluralCategory the plural category of the low end of the range.
* @param endPluralCategory the plural category of the high end of the range.
* @return the plural category of the range
* @internal
* @deprecated This API is ICU internal only.
*/
@Deprecated
static public String getRange(ULocale locale,
String startPluralCategory,
String endPluralCategory) {
final PluralRanges pluralRanges = Factory.getDefaultFactory().getPluralRanges(locale);
return pluralRanges.get(
StandardPluralCategories.valueOf(startPluralCategory),
StandardPluralCategories.valueOf(endPluralCategory))
.toString();
}
}

View File

@ -0,0 +1,85 @@
/*
*******************************************************************************
* Copyright (C) 2008-2014, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
package com.ibm.icu.dev.test.format;
import com.ibm.icu.dev.test.TestFmwk;
import com.ibm.icu.text.MeasureFormat;
import com.ibm.icu.text.MeasureFormat.FormatWidth;
import com.ibm.icu.text.PluralRanges;
import com.ibm.icu.text.PluralRules;
import com.ibm.icu.text.PluralRules.StandardPluralCategories;
import com.ibm.icu.util.Measure;
import com.ibm.icu.util.MeasureUnit;
import com.ibm.icu.util.ULocale;
/**
* @author markdavis
*
*/
public class PluralRangesTest extends TestFmwk {
public static void main(String[] args) {
new PluralRangesTest().run(args);
}
public void TestLocaleData() {
String[][] tests = {
{"de", "other", "one", "one"},
{"xxx", "few", "few", "few" },
{"de", "one", "other", "other"},
{"de", "other", "one", "one"},
{"de", "other", "other", "other"},
{"ro", "one", "few", "few"},
{"ro", "one", "other", "other"},
{"ro", "few", "one", "few"},
};
for (String[] test : tests) {
final ULocale locale = new ULocale(test[0]);
final StandardPluralCategories start = StandardPluralCategories.valueOf(test[1]);
final StandardPluralCategories end = StandardPluralCategories.valueOf(test[2]);
final StandardPluralCategories expected = StandardPluralCategories.valueOf(test[3]);
StandardPluralCategories actual = PluralRules.getRange(locale, start, end);
assertEquals("Deriving range category", expected, actual);
}
}
public void TestFormatting() {
Object[][] tests = {
{0.0, 1.0, ULocale.FRANCE, FormatWidth.WIDE, MeasureUnit.FAHRENHEIT, "de 0 à 1 degré Fahrenheit"},
{1.0, 2.0, ULocale.FRANCE, FormatWidth.WIDE, MeasureUnit.FAHRENHEIT, "de 1 à 2 degrés Fahrenheit"},
{3.1, 4.25, ULocale.FRANCE, FormatWidth.SHORT, MeasureUnit.FAHRENHEIT, "3,14,25 °F"},
{3.1, 4.25, ULocale.ENGLISH, FormatWidth.SHORT, MeasureUnit.FAHRENHEIT, "3.14.25°F"},
{3.1, 4.25, ULocale.CHINESE, FormatWidth.WIDE, MeasureUnit.INCH, "3.1-4.25英寸"},
{0.0, 1.0, new ULocale("xx"), FormatWidth.WIDE, MeasureUnit.INCH, "01 inches"},
};
for (Object[] test : tests) {
double low = (Double) test[0];
double high = (Double) test[1];
final ULocale locale = (ULocale) test[2];
final FormatWidth width = (FormatWidth) test[3];
final MeasureUnit unit = (MeasureUnit) test[4];
final String expected = (String) test[5];
MeasureFormat mf = MeasureFormat.getInstance(locale, width);
String actual = mf.formatMeasureRange(new Measure(low, unit), new Measure(high, unit));
assertEquals("Formatting unit", expected, actual);
}
}
public void TestBasic() {
PluralRanges a = new PluralRanges();
a.add(StandardPluralCategories.one, StandardPluralCategories.other, StandardPluralCategories.one);
StandardPluralCategories actual = a.get(StandardPluralCategories.one, StandardPluralCategories.other);
assertEquals("range", StandardPluralCategories.one, actual);
a.freeze();
try {
a.add(StandardPluralCategories.one, StandardPluralCategories.one, StandardPluralCategories.one);
errln("Failed to cause exception on frozen instance");
} catch (UnsupportedOperationException e) {
}
}
}

View File

@ -63,6 +63,7 @@ public class TestAll extends TestGroup {
"IntlTestDecimalFormatAPIC",
"IntlTestDecimalFormatSymbols",
"IntlTestDecimalFormatSymbolsC",
"PluralRangesTest",
});
}
}