private final transient ImmutableNumberFormat integerFormat;
- private static final SimpleCache<ULocale,Map<MeasureUnit, EnumMap<FormatWidth, QuantityFormatter>>> localeToUnitToStyleToCountToFormat
- = new SimpleCache<ULocale,Map<MeasureUnit, EnumMap<FormatWidth, QuantityFormatter>>>();
+ 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>();
private static final SimpleCache<ULocale, NumericFormatters> localeToNumericDurationFormatters
= new SimpleCache<ULocale,NumericFormatters>();
*/
public static MeasureFormat getInstance(ULocale locale, FormatWidth formatWidth, NumberFormat format) {
PluralRules rules = PluralRules.forLocale(locale);
- Map<MeasureUnit, EnumMap<FormatWidth, QuantityFormatter>> unitToStyleToCountToFormat;
NumericFormatters formatters = null;
- unitToStyleToCountToFormat = localeToUnitToStyleToCountToFormat.get(locale);
- if (unitToStyleToCountToFormat == null) {
- unitToStyleToCountToFormat = loadLocaleData(locale, rules);
- localeToUnitToStyleToCountToFormat.put(locale, unitToStyleToCountToFormat);
+ MeasureFormatData data = localeMeasureFormatData.get(locale);
+ if (data == null) {
+ data = loadLocaleData(locale);
+ localeMeasureFormatData.put(locale, data);
}
if (formatWidth == FormatWidth.NUMERIC) {
formatters = localeToNumericDurationFormatters.get(locale);
formatWidth,
new ImmutableNumberFormat(format),
rules,
- unitToStyleToCountToFormat,
+ data.unitToStyleToCountToFormat,
formatters,
new ImmutableNumberFormat(NumberFormat.getInstance(locale, formatWidth.getCurrencyStyle())),
- new ImmutableNumberFormat(intFormat));
+ new ImmutableNumberFormat(intFormat),
+ data.unitToStyleToPerUnitPattern,
+ data.styleToPerPattern);
}
/**
result.append(affix.substring(pos+replacement.length()));
}
}
+
+ /**
+ * Like formatMeasures but formats with a per unit.
+ *
+ * Will format to a string such as "5 kilometers, 300 meters per hour."
+ *
+ * @param appendTo the formatted string appended here.
+ * @param fieldPosition Identifies a field in the formatted text.
+ * @param perUnit for the example above would be MeasureUnit.HOUR.
+ * @param measures the measures to format.
+ * @return appendTo.
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ @Deprecated
+ public StringBuilder formatMeasuresPer(
+ StringBuilder appendTo, FieldPosition fieldPosition, MeasureUnit perUnit, Measure... measures) {
+ FieldPosition fpos = new FieldPosition(
+ fieldPosition.getFieldAttribute(), fieldPosition.getField());
+ int offset = withPerUnit(
+ formatMeasures(new StringBuilder(), fpos, measures),
+ perUnit,
+ appendTo);
+ if (fpos.getBeginIndex() != 0 || fpos.getEndIndex() != 0) {
+ fieldPosition.setBeginIndex(fpos.getBeginIndex() + offset);
+ fieldPosition.setEndIndex(fpos.getEndIndex() + offset);
+ }
+ return appendTo;
+ }
/**
* Formats a sequence of measures.
this.unitToStyleToCountToFormat,
this.numericFormatters,
this.currencyFormat,
- this.integerFormat);
+ this.integerFormat,
+ this.unitToStyleToPerUnitPattern,
+ this.styleToPerPattern);
}
private MeasureFormat(
Map<MeasureUnit, EnumMap<FormatWidth, QuantityFormatter>> unitToStyleToCountToFormat,
NumericFormatters formatters,
ImmutableNumberFormat currencyFormat,
- ImmutableNumberFormat integerFormat) {
+ ImmutableNumberFormat integerFormat,
+ Map<MeasureUnit, EnumMap<FormatWidth, SimplePatternFormatter>> unitToStyleToPerUnitPattern,
+ EnumMap<FormatWidth, SimplePatternFormatter> styleToPerPattern) {
setLocale(locale, locale);
this.formatWidth = formatWidth;
this.numberFormat = format;
this.numericFormatters = formatters;
this.currencyFormat = currencyFormat;
this.integerFormat = integerFormat;
+ this.unitToStyleToPerUnitPattern = unitToStyleToPerUnitPattern;
+ this.styleToPerPattern = styleToPerPattern;
}
MeasureFormat() {
this.numericFormatters = null;
this.currencyFormat = null;
this.integerFormat = null;
+ this.unitToStyleToPerUnitPattern = null;
+ this.styleToPerPattern = null;
}
static class NumericFormatters {
/**
* Returns formatting data for all MeasureUnits except for currency ones.
*/
- private static Map<MeasureUnit, EnumMap<FormatWidth, QuantityFormatter>> loadLocaleData(
- ULocale locale, PluralRules rules) {
+ 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) {
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);
continue;
}
String resKey = countBundle.getKey();
- if (resKey.equals("dnam") || resKey.equals("per")) {
+ 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());
}
continue;
}
}
- // now fill in the holes
- fillin:
- if (styleToCountToFormat.size() != FormatWidth.values().length) {
- QuantityFormatter fallback = styleToCountToFormat.get(FormatWidth.SHORT);
- if (fallback == null) {
- fallback = styleToCountToFormat.get(FormatWidth.WIDE);
- }
- if (fallback == null) {
- break fillin; // TODO use root
- }
- for (FormatWidth styleItem : FormatWidth.values()) {
- QuantityFormatter countToFormat = styleToCountToFormat.get(styleItem);
- if (countToFormat == null) {
- styleToCountToFormat.put(styleItem, fallback);
- }
- }
- }
+ // 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) {
+ fallback = styleMap.get(FormatWidth.WIDE);
}
- return unitToStyleToCountToFormat;
+ if (fallback == null) {
+ return false;
+ }
+ for (FormatWidth styleItem : FormatWidth.values()) {
+ T item = styleMap.get(styleItem);
+ if (item == null) {
+ styleMap.put(styleItem, fallback);
+ }
+ }
+ return true;
+ }
+
+ private int withPerUnit(CharSequence formatted, MeasureUnit perUnit, StringBuilder appendTo) {
+ int[] offsets = new int[1];
+ Map<FormatWidth, SimplePatternFormatter> styleToPerUnitPattern =
+ unitToStyleToPerUnitPattern.get(perUnit);
+ SimplePatternFormatter perUnitPattern = styleToPerUnitPattern.get(formatWidth);
+ if (perUnitPattern != null) {
+ perUnitPattern.format(appendTo, offsets, formatted);
+ return offsets[0];
+ }
+ SimplePatternFormatter perPattern = styleToPerPattern.get(formatWidth);
+ Map<FormatWidth, QuantityFormatter> styleToCountToFormat = unitToStyleToCountToFormat.get(perUnit);
+ QuantityFormatter countToFormat = styleToCountToFormat.get(formatWidth);
+ String perUnitString = countToFormat.getByVariant("one").getPatternWithNoPlaceholders().trim();
+ perPattern.format(appendTo, offsets, formatted, perUnitString);
+ return offsets[0];
}
private String formatMeasure(Measure measure, ImmutableNumberFormat nf) {
}
return appendTo;
}
+
+ 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;
+ }
+ final Map<MeasureUnit, EnumMap<FormatWidth, QuantityFormatter>> unitToStyleToCountToFormat;
+ final Map<MeasureUnit, EnumMap<FormatWidth, SimplePatternFormatter>> unitToStyleToPerUnitPattern;
+ final EnumMap<FormatWidth, SimplePatternFormatter> styleToPerPattern;
+ }
// Wrapper around NumberFormat that provides immutability and thread-safety.
private static final class ImmutableNumberFormat {
import com.ibm.icu.dev.test.TestFmwk;
import com.ibm.icu.dev.test.serializable.SerializableTest;
+import com.ibm.icu.impl.DontCareFieldPosition;
import com.ibm.icu.impl.Utility;
import com.ibm.icu.math.BigDecimal;
import com.ibm.icu.text.MeasureFormat;
new Measure(2.3, MeasureUnit.INCH)));
}
}
+
+ public void testMultiplesPer() {
+ Object[][] data = new Object[][] {
+ {ULocale.ENGLISH, FormatWidth.WIDE, MeasureUnit.SECOND, "2 miles, 1 foot, 2.3 inches per second"},
+ {ULocale.ENGLISH, FormatWidth.SHORT, MeasureUnit.SECOND, "2 mi, 1 ft, 2.3 inps"},
+ {ULocale.ENGLISH, FormatWidth.NARROW, MeasureUnit.SECOND, "2mi 1\u2032 2.3\u2033/s"},
+ {ULocale.ENGLISH, FormatWidth.WIDE, MeasureUnit.MINUTE, "2 miles, 1 foot, 2.3 inches per minute"},
+ {ULocale.ENGLISH, FormatWidth.SHORT, MeasureUnit.MINUTE, "2 mi, 1 ft, 2.3 in/min"},
+ {ULocale.ENGLISH, FormatWidth.NARROW, MeasureUnit.MINUTE, "2mi 1\u2032 2.3\u2033/m"},
+ };
+ for (Object[] row : data) {
+ MeasureFormat mf = MeasureFormat.getInstance(
+ (ULocale) row[0], (FormatWidth) row[1]);
+ assertEquals(
+ "testMultiples",
+ row[3],
+ mf.formatMeasuresPer(
+ new StringBuilder(),
+ DontCareFieldPosition.INSTANCE,
+ (MeasureUnit) row[2],
+ new Measure(2, MeasureUnit.MILE),
+ new Measure(1, MeasureUnit.FOOT),
+ new Measure(2.3, MeasureUnit.INCH)).toString());
+ }
+ }
public void testGram() {
MeasureFormat mf = MeasureFormat.getInstance(ULocale.ENGLISH, FormatWidth.SHORT);
}
+ public void testFieldPositionMultipleWithPer() {
+ MeasureFormat fmt = MeasureFormat.getInstance(
+ ULocale.ENGLISH, FormatWidth.SHORT);
+ FieldPosition pos = new FieldPosition(NumberFormat.Field.INTEGER);
+ String result = fmt.formatMeasuresPer(
+ new StringBuilder(),
+ pos,
+ MeasureUnit.SECOND,
+ new Measure(354, MeasureUnit.METER),
+ new Measure(23, MeasureUnit.CENTIMETER)).toString();
+ assertEquals("result", "354 m, 23 cmps", result);
+
+ // According to javadocs for {@link Format#format} FieldPosition is set to
+ // beginning and end of first such field encountered instead of the last
+ // such field encountered.
+ assertEquals("beginIndex", 0, pos.getBeginIndex());
+ assertEquals("endIndex", 3, pos.getEndIndex());
+
+ pos = new FieldPosition(NumberFormat.Field.DECIMAL_SEPARATOR);
+ result = fmt.formatMeasuresPer(
+ new StringBuilder("123456: "),
+ pos,
+ MeasureUnit.SECOND,
+ new Measure(354, MeasureUnit.METER),
+ new Measure(23, MeasureUnit.CENTIMETER),
+ new Measure(5.4, MeasureUnit.MILLIMETER)).toString();
+ assertEquals("result", "123456: 354 m, 23 cm, 5.4 mmps", result);
+ assertEquals("beginIndex", 23, pos.getBeginIndex());
+ assertEquals("endIndex", 24, pos.getEndIndex());
+
+ pos = new FieldPosition(NumberFormat.Field.INTEGER);
+ result = fmt.formatMeasuresPer(
+ new StringBuilder(),
+ pos,
+ MeasureUnit.MINUTE,
+ new Measure(354, MeasureUnit.METER),
+ new Measure(23, MeasureUnit.CENTIMETER)).toString();
+ assertEquals("result", "354 m, 23 cm/min", result);
+
+ // According to javadocs for {@link Format#format} FieldPosition is set to
+ // beginning and end of first such field encountered instead of the last
+ // such field encountered.
+ assertEquals("beginIndex", 0, pos.getBeginIndex());
+ assertEquals("endIndex", 3, pos.getEndIndex());
+
+ pos = new FieldPosition(NumberFormat.Field.DECIMAL_SEPARATOR);
+ result = fmt.formatMeasuresPer(
+ new StringBuilder("123456: "),
+ pos,
+ MeasureUnit.MINUTE,
+ new Measure(354, MeasureUnit.METER),
+ new Measure(23, MeasureUnit.CENTIMETER),
+ new Measure(5.4, MeasureUnit.MILLIMETER)).toString();
+ assertEquals("result", "123456: 354 m, 23 cm, 5.4 mm/min", result);
+ assertEquals("beginIndex", 23, pos.getBeginIndex());
+ assertEquals("endIndex", 24, pos.getEndIndex());
+ }
+
public void testOldFormatWithList() {
List<Measure> measures = new ArrayList<Measure>(2);
measures.add(new Measure(5, MeasureUnit.ACRE));