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:
parent
724f7b5c10
commit
3f4f8032d1
@ -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() {
|
||||
}
|
||||
|
@ -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() {}
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -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();
|
||||
|
Loading…
Reference in New Issue
Block a user