ICU-11986 UResource.Value.getAliasString(), sink.leave(), MeasureFormat use resource enumeration, store one data reference not pieces of that data

X-SVN-Rev: 38095
This commit is contained in:
Markus Scherer 2015-11-19 22:56:12 +00:00
parent 724f7b5c10
commit 3f4f8032d1
5 changed files with 406 additions and 173 deletions

View File

@ -849,6 +849,15 @@ public final class ICUResourceBundleReader {
return s;
}
@Override
public String getAliasString() {
String s = reader.getAlias(res);
if (s == null) {
throw new UResourceTypeMismatchException("");
}
return s;
}
@Override
public int getInt() {
if (RES_GET_TYPE(res) != UResourceBundle.INT) {
@ -945,14 +954,16 @@ public final class ICUResourceBundleReader {
assert(table.size == numItems);
table.getAllItems(reader, key, value, subSink);
}
/* TODO: settle on how to deal with aliases, port to C++
} else if (type == ICUResourceBundle.ALIAS) {
throw new UnsupportedOperationException(
"aliases not handled in resource enumeration");
"aliases not handled in resource enumeration"); */
} else {
value.res = res;
sink.put(i, value);
}
}
sink.leave();
}
}
private static final class Array32 extends Array {
@ -1044,9 +1055,10 @@ public final class ICUResourceBundleReader {
assert(table.size == numItems);
table.getAllItems(reader, key, value, subSink);
}
/* TODO: settle on how to deal with aliases, port to C++
} else if (type == ICUResourceBundle.ALIAS) {
throw new UnsupportedOperationException(
"aliases not handled in resource enumeration");
"aliases not handled in resource enumeration"); */
} else if (reader.isNoInheritanceMarker(res)) {
sink.putNoFallback(key);
} else {
@ -1054,6 +1066,7 @@ public final class ICUResourceBundleReader {
sink.put(key, value);
}
}
sink.leave();
}
Table() {
}

View File

@ -188,6 +188,15 @@ public final class UResource {
return csLength <= length && regionMatches(length - csLength, cs, csLength);
}
/**
* @return true if the substring of this key starting from the offset
* contains the same characters as the other sequence.
*/
public boolean regionMatches(int start, CharSequence cs) {
int csLength = cs.length();
return csLength == (length - start) && regionMatches(start, cs, csLength);
}
@Override
public int hashCode() {
// Never return s.hashCode(), so that
@ -241,6 +250,11 @@ public final class UResource {
*/
public abstract String getString();
/**
* @throws UResourceTypeMismatchException if this is not an alias resource
*/
public abstract String getAliasString();
/**
* @see UResourceBundle#getInt()
* @throws UResourceTypeMismatchException if this is not an integer resource
@ -341,6 +355,13 @@ public final class UResource {
public TableSink getOrCreateTableSink(int index, int initialSize) {
return null;
}
/**
* "Leaves" the array.
* Indicates that all of the resources and sub-resources of the current array
* have been enumerated.
*/
public void leave() {}
}
/**
@ -397,5 +418,12 @@ public final class UResource {
public TableSink getOrCreateTableSink(Key key, int initialSize) {
return null;
}
/**
* "Leaves" the table.
* Indicates that all of the resources and sub-resources of the current table
* have been enumerated.
*/
public void leave() {}
}
}

View File

@ -33,11 +33,13 @@ import com.ibm.icu.impl.ICUData;
import com.ibm.icu.impl.ICUResourceBundle;
import com.ibm.icu.impl.SimpleCache;
import com.ibm.icu.impl.SimplePatternFormatter;
import com.ibm.icu.impl.UResource;
import com.ibm.icu.math.BigDecimal;
import com.ibm.icu.text.PluralRules.Factory;
import com.ibm.icu.text.PluralRules.StandardPluralCategories;
import com.ibm.icu.util.Currency;
import com.ibm.icu.util.CurrencyAmount;
import com.ibm.icu.util.ICUException;
import com.ibm.icu.util.Measure;
import com.ibm.icu.util.MeasureUnit;
import com.ibm.icu.util.TimeZone;
@ -113,6 +115,8 @@ public class MeasureFormat extends UFormat {
// Generated by serialver from JDK 1.4.1_01
static final long serialVersionUID = -7182021401701778240L;
private final transient MeasureFormatData cache;
private final transient ImmutableNumberFormat numberFormat;
private final transient FormatWidth formatWidth;
@ -120,19 +124,12 @@ public class MeasureFormat extends UFormat {
// 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 final transient Map<MeasureUnit, EnumMap<FormatWidth, SimplePatternFormatter>> unitToStyleToPerUnitPattern;
private final transient EnumMap<FormatWidth, SimplePatternFormatter> styleToPerPattern;
private static final SimpleCache<ULocale, MeasureFormatData> localeMeasureFormatData
= new SimpleCache<ULocale, MeasureFormatData>();
@ -167,21 +164,21 @@ public class MeasureFormat extends UFormat {
*
* @stable ICU 53
*/
WIDE("units", ListFormatter.Style.DURATION, NumberFormat.PLURALCURRENCYSTYLE),
WIDE(ListFormatter.Style.DURATION, NumberFormat.PLURALCURRENCYSTYLE),
/**
* Abbreviate when possible.
*
* @stable ICU 53
*/
SHORT("unitsShort", ListFormatter.Style.DURATION_SHORT, NumberFormat.ISOCURRENCYSTYLE),
SHORT(ListFormatter.Style.DURATION_SHORT, NumberFormat.ISOCURRENCYSTYLE),
/**
* Brief. Use only a symbol for the unit when possible.
*
* @stable ICU 53
*/
NARROW("unitsNarrow", ListFormatter.Style.DURATION_NARROW, NumberFormat.CURRENCYSTYLE),
NARROW(ListFormatter.Style.DURATION_NARROW, NumberFormat.CURRENCYSTYLE),
/**
* Identical to NARROW except when formatMeasures is called with
@ -190,17 +187,16 @@ public class MeasureFormat extends UFormat {
*
* @stable ICU 53
*/
NUMERIC("unitsNarrow", ListFormatter.Style.DURATION_NARROW, NumberFormat.CURRENCYSTYLE);
NUMERIC(ListFormatter.Style.DURATION_NARROW, NumberFormat.CURRENCYSTYLE);
// Be sure to update the toFormatWidth and fromFormatWidth() functions
// when adding an enum value.
private static final int INDEX_COUNT = 3; // NARROW.ordinal() + 1
final String resourceKey;
private final ListFormatter.Style listFormatterStyle;
private final int currencyStyle;
private FormatWidth(String resourceKey, ListFormatter.Style style, int currencyStyle) {
this.resourceKey = resourceKey;
private FormatWidth(ListFormatter.Style style, int currencyStyle) {
this.listFormatterStyle = style;
this.currencyStyle = currencyStyle;
}
@ -268,15 +264,13 @@ public class MeasureFormat extends UFormat {
intFormat.setRoundingMode(BigDecimal.ROUND_DOWN);
return new MeasureFormat(
locale,
data,
formatWidth,
new ImmutableNumberFormat(format),
rules,
data.unitToStyleToCountToFormat,
formatters,
new ImmutableNumberFormat(NumberFormat.getInstance(locale, formatWidth.getCurrencyStyle())),
new ImmutableNumberFormat(intFormat),
data.unitToStyleToPerUnitPattern,
data.styleToPerPattern);
new ImmutableNumberFormat(intFormat));
}
/**
@ -454,8 +448,7 @@ public class MeasureFormat extends UFormat {
// FieldPosition pos2 = new FieldPosition(0);
// currencyFormat.format(currencyHigh, buffer2, pos2);
} else {
Map<FormatWidth, QuantityFormatter> styleToCountToFormat = unitToStyleToCountToFormat.get(lowValue.getUnit());
QuantityFormatter countToFormat = styleToCountToFormat.get(formatWidth);
QuantityFormatter countToFormat = getQuantityFormatter(lowValue.getUnit(), formatWidth);
SimplePatternFormatter formatter = countToFormat.getByVariant(resolvedCategory.toString());
return formatter.format(formattedNumber);
}
@ -668,51 +661,43 @@ public class MeasureFormat extends UFormat {
MeasureFormat withNumberFormat(NumberFormat format) {
return new MeasureFormat(
getLocale(),
this.cache,
this.formatWidth,
new ImmutableNumberFormat(format),
this.rules,
this.unitToStyleToCountToFormat,
this.numericFormatters,
this.currencyFormat,
this.integerFormat,
this.unitToStyleToPerUnitPattern,
this.styleToPerPattern);
this.integerFormat);
}
private MeasureFormat(
ULocale locale,
MeasureFormatData data,
FormatWidth formatWidth,
ImmutableNumberFormat format,
PluralRules rules,
Map<MeasureUnit, EnumMap<FormatWidth, QuantityFormatter>> unitToStyleToCountToFormat,
NumericFormatters formatters,
ImmutableNumberFormat currencyFormat,
ImmutableNumberFormat integerFormat,
Map<MeasureUnit, EnumMap<FormatWidth, SimplePatternFormatter>> unitToStyleToPerUnitPattern,
EnumMap<FormatWidth, SimplePatternFormatter> styleToPerPattern) {
ImmutableNumberFormat integerFormat) {
setLocale(locale, locale);
this.cache = data;
this.formatWidth = formatWidth;
this.numberFormat = format;
this.rules = rules;
this.unitToStyleToCountToFormat = unitToStyleToCountToFormat;
this.numericFormatters = formatters;
this.currencyFormat = currencyFormat;
this.integerFormat = integerFormat;
this.unitToStyleToPerUnitPattern = unitToStyleToPerUnitPattern;
this.styleToPerPattern = styleToPerPattern;
}
MeasureFormat() {
// Make compiler happy by setting final fields to null.
this.cache = null;
this.formatWidth = null;
this.numberFormat = null;
this.rules = null;
this.unitToStyleToCountToFormat = null;
this.numericFormatters = null;
this.currencyFormat = null;
this.integerFormat = null;
this.unitToStyleToPerUnitPattern = null;
this.styleToPerPattern = null;
}
static class NumericFormatters {
@ -745,113 +730,270 @@ public class MeasureFormat extends UFormat {
}
/**
* Returns formatting data for all MeasureUnits except for currency ones.
* Sink for enumerating all of the measurement unit display names.
* Contains inner sink classes, each one corresponding to a type of resource table.
* The outer sink handles the top-level units, unitsNarrow, and unitsShort tables.
*
* More specific bundles (en_GB) are enumerated before their parents (en_001, en, root):
* Only store a value if it is still missing, that is, it has not been overridden.
*
* C++: Each inner sink class has a reference to the main outer sink.
* Java: Use non-static inner classes instead.
*/
private static MeasureFormatData loadLocaleData(
ULocale locale) {
QuantityFormatter.Builder builder = new QuantityFormatter.Builder();
Map<MeasureUnit, EnumMap<FormatWidth, QuantityFormatter>> unitToStyleToCountToFormat
= new HashMap<MeasureUnit, EnumMap<FormatWidth, QuantityFormatter>>();
Map<MeasureUnit, EnumMap<FormatWidth, SimplePatternFormatter>> unitToStyleToPerUnitPattern
= new HashMap<MeasureUnit, EnumMap<FormatWidth, SimplePatternFormatter>>();
ICUResourceBundle resource = (ICUResourceBundle)UResourceBundle.getBundleInstance(ICUData.ICU_UNIT_BASE_NAME, locale);
EnumMap<FormatWidth, SimplePatternFormatter> styleToPerPattern = new EnumMap<FormatWidth, SimplePatternFormatter>(FormatWidth.class);
for (FormatWidth styleItem : FormatWidth.values()) {
try {
ICUResourceBundle unitTypeRes = resource.getWithFallback(styleItem.resourceKey);
ICUResourceBundle compoundRes = unitTypeRes.getWithFallback("compound");
ICUResourceBundle perRes = compoundRes.getWithFallback("per");
styleToPerPattern.put(styleItem, SimplePatternFormatter.compile(perRes.getString()));
} catch (MissingResourceException e) {
// may not have compound/per for every width.
continue;
}
}
fillInStyleMap(styleToPerPattern);
for (MeasureUnit unit : MeasureUnit.getAvailable()) {
// Currency data cannot be found here. Skip.
if (unit instanceof Currency) {
continue;
}
EnumMap<FormatWidth, QuantityFormatter> styleToCountToFormat = unitToStyleToCountToFormat.get(unit);
if (styleToCountToFormat == null) {
unitToStyleToCountToFormat.put(unit, styleToCountToFormat = new EnumMap<FormatWidth, QuantityFormatter>(FormatWidth.class));
}
EnumMap<FormatWidth, SimplePatternFormatter> styleToPerUnitPattern = new EnumMap<FormatWidth, SimplePatternFormatter>(FormatWidth.class);
unitToStyleToPerUnitPattern.put(unit, styleToPerUnitPattern);
for (FormatWidth styleItem : FormatWidth.values()) {
try {
ICUResourceBundle unitTypeRes = resource.getWithFallback(styleItem.resourceKey);
ICUResourceBundle unitsRes = unitTypeRes.getWithFallback(unit.getType());
ICUResourceBundle oneUnitRes = unitsRes.getWithFallback(unit.getSubtype());
builder.reset();
boolean havePluralItem = false;
int len = oneUnitRes.getSize();
for (int i = 0; i < len; i++) {
UResourceBundle countBundle;
try {
countBundle = oneUnitRes.get(i);
} catch (MissingResourceException e) {
continue;
}
String resKey = countBundle.getKey();
if (resKey.equals("dnam")) {
continue; // skip display name & per pattern (new in CLDR 26 / ICU 54) for now, not part of plurals
}
if (resKey.equals("per")) {
styleToPerUnitPattern.put(
styleItem, SimplePatternFormatter.compile(countBundle.getString()));
continue;
}
havePluralItem = true;
builder.add(resKey, countBundle.getString());
private static final class UnitDataSink extends UResource.TableSink {
/**
* Sink for a table of display patterns. For example,
* unitsShort/duration/hour contains other{"{0} hrs"}.
*/
class UnitPatternSink extends UResource.TableSink {
QuantityFormatter.Builder builder = new QuantityFormatter.Builder();
@Override
public void put(UResource.Key key, UResource.Value value) {
if (key.contentEquals("dnam")) {
// Skip the unit display name for now.
} else if (key.contentEquals("per")) {
// For example, "{0}/h".
cacheData.setPerUnitFormatterIfAbsent(unit, width, value);
} else {
// The key must be one of the plural form strings. For example:
// one{"{0} hr"}
// other{"{0} hrs"}
if (!hasPatterns) {
builder.add(key.toString(), value.getString());
}
if (havePluralItem) {
// might not have any plural items if countBundle only has "dnam" display name, for instance,
// as with fr unitsNarrow/light/lux in CLDR 26
styleToCountToFormat.put(styleItem, builder.build());
}
} catch (MissingResourceException e) {
continue;
}
}
// TODO: if no fallback available, get from root.
fillInStyleMap(styleToCountToFormat);
fillInStyleMap(styleToPerUnitPattern);
}
return new MeasureFormatData(unitToStyleToCountToFormat, unitToStyleToPerUnitPattern, styleToPerPattern);
}
private static <T> boolean fillInStyleMap(Map<FormatWidth, T> styleMap) {
if (styleMap.size() == FormatWidth.values().length) {
return true;
}
T fallback = styleMap.get(FormatWidth.SHORT);
if (fallback == null) {
return false;
}
for (FormatWidth styleItem : FormatWidth.values()) {
T item = styleMap.get(styleItem);
if (item == null) {
styleMap.put(styleItem, fallback);
@Override
public void leave() {
if (builder.hasPatterns()) {
EnumMap<FormatWidth, QuantityFormatter> styleToCountToFormat =
cacheData.unitToStyleToCountToFormat.get(unit);
if (styleToCountToFormat == null) {
styleToCountToFormat =
new EnumMap<FormatWidth, QuantityFormatter>(FormatWidth.class);
cacheData.unitToStyleToCountToFormat.put(unit, styleToCountToFormat);
}
styleToCountToFormat.put(width, builder.build());
}
}
}
return true;
UnitPatternSink patternSink = new UnitPatternSink();
/**
* Sink for a table of per-unit tables. For example,
* unitsShort/duration contains tables for duration-unit subtypes day & hour.
*/
class UnitSubtypeSink extends UResource.TableSink {
@Override
public UResource.TableSink getOrCreateTableSink(UResource.Key key, int initialSize) {
// Should we ignore or reject unknown units?
unit = MeasureUnit.internalGetInstance(type, key.toString()); // never null
Map<FormatWidth, QuantityFormatter> styleToCountToFormat =
cacheData.unitToStyleToCountToFormat.get(unit);
hasPatterns = styleToCountToFormat != null && styleToCountToFormat.containsKey(width);
return patternSink;
}
}
UnitSubtypeSink subtypeSink = new UnitSubtypeSink();
/**
* Sink for compound x-per-y display pattern. For example,
* unitsShort/compound/per may be "{0}/{1}".
*/
class UnitCompoundSink extends UResource.TableSink {
@Override
public void put(UResource.Key key, UResource.Value value) {
if (key.contentEquals("per")) {
cacheData.styleToPerPattern.put(width,
SimplePatternFormatter.compile(value.getString()));
}
}
}
UnitCompoundSink compoundSink = new UnitCompoundSink();
/**
* Sink for a table of unit type tables. For example,
* unitsShort contains tables for area & duration.
* It also contains a table for the compound/per pattern.
*/
class UnitTypeSink extends UResource.TableSink {
@Override
public UResource.TableSink getOrCreateTableSink(UResource.Key key, int initialSize) {
if (key.contentEquals("currency")) {
// Skip.
} else if (key.contentEquals("compound")) {
if (!cacheData.hasPerFormatter(width)) {
return compoundSink;
}
} else {
type = key.toString();
return subtypeSink;
}
return null;
}
}
UnitTypeSink typeSink = new UnitTypeSink();
UnitDataSink(MeasureFormatData outputData) {
cacheData = outputData;
}
@Override
public void put(UResource.Key key, UResource.Value value) {
// Handle aliases like
// units:alias{"/LOCALE/unitsShort"}
// which should only occur in the root bundle.
if (value.getType() != ICUResourceBundle.ALIAS) { return; }
FormatWidth sourceWidth = widthFromKey(key);
if (sourceWidth == null) {
// Alias from something we don't care about.
return;
}
FormatWidth targetWidth = widthFromAlias(value);
if (targetWidth == null) {
// We do not recognize what to fall back to.
throw new ICUException("Units data fallback from " + key +
" to unknown " + value.getAliasString());
}
// Check that we do not fall back to another fallback.
if (cacheData.widthFallback[targetWidth.ordinal()] != null) {
throw new ICUException("Units data fallback from " + key +
" to " + value.getAliasString() + " which falls back to something else");
}
cacheData.widthFallback[sourceWidth.ordinal()] = targetWidth;
}
@Override
public UResource.TableSink getOrCreateTableSink(UResource.Key key, int initialSize) {
if ((width = widthFromKey(key)) != null) {
return typeSink;
}
return null;
}
static FormatWidth widthFromKey(UResource.Key key) {
if (key.startsWith("units")) {
if (key.length() == 5) {
return FormatWidth.WIDE;
} else if (key.regionMatches(5, "Short")) {
return FormatWidth.SHORT;
} else if (key.regionMatches(5, "Narrow")) {
return FormatWidth.NARROW;
}
}
return null;
}
static FormatWidth widthFromAlias(UResource.Value value) {
String s = value.getAliasString();
// For example: "/LOCALE/unitsShort"
if (s.startsWith("/LOCALE/units")) {
if (s.length() == 13) {
return FormatWidth.WIDE;
} else if (s.length() == 18 && s.endsWith("Short")) {
return FormatWidth.SHORT;
} else if (s.length() == 19 && s.endsWith("Narrow")) {
return FormatWidth.NARROW;
}
}
return null;
}
// Output data.
MeasureFormatData cacheData;
// Path to current data.
FormatWidth width;
String type;
MeasureUnit unit;
/** True if we already have plural-form display patterns for width/type/subtype. */
boolean hasPatterns;
}
/**
* Returns formatting data for all MeasureUnits except for currency ones.
*/
private static MeasureFormatData loadLocaleData(ULocale locale) {
ICUResourceBundle resource =
(ICUResourceBundle)UResourceBundle.getBundleInstance(ICUData.ICU_UNIT_BASE_NAME, locale);
MeasureFormatData cacheData = new MeasureFormatData();
UnitDataSink sink = new UnitDataSink(cacheData);
resource.getAllTableItemsWithFallback("", sink);
return cacheData;
}
private static final FormatWidth getRegularWidth(FormatWidth width) {
if (width == FormatWidth.NUMERIC) {
return FormatWidth.NARROW;
}
return width;
}
private QuantityFormatter getQuantityFormatter(MeasureUnit unit, FormatWidth width) {
width = getRegularWidth(width);
Map<FormatWidth, QuantityFormatter> styleToCountToFormat =
cache.unitToStyleToCountToFormat.get(unit);
QuantityFormatter countToFormat = styleToCountToFormat.get(width);
if (countToFormat != null) {
return countToFormat;
}
FormatWidth fallbackWidth = cache.widthFallback[width.ordinal()];
if (fallbackWidth != null) {
countToFormat = styleToCountToFormat.get(fallbackWidth);
if (countToFormat != null) {
return countToFormat;
}
}
throw new MissingResourceException("no formatting patterns for " + unit + " and width " + width, null, null);
}
private SimplePatternFormatter getPerUnitFormatter(MeasureUnit unit, FormatWidth width) {
width = getRegularWidth(width);
Map<FormatWidth, SimplePatternFormatter> styleToPerUnitPattern =
cache.unitToStyleToPerUnitPattern.get(unit);
if (styleToPerUnitPattern == null) {
return null;
}
SimplePatternFormatter perUnitPattern = styleToPerUnitPattern.get(width);
if (perUnitPattern != null) {
return perUnitPattern;
}
FormatWidth fallbackWidth = cache.widthFallback[width.ordinal()];
if (fallbackWidth != null) {
perUnitPattern = styleToPerUnitPattern.get(fallbackWidth);
if (perUnitPattern != null) {
return perUnitPattern;
}
}
return null;
}
private SimplePatternFormatter getPerFormatter(FormatWidth width) {
width = getRegularWidth(width);
SimplePatternFormatter perPattern = cache.styleToPerPattern.get(width);
if (perPattern != null) {
return perPattern;
}
FormatWidth fallbackWidth = cache.widthFallback[width.ordinal()];
if (fallbackWidth != null) {
perPattern = cache.styleToPerPattern.get(fallbackWidth);
if (perPattern != null) {
return perPattern;
}
}
throw new MissingResourceException("no x-per-y pattern for width " + width, null, null);
}
private int withPerUnitAndAppend(
CharSequence formatted, MeasureUnit perUnit, StringBuilder appendTo) {
int[] offsets = new int[1];
Map<FormatWidth, SimplePatternFormatter> styleToPerUnitPattern =
unitToStyleToPerUnitPattern.get(perUnit);
SimplePatternFormatter perUnitPattern = styleToPerUnitPattern.get(formatWidth);
SimplePatternFormatter perUnitPattern = getPerUnitFormatter(perUnit, formatWidth);
if (perUnitPattern != null) {
perUnitPattern.formatAndAppend(appendTo, offsets, formatted);
return offsets[0];
}
SimplePatternFormatter perPattern = styleToPerPattern.get(formatWidth);
Map<FormatWidth, QuantityFormatter> styleToCountToFormat = unitToStyleToCountToFormat.get(perUnit);
QuantityFormatter countToFormat = styleToCountToFormat.get(formatWidth);
SimplePatternFormatter perPattern = getPerFormatter(formatWidth);
QuantityFormatter countToFormat = getQuantityFormatter(perUnit, formatWidth);
String perUnitString = countToFormat.getByVariant("one").getPatternWithNoPlaceholders().trim();
perPattern.formatAndAppend(appendTo, offsets, formatted, perUnitString);
return offsets[0];
@ -882,8 +1024,7 @@ public class MeasureFormat extends UFormat {
StringBuffer formattedNumber = nf.format(n, new StringBuffer(), fpos);
String keyword = rules.select(new PluralRules.FixedDecimal(n.doubleValue(), fpos.getCountVisibleFractionDigits(), fpos.getFractionDigits()));
Map<FormatWidth, QuantityFormatter> styleToCountToFormat = unitToStyleToCountToFormat.get(unit);
QuantityFormatter countToFormat = styleToCountToFormat.get(formatWidth);
QuantityFormatter countToFormat = getQuantityFormatter(unit, formatWidth);
SimplePatternFormatter formatter = countToFormat.getByVariant(keyword);
int[] offsets = new int[1];
formatter.formatAndAppend(appendTo, offsets, formattedNumber);
@ -897,18 +1038,48 @@ public class MeasureFormat extends UFormat {
return appendTo;
}
/**
* Instances contain all MeasureFormat specific data for a particular locale.
* This data is cached. It is never copied, but is shared via shared pointers.
*
* Note: We might change the cache data to have
* an array[WIDTH_INDEX_COUNT] or EnumMap<FormatWidth, ...> of
* complete sets of unit & per patterns,
* to correspond to the resource data and its aliases.
*
* TODO: Maybe store more sparsely in general, with pointers rather than potentially-empty objects.
*/
private static final class MeasureFormatData {
MeasureFormatData(
Map<MeasureUnit, EnumMap<FormatWidth, QuantityFormatter>> unitToStyleToCountToFormat,
Map<MeasureUnit, EnumMap<FormatWidth, SimplePatternFormatter>> unitToStyleToPerUnitPattern,
EnumMap<FormatWidth, SimplePatternFormatter> styleToPerPattern) {
this.unitToStyleToCountToFormat = unitToStyleToCountToFormat;
this.unitToStyleToPerUnitPattern = unitToStyleToPerUnitPattern;
this.styleToPerPattern = styleToPerPattern;
boolean hasPerFormatter(FormatWidth width) {
return styleToPerPattern.containsKey(width);
}
final Map<MeasureUnit, EnumMap<FormatWidth, QuantityFormatter>> unitToStyleToCountToFormat;
final Map<MeasureUnit, EnumMap<FormatWidth, SimplePatternFormatter>> unitToStyleToPerUnitPattern;
final EnumMap<FormatWidth, SimplePatternFormatter> styleToPerPattern;
void setPerUnitFormatterIfAbsent(MeasureUnit unit, FormatWidth width, UResource.Value value) {
EnumMap<FormatWidth, SimplePatternFormatter> styleToPerUnitPattern =
unitToStyleToPerUnitPattern.get(unit);
if (styleToPerUnitPattern == null) {
styleToPerUnitPattern =
new EnumMap<FormatWidth, SimplePatternFormatter>(FormatWidth.class);
unitToStyleToPerUnitPattern.put(unit, styleToPerUnitPattern);
}
if (!styleToPerUnitPattern.containsKey(width)) {
styleToPerUnitPattern.put(width, SimplePatternFormatter.compile(value.getString()));
}
}
/**
* Redirection data from root-bundle, top-level sideways aliases.
* - null: initial value, just fall back to root
* - FormatWidth.WIDE/SHORT/NARROW: sideways alias for missing data
*/
final FormatWidth widthFallback[] = new FormatWidth[FormatWidth.INDEX_COUNT];
/** Measure unit -> format width -> plural form -> pattern ("{0} meters") */
final Map<MeasureUnit, EnumMap<FormatWidth, QuantityFormatter>> unitToStyleToCountToFormat =
new HashMap<MeasureUnit, EnumMap<FormatWidth, QuantityFormatter>>();
final Map<MeasureUnit, EnumMap<FormatWidth, SimplePatternFormatter>> unitToStyleToPerUnitPattern =
new HashMap<MeasureUnit, EnumMap<FormatWidth, SimplePatternFormatter>>();
final EnumMap<FormatWidth, SimplePatternFormatter> styleToPerPattern =
new EnumMap<FormatWidth, SimplePatternFormatter>(FormatWidth.class);;
}
// Wrapper around NumberFormat that provides immutability and thread-safety.

View File

@ -1,14 +1,11 @@
/*
*******************************************************************************
* Copyright (C) 2013-2014, International Business Machines Corporation and *
* others. All Rights Reserved. *
* Copyright (C) 2013-2015, International Business Machines Corporation and
* others. All Rights Reserved.
*******************************************************************************
*/
package com.ibm.icu.text;
import java.util.HashMap;
import java.util.Map;
import com.ibm.icu.impl.SimplePatternFormatter;
/**
@ -20,22 +17,36 @@ import com.ibm.icu.impl.SimplePatternFormatter;
* PluralRules and DecimalFormat. It is package-protected as it is not meant for public use.
*/
class QuantityFormatter {
private static final Map<String, Integer> INDEX_MAP = new HashMap<String, Integer>();
private static final int MAX_INDEX;
static {
int idx = 0;
// Other must be first.
INDEX_MAP.put("other", idx++);
INDEX_MAP.put("zero", idx++);
INDEX_MAP.put("one", idx++);
INDEX_MAP.put("two", idx++);
INDEX_MAP.put("few", idx++);
INDEX_MAP.put("many", idx++);
MAX_INDEX = idx;
/**
* Plural forms in index order: "other", "zero", "one", "two", "few", "many"
* "other" must be first.
*/
private static final int getPluralIndex(String pluralForm) {
if (pluralForm.equals("other")) {
return 0;
}
if (pluralForm.length() == 3) {
if (pluralForm.equals("one")) {
return 2;
}
if (pluralForm.equals("two")) {
return 3;
}
if (pluralForm.equals("few")) {
return 4;
}
}
if (pluralForm.length() == 4) {
if (pluralForm.equals("many")) {
return 5;
}
if (pluralForm.equals("zero")) {
return 1;
}
}
return -1;
}
private static final int INDEX_COUNT = 6;
/**
* Builder builds a QuantityFormatter.
@ -43,9 +54,12 @@ class QuantityFormatter {
* @author rocketman
*/
static class Builder {
private SimplePatternFormatter[] templates;
boolean hasPatterns() {
return templates != null;
}
/**
* Adds a template.
* @param variant the plural variant, e.g "zero", "one", "two", "few", "many", "other"
@ -57,9 +71,8 @@ class QuantityFormatter {
* if template has more than just the {0} placeholder.
*/
public Builder add(String variant, String template) {
ensureCapacity();
Integer idx = INDEX_MAP.get(variant);
if (idx == null) {
int idx = getPluralIndex(variant);
if (idx < 0) {
throw new IllegalArgumentException(variant);
}
SimplePatternFormatter newT = SimplePatternFormatter.compile(template);
@ -67,7 +80,9 @@ class QuantityFormatter {
throw new IllegalArgumentException(
"Extra placeholders: " + template);
}
templates[idx.intValue()] = newT;
// Keep templates == null until we add one.
ensureCapacity();
templates[idx] = newT;
return this;
}
@ -96,7 +111,7 @@ class QuantityFormatter {
private void ensureCapacity() {
if (templates == null) {
templates = new SimplePatternFormatter[MAX_INDEX];
templates = new SimplePatternFormatter[INDEX_COUNT];
}
}
@ -128,8 +143,8 @@ class QuantityFormatter {
* @return the SimplePatternFormatter
*/
public SimplePatternFormatter getByVariant(String variant) {
Integer idxObj = INDEX_MAP.get(variant);
SimplePatternFormatter template = templates[idxObj == null ? 0 : idxObj.intValue()];
int idx = getPluralIndex(variant);
SimplePatternFormatter template = templates[idx < 0 ? 0 : idx];
return template == null ? templates[0] : template;
}
@ -139,6 +154,4 @@ class QuantityFormatter {
}
return pluralRules.select(quantity);
}
}

View File

@ -1,7 +1,7 @@
/*
*******************************************************************************
* Copyright (C) 2008-2014, International Business Machines Corporation and *
* others. All Rights Reserved. *
* Copyright (C) 2008-2015, International Business Machines Corporation and
* others. All Rights Reserved.
*******************************************************************************
*/
package com.ibm.icu.dev.test.format;
@ -271,6 +271,14 @@ public class TimeUnitTest extends TestFmwk {
tuf.setNumberFormat(numfmt);
}
public void TestBritishShortHourFallback() {
// See ticket #11986 "incomplete fallback in MeasureFormat".
Object oneHour = new TimeUnitAmount(1, TimeUnit.HOUR);
ULocale en_GB = new ULocale("en_GB");
TimeUnitFormat formatter = new TimeUnitFormat(en_GB, TimeUnitFormat.ABBREVIATED_NAME);
String result = formatter.format(oneHour);
assertEquals("TestBritishShortHourFallback()", "1 hr", result);
}
private void formatParsing(TimeUnitFormat format) {
final TimeUnit[] values = TimeUnit.values();