import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.ObjectStreamException;
+import java.math.RoundingMode;
import java.text.FieldPosition;
import java.text.ParsePosition;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
-import java.util.EnumMap;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import com.ibm.icu.impl.ICUResourceBundle;
import com.ibm.icu.impl.SimpleCache;
import com.ibm.icu.impl.SimpleFormatterImpl;
-import com.ibm.icu.impl.StandardPlural;
-import com.ibm.icu.impl.UResource;
-import com.ibm.icu.math.BigDecimal;
-import com.ibm.icu.text.PluralRules.Factory;
+import com.ibm.icu.impl.number.LongNameHandler;
+import com.ibm.icu.number.FormattedNumber;
+import com.ibm.icu.number.LocalizedNumberFormatter;
+import com.ibm.icu.number.NumberFormatter;
+import com.ibm.icu.number.NumberFormatter.UnitWidth;
+import com.ibm.icu.number.Rounder;
+import com.ibm.icu.text.ListFormatter.FormattedListBuilder;
import com.ibm.icu.util.Currency;
-import com.ibm.icu.util.CurrencyAmount;
-import com.ibm.icu.util.ICUException;
+import com.ibm.icu.util.ICUUncheckedIOException;
import com.ibm.icu.util.Measure;
import com.ibm.icu.util.MeasureUnit;
import com.ibm.icu.util.TimeZone;
// 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;
// PluralRules is documented as being immutable which implies thread-safety.
private final transient NumericFormatters numericFormatters;
- private final transient ImmutableNumberFormat currencyFormat;
+ private final transient NumberFormat numberFormat;
- private final transient ImmutableNumberFormat integerFormat;
-
- private static final SimpleCache<ULocale, MeasureFormatData> localeMeasureFormatData = new SimpleCache<ULocale, MeasureFormatData>();
+ private final transient LocalizedNumberFormatter numberFormatter;
private static final SimpleCache<ULocale, NumericFormatters> localeToNumericDurationFormatters = new SimpleCache<ULocale, NumericFormatters>();
*
* @stable ICU 53
*/
- WIDE(ListFormatter.Style.DURATION, NumberFormat.PLURALCURRENCYSTYLE),
+ WIDE(ListFormatter.Style.DURATION, UnitWidth.FULL_NAME, UnitWidth.FULL_NAME),
/**
* Abbreviate when possible.
*
* @stable ICU 53
*/
- SHORT(ListFormatter.Style.DURATION_SHORT, NumberFormat.ISOCURRENCYSTYLE),
+ SHORT(ListFormatter.Style.DURATION_SHORT, UnitWidth.SHORT, UnitWidth.ISO_CODE),
/**
* Brief. Use only a symbol for the unit when possible.
*
* @stable ICU 53
*/
- NARROW(ListFormatter.Style.DURATION_NARROW, NumberFormat.CURRENCYSTYLE),
+ NARROW(ListFormatter.Style.DURATION_NARROW, UnitWidth.NARROW, UnitWidth.SHORT),
/**
* Identical to NARROW except when formatMeasures is called with an hour and minute; minute and
*
* @stable ICU 53
*/
- NUMERIC(ListFormatter.Style.DURATION_NARROW, NumberFormat.CURRENCYSTYLE);
+ NUMERIC(ListFormatter.Style.DURATION_NARROW, UnitWidth.NARROW, UnitWidth.SHORT),
- // Be sure to update the toFormatWidth and fromFormatWidth() functions
- // when adding an enum value.
- private static final int INDEX_COUNT = 3; // NARROW.ordinal() + 1
+ /**
+ * The default format width for getCurrencyFormat(), which is to show the symbol for currency
+ * (UnitWidth.SHORT) but wide for other units.
+ *
+ * @internal Use {@link #getCurrencyFormat()}
+ * @deprecated ICU 61 This API is ICU internal only.
+ */
+ @Deprecated
+ DEFAULT_CURRENCY(ListFormatter.Style.DURATION, UnitWidth.FULL_NAME, UnitWidth.SHORT);
private final ListFormatter.Style listFormatterStyle;
- private final int currencyStyle;
- private FormatWidth(ListFormatter.Style style, int currencyStyle) {
+ /**
+ * The {@link UnitWidth} (used for newer NumberFormatter API) that corresponds to this
+ * FormatWidth (used for the older APIs) for all units except currencies.
+ */
+ final UnitWidth unitWidth;
+
+ /**
+ * The {@link UnitWidth} (used for newer NumberFormatter API) that corresponds to this
+ * FormatWidth (used for the older APIs) for currencies.
+ */
+ final UnitWidth currencyWidth;
+
+ private FormatWidth(ListFormatter.Style style, UnitWidth unitWidth, UnitWidth currencyWidth) {
this.listFormatterStyle = style;
- this.currencyStyle = currencyStyle;
+ this.unitWidth = unitWidth;
+ this.currencyWidth = currencyWidth;
}
ListFormatter.Style getListFormatterStyle() {
return listFormatterStyle;
}
-
- int getCurrencyStyle() {
- return currencyStyle;
- }
}
/**
ULocale locale,
FormatWidth formatWidth,
NumberFormat format) {
- PluralRules rules = PluralRules.forLocale(locale);
- NumericFormatters formatters = null;
- MeasureFormatData data = localeMeasureFormatData.get(locale);
- if (data == null) {
- data = loadLocaleData(locale);
- localeMeasureFormatData.put(locale, data);
- }
- if (formatWidth == FormatWidth.NUMERIC) {
- formatters = localeToNumericDurationFormatters.get(locale);
- if (formatters == null) {
- formatters = loadNumericFormatters(locale);
- localeToNumericDurationFormatters.put(locale, formatters);
- }
- }
- NumberFormat intFormat = NumberFormat.getInstance(locale);
- intFormat.setMaximumFractionDigits(0);
- intFormat.setMinimumFractionDigits(0);
- intFormat.setRoundingMode(BigDecimal.ROUND_DOWN);
- return new MeasureFormat(locale,
- data,
- formatWidth,
- new ImmutableNumberFormat(format),
- rules,
- formatters,
- new ImmutableNumberFormat(
- NumberFormat.getInstance(locale, formatWidth.getCurrencyStyle())),
- new ImmutableNumberFormat(intFormat));
+ return new MeasureFormat(locale, formatWidth, format, null, null);
}
/**
* must be a Collection<? extends Measure>, Measure[], or Measure object.
* @param toAppendTo
* Formatted string appended here.
- * @param pos
+ * @param fpos
* Identifies a field in the formatted text.
* @see java.text.Format#format(java.lang.Object, java.lang.StringBuffer, java.text.FieldPosition)
*
* @stable ICU53
*/
@Override
- public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) {
+ public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition fpos) {
int prevLength = toAppendTo.length();
- FieldPosition fpos = new FieldPosition(pos.getFieldAttribute(), pos.getField());
+ fpos.setBeginIndex(0);
+ fpos.setEndIndex(0);
if (obj instanceof Collection) {
Collection<?> coll = (Collection<?>) obj;
Measure[] measures = new Measure[coll.size()];
}
measures[idx++] = (Measure) o;
}
- toAppendTo.append(formatMeasures(new StringBuilder(), fpos, measures));
+ formatMeasuresInternal(toAppendTo, fpos, measures);
} else if (obj instanceof Measure[]) {
- toAppendTo.append(formatMeasures(new StringBuilder(), fpos, (Measure[]) obj));
+ formatMeasuresInternal(toAppendTo, fpos, (Measure[]) obj);
} else if (obj instanceof Measure) {
- toAppendTo.append(formatMeasure((Measure) obj, numberFormat, new StringBuilder(), fpos));
+ FormattedNumber result = formatMeasure((Measure) obj);
+ result.populateFieldPosition(fpos); // No offset: toAppendTo.length() is considered below
+ result.appendTo(toAppendTo);
} else {
throw new IllegalArgumentException(obj.toString());
}
- if (fpos.getBeginIndex() != 0 || fpos.getEndIndex() != 0) {
- pos.setBeginIndex(fpos.getBeginIndex() + prevLength);
- pos.setEndIndex(fpos.getEndIndex() + prevLength);
+ if (prevLength > 0 && fpos.getEndIndex() != 0) {
+ fpos.setBeginIndex(fpos.getBeginIndex() + prevLength);
+ fpos.setEndIndex(fpos.getEndIndex() + prevLength);
}
return toAppendTo;
}
return formatMeasures(new StringBuilder(), DontCareFieldPosition.INSTANCE, measures).toString();
}
- /**
- * Format a range of measures, such as "3.4-5.1 meters". It is the caller’s responsibility to have
- * the appropriate values in appropriate order, and using the appropriate Number values. <br>
- * Note: If the format doesn’t have enough decimals, or lowValue ≥ highValue, the result will be a
- * degenerate range, like “5-5 meters”. <br>
- * Currency Units are not yet supported.
- *
- * @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();
- final boolean isCurrency = unit instanceof Currency;
-
- UFieldPosition lowFpos = new UFieldPosition();
- UFieldPosition highFpos = new UFieldPosition();
- StringBuffer lowFormatted = null;
- StringBuffer highFormatted = null;
-
- if (isCurrency) {
- Currency currency = (Currency) unit;
- int fracDigits = currency.getDefaultFractionDigits();
- int maxFrac = numberFormat.nf.getMaximumFractionDigits();
- int minFrac = numberFormat.nf.getMinimumFractionDigits();
- if (fracDigits != maxFrac || fracDigits != minFrac) {
- DecimalFormat currentNumberFormat = (DecimalFormat) numberFormat.get();
- currentNumberFormat.setMaximumFractionDigits(fracDigits);
- currentNumberFormat.setMinimumFractionDigits(fracDigits);
- lowFormatted = currentNumberFormat.format(lowNumber, new StringBuffer(), lowFpos);
- highFormatted = currentNumberFormat.format(highNumber, new StringBuffer(), highFpos);
- }
- }
- if (lowFormatted == null) {
- lowFormatted = numberFormat.format(lowNumber, new StringBuffer(), lowFpos);
- highFormatted = numberFormat.format(highNumber, new StringBuffer(), highFpos);
- }
-
- final double lowDouble = lowNumber.doubleValue();
- String keywordLow = rules.select(new PluralRules.FixedDecimal(lowDouble,
- lowFpos.getCountVisibleFractionDigits(),
- lowFpos.getFractionDigits()));
-
- final double highDouble = highNumber.doubleValue();
- String keywordHigh = rules.select(new PluralRules.FixedDecimal(highDouble,
- highFpos.getCountVisibleFractionDigits(),
- highFpos.getFractionDigits()));
-
- final PluralRanges pluralRanges = Factory.getDefaultFactory().getPluralRanges(getLocale());
- StandardPlural resolvedPlural = pluralRanges.get(StandardPlural.fromString(keywordLow),
- StandardPlural.fromString(keywordHigh));
-
- String rangeFormatter = getRangeFormat(getLocale(), formatWidth);
- String formattedNumber = SimpleFormatterImpl
- .formatCompiledPattern(rangeFormatter, lowFormatted, highFormatted);
-
- if (isCurrency) {
- // Nasty hack
- currencyFormat.format(1d); // have to call this for the side effect
-
- Currency currencyUnit = (Currency) unit;
- StringBuilder result = new StringBuilder();
- appendReplacingCurrency(currencyFormat.getPrefix(lowDouble >= 0),
- currencyUnit,
- resolvedPlural,
- result);
- result.append(formattedNumber);
- appendReplacingCurrency(currencyFormat.getSuffix(highDouble >= 0),
- currencyUnit,
- resolvedPlural,
- result);
- return result.toString();
- // StringBuffer buffer = new StringBuffer();
- // CurrencyAmount currencyLow = (CurrencyAmount) lowValue;
- // CurrencyAmount currencyHigh = (CurrencyAmount) highValue;
- // FieldPosition pos = new FieldPosition(NumberFormat.INTEGER_FIELD);
- // currencyFormat.format(currencyLow, buffer, pos);
- // int startOfInteger = pos.getBeginIndex();
- // StringBuffer buffer2 = new StringBuffer();
- // FieldPosition pos2 = new FieldPosition(0);
- // currencyFormat.format(currencyHigh, buffer2, pos2);
- } else {
- String formatter = getPluralFormatter(lowValue.getUnit(),
- formatWidth,
- resolvedPlural.ordinal());
- return SimpleFormatterImpl.formatCompiledPattern(formatter, formattedNumber);
- }
- }
-
- private void appendReplacingCurrency(
- String affix,
- Currency unit,
- StandardPlural resolvedPlural,
- StringBuilder result) {
- String replacement = "¤";
- int pos = affix.indexOf(replacement);
- if (pos < 0) {
- replacement = "XXX";
- pos = affix.indexOf(replacement);
- }
- if (pos < 0) {
- result.append(affix);
- } else {
- // for now, just assume single
- result.append(affix.substring(0, pos));
- // we have a mismatch between the number style and the currency style, so remap
- int currentStyle = formatWidth.getCurrencyStyle();
- if (currentStyle == NumberFormat.ISOCURRENCYSTYLE) {
- result.append(unit.getCurrencyCode());
- } else {
- result.append(unit.getName(currencyFormat.nf.getLocale(ULocale.ACTUAL_LOCALE),
- currentStyle == NumberFormat.CURRENCYSTYLE ? Currency.SYMBOL_NAME
- : Currency.PLURAL_LONG_NAME,
- resolvedPlural.getKeyword(),
- null));
- }
- result.append(affix.substring(pos + replacement.length()));
- }
- }
+ // NOTE: For formatMeasureRange(), see http://bugs.icu-project.org/trac/ticket/12454
/**
* Formats a single measure per unit.
MeasureUnit perUnit,
StringBuilder appendTo,
FieldPosition pos) {
- MeasureUnit resolvedUnit = MeasureUnit.resolveUnitPerUnit(measure.getUnit(), perUnit);
- if (resolvedUnit != null) {
- Measure newMeasure = new Measure(measure.getNumber(), resolvedUnit);
- return formatMeasure(newMeasure, numberFormat, appendTo, pos);
- }
- FieldPosition fpos = new FieldPosition(pos.getFieldAttribute(), pos.getField());
- int offset = withPerUnitAndAppend(
- formatMeasure(measure, numberFormat, new StringBuilder(), fpos),
- perUnit,
- appendTo);
- if (fpos.getBeginIndex() != 0 || fpos.getEndIndex() != 0) {
- pos.setBeginIndex(fpos.getBeginIndex() + offset);
- pos.setEndIndex(fpos.getEndIndex() + offset);
- }
+ FormattedNumber result = getUnitFormatterFromCache(NUMBER_FORMATTER_STANDARD,
+ measure.getUnit(),
+ perUnit).format(measure.getNumber());
+ result.populateFieldPosition(pos, appendTo.length());
+ result.appendTo(appendTo);
return appendTo;
}
*
* @param appendTo
* the formatted string appended here.
- * @param fieldPosition
+ * @param fpos
* Identifies a field in the formatted text.
* @param measures
* the measures to format.
*/
public StringBuilder formatMeasures(
StringBuilder appendTo,
+ FieldPosition fpos,
+ Measure... measures) {
+ int prevLength = appendTo.length();
+ formatMeasuresInternal(appendTo, fpos, measures);
+ if (prevLength > 0 && fpos.getEndIndex() > 0) {
+ fpos.setBeginIndex(fpos.getBeginIndex() + prevLength);
+ fpos.setEndIndex(fpos.getEndIndex() + prevLength);
+ }
+ return appendTo;
+ }
+
+ private void formatMeasuresInternal(
+ Appendable appendTo,
FieldPosition fieldPosition,
Measure... measures) {
// fast track for trivial cases
if (measures.length == 0) {
- return appendTo;
+ return;
}
if (measures.length == 1) {
- return formatMeasure(measures[0], numberFormat, appendTo, fieldPosition);
+ FormattedNumber result = formatMeasure(measures[0]);
+ result.populateFieldPosition(fieldPosition);
+ result.appendTo(appendTo);
+ return;
}
if (formatWidth == FormatWidth.NUMERIC) {
// track.
Number[] hms = toHMS(measures);
if (hms != null) {
- return formatNumeric(hms, appendTo);
+ formatNumeric(hms, appendTo);
+ return;
}
}
ListFormatter listFormatter = ListFormatter.getInstance(getLocale(),
formatWidth.getListFormatterStyle());
if (fieldPosition != DontCareFieldPosition.INSTANCE) {
- return formatMeasuresSlowTrack(listFormatter, appendTo, fieldPosition, measures);
+ formatMeasuresSlowTrack(listFormatter, appendTo, fieldPosition, measures);
+ return;
}
// Fast track: No field position.
String[] results = new String[measures.length];
for (int i = 0; i < measures.length; i++) {
- results[i] = formatMeasure(measures[i],
- i == measures.length - 1 ? numberFormat : integerFormat);
+ if (i == measures.length - 1) {
+ results[i] = formatMeasure(measures[i]).toString();
+ } else {
+ results[i] = formatMeasureInteger(measures[i]).toString();
+ }
}
- return appendTo.append(listFormatter.format((Object[]) results));
-
+ FormattedListBuilder builder = listFormatter.format(Arrays.asList(results), -1);
+ builder.appendTo(appendTo);
}
/**
* @stable ICU 58
*/
public String getUnitDisplayName(MeasureUnit unit) {
- FormatWidth width = getRegularWidth(formatWidth);
- Map<FormatWidth, String> styleToDnam = cache.unitToStyleToDnam.get(unit);
- if (styleToDnam == null) {
- return null;
- }
-
- String dnam = styleToDnam.get(width);
- if (dnam != null) {
- return dnam;
- }
- FormatWidth fallbackWidth = cache.widthFallback[width.ordinal()];
- if (fallbackWidth != null) {
- dnam = styleToDnam.get(fallbackWidth);
- }
- return dnam;
+ return LongNameHandler.getUnitDisplayName(getLocale(), unit, formatWidth.unitWidth);
}
/**
* @stable ICU 53
*/
public NumberFormat getNumberFormat() {
- return numberFormat.get();
+ return (NumberFormat) numberFormat.clone();
}
/**
MeasureFormat withNumberFormat(NumberFormat format) {
return new MeasureFormat(getLocale(),
- this.cache,
this.formatWidth,
- new ImmutableNumberFormat(format),
+ format,
this.rules,
- this.numericFormatters,
- this.currencyFormat,
- this.integerFormat);
+ this.numericFormatters);
+ }
+
+ MeasureFormat(ULocale locale, FormatWidth formatWidth) {
+ this(locale, formatWidth, null, null, null);
}
private MeasureFormat(
ULocale locale,
- MeasureFormatData data,
FormatWidth formatWidth,
- ImmutableNumberFormat format,
+ NumberFormat numberFormat,
PluralRules rules,
- NumericFormatters formatters,
- ImmutableNumberFormat currencyFormat,
- ImmutableNumberFormat integerFormat) {
+ NumericFormatters formatters) {
+ // Needed for getLocale(ULocale.VALID_LOCALE).
setLocale(locale, locale);
- this.cache = data;
this.formatWidth = formatWidth;
- this.numberFormat = format;
+
+ if (rules == null) {
+ rules = PluralRules.forLocale(locale);
+ }
this.rules = rules;
+
+ if (numberFormat == null) {
+ numberFormat = NumberFormat.getInstance(locale);
+ } else {
+ numberFormat = (NumberFormat) numberFormat.clone();
+ }
+ this.numberFormat = numberFormat;
+
+ if (formatters == null && formatWidth == FormatWidth.NUMERIC) {
+ formatters = localeToNumericDurationFormatters.get(locale);
+ if (formatters == null) {
+ formatters = loadNumericFormatters(locale);
+ localeToNumericDurationFormatters.put(locale, formatters);
+ }
+ }
this.numericFormatters = formatters;
- this.currencyFormat = currencyFormat;
- this.integerFormat = integerFormat;
+
+ if (!(numberFormat instanceof DecimalFormat)) {
+ throw new IllegalArgumentException();
+ }
+ numberFormatter = ((DecimalFormat) numberFormat).toNumberFormatter()
+ .unitWidth(formatWidth.unitWidth);
}
- MeasureFormat() {
- // Make compiler happy by setting final fields to null.
- this.cache = null;
- this.formatWidth = null;
- this.numberFormat = null;
- this.rules = null;
- this.numericFormatters = null;
- this.currencyFormat = null;
- this.integerFormat = null;
+ MeasureFormat(
+ ULocale locale,
+ FormatWidth formatWidth,
+ NumberFormat numberFormat,
+ PluralRules rules) {
+ this(locale, formatWidth, numberFormat, rules, null);
+ if (formatWidth == FormatWidth.NUMERIC) {
+ throw new IllegalArgumentException(
+ "The format width 'numeric' is not allowed by this constructor");
+ }
}
static class NumericFormatters {
loadNumericDurationFormat(r, "hms"));
}
- /**
- * 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 final class UnitDataSink extends UResource.Sink {
- void setFormatterIfAbsent(int index, UResource.Value value, int minPlaceholders) {
- if (patterns == null) {
- EnumMap<FormatWidth, String[]> styleToPatterns = cacheData.unitToStyleToPatterns
- .get(unit);
- if (styleToPatterns == null) {
- styleToPatterns = new EnumMap<FormatWidth, String[]>(FormatWidth.class);
- cacheData.unitToStyleToPatterns.put(unit, styleToPatterns);
- } else {
- patterns = styleToPatterns.get(width);
- }
- if (patterns == null) {
- patterns = new String[MeasureFormatData.PATTERN_COUNT];
- styleToPatterns.put(width, patterns);
- }
- }
- if (patterns[index] == null) {
- patterns[index] = SimpleFormatterImpl
- .compileToStringMinMaxArguments(value.getString(), sb, minPlaceholders, 1);
- }
- }
-
- void setDnamIfAbsent(UResource.Value value) {
- EnumMap<FormatWidth, String> styleToDnam = cacheData.unitToStyleToDnam.get(unit);
- if (styleToDnam == null) {
- styleToDnam = new EnumMap<FormatWidth, String>(FormatWidth.class);
- cacheData.unitToStyleToDnam.put(unit, styleToDnam);
- }
- if (styleToDnam.get(width) == null) {
- styleToDnam.put(width, value.getString());
- }
- }
-
- /**
- * Consume a display pattern. For example, unitsShort/duration/hour contains other{"{0} hrs"}.
- */
- void consumePattern(UResource.Key key, UResource.Value value) {
- if (key.contentEquals("dnam")) {
- // The display name for the unit in the current width.
- setDnamIfAbsent(value);
- } else if (key.contentEquals("per")) {
- // For example, "{0}/h".
- setFormatterIfAbsent(MeasureFormatData.PER_UNIT_INDEX, value, 1);
- } else {
- // The key must be one of the plural form strings. For example:
- // one{"{0} hr"}
- // other{"{0} hrs"}
- setFormatterIfAbsent(StandardPlural.indexFromString(key), value, 0);
- }
- }
-
- /**
- * Consume a table of per-unit tables. For example, unitsShort/duration contains tables for
- * duration-unit subtypes day & hour.
- */
- void consumeSubtypeTable(UResource.Key key, UResource.Value value) {
- unit = MeasureUnit.internalGetInstance(type, key.toString()); // never null
- // Trigger a fresh lookup of the patterns for this unit+width.
- patterns = null;
-
- // We no longer handle units like "coordinate" here (which do not have plural variants)
- if (value.getType() == ICUResourceBundle.TABLE) {
- // Units that have plural variants
- UResource.Table patternTableTable = value.getTable();
- for (int i = 0; patternTableTable.getKeyAndValue(i, key, value); i++) {
- consumePattern(key, value);
- }
- } else {
- throw new ICUException("Data for unit '" + unit + "' is in an unknown format");
- }
- }
-
- /**
- * Consume compound x-per-y display pattern. For example, unitsShort/compound/per may be
- * "{0}/{1}".
- */
- void consumeCompoundPattern(UResource.Key key, UResource.Value value) {
- if (key.contentEquals("per")) {
- cacheData.styleToPerPattern.put(width,
- SimpleFormatterImpl.compileToStringMinMaxArguments(value.getString(), sb, 2, 2));
- }
- }
+ /// BEGIN NUMBER FORMATTER CACHING MACHINERY ///
- /**
- * Consume a table of unit type tables. For example, unitsShort contains tables for area &
- * duration. It also contains a table for the compound/per pattern.
- */
- void consumeUnitTypesTable(UResource.Key key, UResource.Value value) {
- if (key.contentEquals("currency")) {
- // Skip.
- } else if (key.contentEquals("compound")) {
- if (!cacheData.hasPerFormatter(width)) {
- UResource.Table compoundTable = value.getTable();
- for (int i = 0; compoundTable.getKeyAndValue(i, key, value); i++) {
- consumeCompoundPattern(key, value);
- }
- }
- } else if (key.contentEquals("coordinate")) {
- // special handling but we need to determine what that is
- } else {
- type = key.toString();
- UResource.Table subtypeTable = value.getTable();
- for (int i = 0; subtypeTable.getKeyAndValue(i, key, value); i++) {
- consumeSubtypeTable(key, value);
- }
- }
- }
+ static final int NUMBER_FORMATTER_STANDARD = 1;
+ static final int NUMBER_FORMATTER_CURRENCY = 2;
+ static final int NUMBER_FORMATTER_INTEGER = 3;
- UnitDataSink(MeasureFormatData outputData) {
- cacheData = outputData;
- }
-
- void consumeAlias(UResource.Key key, UResource.Value value) {
- // Handle aliases like
- // units:alias{"/LOCALE/unitsShort"}
- // which should only occur in the root bundle.
- 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;
- }
-
- public void consumeTable(UResource.Key key, UResource.Value value) {
- if ((width = widthFromKey(key)) != null) {
- UResource.Table unitTypesTable = value.getTable();
- for (int i = 0; unitTypesTable.getKeyAndValue(i, key, value); i++) {
- consumeUnitTypesTable(key, value);
- }
- }
- }
+ static class NumberFormatterCacheEntry {
+ int type;
+ MeasureUnit unit;
+ MeasureUnit perUnit;
+ LocalizedNumberFormatter formatter;
+ }
- 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;
- }
+ // formatter1 is most recently used.
+ private transient NumberFormatterCacheEntry formatter1 = null;
+ private transient NumberFormatterCacheEntry formatter2 = null;
+ private transient NumberFormatterCacheEntry formatter3 = null;
+
+ private synchronized LocalizedNumberFormatter getUnitFormatterFromCache(
+ int type,
+ MeasureUnit unit,
+ MeasureUnit perUnit) {
+ if (formatter1 != null) {
+ if (formatter1.type == type && formatter1.unit == unit && formatter1.perUnit == perUnit) {
+ return formatter1.formatter;
}
- 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;
+ if (formatter2 != null) {
+ if (formatter2.type == type
+ && formatter2.unit == unit
+ && formatter2.perUnit == perUnit) {
+ return formatter2.formatter;
}
- }
- return null;
- }
-
- @Override
- public void put(UResource.Key key, UResource.Value value, boolean noFallback) {
- // Main entry point to sink
- UResource.Table widthsTable = value.getTable();
- for (int i = 0; widthsTable.getKeyAndValue(i, key, value); i++) {
- if (value.getType() == ICUResourceBundle.ALIAS) {
- consumeAlias(key, value);
- } else {
- consumeTable(key, value);
+ if (formatter3 != null) {
+ if (formatter3.type == type
+ && formatter3.unit == unit
+ && formatter3.perUnit == perUnit) {
+ return formatter3.formatter;
+ }
}
}
}
- // Output data.
- MeasureFormatData cacheData;
-
- // Path to current data.
- FormatWidth width;
- String type;
- MeasureUnit unit;
-
- // Temporary
- StringBuilder sb = new StringBuilder();
- String[] patterns;
- }
-
- /**
- * 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.getAllItemsWithFallback("", sink);
- return cacheData;
- }
-
- private static final FormatWidth getRegularWidth(FormatWidth width) {
- if (width == FormatWidth.NUMERIC) {
- return FormatWidth.NARROW;
- }
- return width;
- }
-
- private String getFormatterOrNull(MeasureUnit unit, FormatWidth width, int index) {
- width = getRegularWidth(width);
- Map<FormatWidth, String[]> styleToPatterns = cache.unitToStyleToPatterns.get(unit);
- String[] patterns = styleToPatterns.get(width);
- if (patterns != null && patterns[index] != null) {
- return patterns[index];
- }
- FormatWidth fallbackWidth = cache.widthFallback[width.ordinal()];
- if (fallbackWidth != null) {
- patterns = styleToPatterns.get(fallbackWidth);
- if (patterns != null && patterns[index] != null) {
- return patterns[index];
- }
- }
- return null;
- }
-
- private String getFormatter(MeasureUnit unit, FormatWidth width, int index) {
- String pattern = getFormatterOrNull(unit, width, index);
- if (pattern == null) {
- throw new MissingResourceException(
- "no formatting pattern for " + unit + ", width " + width + ", index " + index,
- null,
- null);
- }
- return pattern;
- }
-
- /**
- * @internal
- * @deprecated This API is ICU internal only.
- */
- @Deprecated
- public String getPluralFormatter(MeasureUnit unit, FormatWidth width, int index) {
- if (index != StandardPlural.OTHER_INDEX) {
- String pattern = getFormatterOrNull(unit, width, index);
- if (pattern != null) {
- return pattern;
- }
- }
- return getFormatter(unit, width, StandardPlural.OTHER_INDEX);
+ // No hit; create a new formatter.
+ LocalizedNumberFormatter formatter;
+ if (type == NUMBER_FORMATTER_STANDARD) {
+ formatter = getNumberFormatter().unit(unit).perUnit(perUnit)
+ .unitWidth(formatWidth.unitWidth);
+ } else if (type == NUMBER_FORMATTER_CURRENCY) {
+ formatter = NumberFormatter.withLocale(getLocale()).unit(unit).perUnit(perUnit)
+ .unitWidth(formatWidth.currencyWidth);
+ } else {
+ assert type == NUMBER_FORMATTER_INTEGER;
+ formatter = getNumberFormatter().unit(unit).perUnit(perUnit).unitWidth(formatWidth.unitWidth)
+ .rounding(Rounder.integer().withMode(RoundingMode.DOWN));
+ }
+ formatter3 = formatter2;
+ formatter2 = formatter1;
+ formatter1 = new NumberFormatterCacheEntry();
+ formatter1.type = type;
+ formatter1.unit = unit;
+ formatter1.perUnit = perUnit;
+ formatter1.formatter = formatter;
+ return formatter;
}
- private String getPerFormatter(FormatWidth width) {
- width = getRegularWidth(width);
- String 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);
+ synchronized void clearCache() {
+ formatter1 = null;
+ formatter2 = null;
+ formatter3 = null;
}
- private int withPerUnitAndAppend(
- CharSequence formatted,
- MeasureUnit perUnit,
- StringBuilder appendTo) {
- int[] offsets = new int[1];
- String perUnitPattern = getFormatterOrNull(perUnit,
- formatWidth,
- MeasureFormatData.PER_UNIT_INDEX);
- if (perUnitPattern != null) {
- SimpleFormatterImpl.formatAndAppend(perUnitPattern, appendTo, offsets, formatted);
- return offsets[0];
- }
- String perPattern = getPerFormatter(formatWidth);
- String pattern = getPluralFormatter(perUnit, formatWidth, StandardPlural.ONE.ordinal());
- String perUnitString = SimpleFormatterImpl.getTextWithNoArguments(pattern).trim();
- SimpleFormatterImpl.formatAndAppend(perPattern, appendTo, offsets, formatted, perUnitString);
- return offsets[0];
+ // Can be overridden by subclasses:
+ LocalizedNumberFormatter getNumberFormatter() {
+ return numberFormatter;
}
- private String formatMeasure(Measure measure, ImmutableNumberFormat nf) {
- return formatMeasure(measure, nf, new StringBuilder(), DontCareFieldPosition.INSTANCE)
- .toString();
- }
+ /// END NUMBER FORMATTER CACHING MACHINERY ///
- private StringBuilder formatMeasure(
- Measure measure,
- ImmutableNumberFormat nf,
- StringBuilder appendTo,
- FieldPosition fieldPosition) {
- Number n = measure.getNumber();
+ private FormattedNumber formatMeasure(Measure measure) {
MeasureUnit unit = measure.getUnit();
if (unit instanceof Currency) {
- return appendTo.append(currencyFormat
- .format(new CurrencyAmount(n, (Currency) unit), new StringBuffer(), fieldPosition));
-
- }
- StringBuffer formattedNumber = new StringBuffer();
- StandardPlural pluralForm = QuantityFormatter
- .selectPlural(n, nf.nf, rules, formattedNumber, fieldPosition);
- String formatter = getPluralFormatter(unit, formatWidth, pluralForm.ordinal());
- return QuantityFormatter.format(formatter, formattedNumber, appendTo, fieldPosition);
- }
-
- /**
- * 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.
- */
- private static final class MeasureFormatData {
- static final int PER_UNIT_INDEX = StandardPlural.COUNT;
- static final int PATTERN_COUNT = PER_UNIT_INDEX + 1;
-
- boolean hasPerFormatter(FormatWidth width) {
- return styleToPerPattern.containsKey(width);
- }
-
- /**
- * 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 -> array of patterns ("{0} meters") (plurals + PER_UNIT_INDEX)
- */
- final Map<MeasureUnit, EnumMap<FormatWidth, String[]>> unitToStyleToPatterns = new HashMap<MeasureUnit, EnumMap<FormatWidth, String[]>>();
- final Map<MeasureUnit, EnumMap<FormatWidth, String>> unitToStyleToDnam = new HashMap<MeasureUnit, EnumMap<FormatWidth, String>>();
- final EnumMap<FormatWidth, String> styleToPerPattern = new EnumMap<FormatWidth, String>(
- FormatWidth.class);;
- }
-
- // 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);
- }
-
- @SuppressWarnings("unused")
- public synchronized String format(Number number) {
- return nf.format(number);
- }
-
- public String getPrefix(boolean positive) {
- return positive ? ((DecimalFormat) nf).getPositivePrefix()
- : ((DecimalFormat) nf).getNegativePrefix();
- }
-
- public String getSuffix(boolean positive) {
- return positive ? ((DecimalFormat) nf).getPositiveSuffix()
- : ((DecimalFormat) nf).getNegativeSuffix();
- }
- }
-
- static final class PatternData {
- final String prefix;
- final String suffix;
-
- public PatternData(String pattern) {
- int pos = pattern.indexOf("{0}");
- if (pos < 0) {
- prefix = pattern;
- suffix = null;
- } else {
- prefix = pattern.substring(0, pos);
- suffix = pattern.substring(pos + 3);
- }
- }
-
- @Override
- public String toString() {
- return prefix + "; " + suffix;
+ return getUnitFormatterFromCache(NUMBER_FORMATTER_CURRENCY, unit, null)
+ .format(measure.getNumber());
+ } else {
+ return getUnitFormatterFromCache(NUMBER_FORMATTER_STANDARD, unit, null)
+ .format(measure.getNumber());
}
-
}
- Object toTimeUnitProxy() {
- return new MeasureProxy(getLocale(), formatWidth, numberFormat.get(), TIME_UNIT_FORMAT);
- }
-
- Object toCurrencyProxy() {
- return new MeasureProxy(getLocale(), formatWidth, numberFormat.get(), CURRENCY_FORMAT);
+ private FormattedNumber formatMeasureInteger(Measure measure) {
+ return getUnitFormatterFromCache(NUMBER_FORMATTER_INTEGER, measure.getUnit(), null)
+ .format(measure.getNumber());
}
- private StringBuilder formatMeasuresSlowTrack(
+ private void formatMeasuresSlowTrack(
ListFormatter listFormatter,
- StringBuilder appendTo,
+ Appendable appendTo,
FieldPosition fieldPosition,
Measure... measures) {
String[] results = new String[measures.length];
int fieldPositionFoundIndex = -1;
for (int i = 0; i < measures.length; ++i) {
- ImmutableNumberFormat nf = (i == measures.length - 1 ? numberFormat : integerFormat);
+ FormattedNumber result;
+ if (i == measures.length - 1) {
+ result = formatMeasure(measures[i]);
+ } else {
+ result = formatMeasureInteger(measures[i]);
+ }
if (fieldPositionFoundIndex == -1) {
- results[i] = formatMeasure(measures[i], nf, new StringBuilder(), fpos).toString();
- if (fpos.getBeginIndex() != 0 || fpos.getEndIndex() != 0) {
+ result.populateFieldPosition(fpos);
+ if (fpos.getEndIndex() != 0) {
fieldPositionFoundIndex = i;
}
- } else {
- results[i] = formatMeasure(measures[i], nf);
}
+ results[i] = result.toString();
}
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());
- fieldPosition.setEndIndex(fpos.getEndIndex() + builder.getOffset() + appendTo.length());
+ fieldPosition.setBeginIndex(fpos.getBeginIndex() + builder.getOffset());
+ fieldPosition.setEndIndex(fpos.getEndIndex() + builder.getOffset());
}
- return appendTo.append(builder.toString());
+ builder.appendTo(appendTo);
}
// type is one of "hm", "ms" or "hms"
// 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) {
+ private void formatNumeric(Number[] hms, Appendable 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.
long millis = (long) (((Math.floor(hms[0].doubleValue()) * 60.0
+ Math.floor(hms[1].doubleValue())) * 60.0 + Math.floor(hms[2].doubleValue())) * 1000.0);
Date d = new Date(millis);
- // if hour-minute-second
if (startIndex == 0 && endIndex == 2) {
- return formatNumeric(d,
+ // if hour-minute-second
+ formatNumeric(d,
numericFormatters.getHourMinuteSecond(),
DateFormat.Field.SECOND,
hms[endIndex],
appendable);
- }
- // if minute-second
- if (startIndex == 1 && endIndex == 2) {
- return formatNumeric(d,
+ } else if (startIndex == 1 && endIndex == 2) {
+ // if minute-second
+ formatNumeric(d,
numericFormatters.getMinuteSecond(),
DateFormat.Field.SECOND,
hms[endIndex],
appendable);
- }
- // if hour-minute
- if (startIndex == 0 && endIndex == 1) {
- return formatNumeric(d,
+ } else if (startIndex == 0 && endIndex == 1) {
+ // if hour-minute
+ formatNumeric(d,
numericFormatters.getHourMinute(),
DateFormat.Field.MINUTE,
hms[endIndex],
appendable);
+ } else {
+ throw new IllegalStateException();
}
- throw new IllegalStateException();
}
// Formats a duration as 5:00:37 or 23:59.
// smallestAmount is 37.3. This smallest field is formatted with this object's
// NumberFormat instead of formatter.
// appendTo is where the formatted string is appended.
- private StringBuilder formatNumeric(
+ private void formatNumeric(
Date duration,
DateFormat formatter,
DateFormat.Field smallestField,
Number smallestAmount,
- StringBuilder appendTo) {
+ Appendable appendTo) {
// Format the smallest amount ahead of time.
String smallestAmountFormatted;
// integer part with the corresponding value from formatting the date. Otherwise
// when formatting 0 minutes 9 seconds, we may get "00:9" instead of "00:09"
FieldPosition intFieldPosition = new FieldPosition(NumberFormat.INTEGER_FIELD);
- smallestAmountFormatted = numberFormat
- .format(smallestAmount, new StringBuffer(), intFieldPosition).toString();
+ FormattedNumber result = getNumberFormatter().format(smallestAmount);
+ result.populateFieldPosition(intFieldPosition);
+ smallestAmountFormatted = result.toString();
// Give up if there is no integer field.
if (intFieldPosition.getBeginIndex() == 0 && intFieldPosition.getEndIndex() == 0) {
throw new IllegalStateException();
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,
- intFieldPosition.getEndIndex(),
- smallestAmountFormatted.length());
- appendTo.append(draft, smallestFieldPosition.getEndIndex(), draft.length());
- } else {
- // As fallback, just use the formatted duration.
- appendTo.append(draft);
+ try {
+ // 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,
+ intFieldPosition.getEndIndex(),
+ smallestAmountFormatted.length());
+ appendTo.append(draft, smallestFieldPosition.getEndIndex(), draft.length());
+ } else {
+ // As fallback, just use the formatted duration.
+ appendTo.append(draft);
+ }
+ } catch (IOException e) {
+ throw new ICUUncheckedIOException(e);
}
- return appendTo;
+ }
+
+ Object toTimeUnitProxy() {
+ return new MeasureProxy(getLocale(), formatWidth, getNumberFormat(), TIME_UNIT_FORMAT);
+ }
+
+ Object toCurrencyProxy() {
+ return new MeasureProxy(getLocale(), formatWidth, getNumberFormat(), CURRENCY_FORMAT);
}
private Object writeReplace() throws ObjectStreamException {
- return new MeasureProxy(getLocale(), formatWidth, numberFormat.get(), MEASURE_FORMAT);
+ return new MeasureProxy(getLocale(), formatWidth, getNumberFormat(), MEASURE_FORMAT);
}
static class MeasureProxy implements Externalizable {
package com.ibm.icu.text;
import java.io.ObjectStreamException;
-import java.text.FieldPosition;
import java.text.ParseException;
import java.text.ParsePosition;
import java.util.HashMap;
import com.ibm.icu.impl.ICUData;
import com.ibm.icu.impl.ICUResourceBundle;
import com.ibm.icu.impl.UResource;
-import com.ibm.icu.util.Measure;
+import com.ibm.icu.number.LocalizedNumberFormatter;
import com.ibm.icu.util.TimeUnit;
import com.ibm.icu.util.TimeUnitAmount;
import com.ibm.icu.util.ULocale;
import com.ibm.icu.util.ULocale.Category;
import com.ibm.icu.util.UResourceBundle;
+
/**
* Format or parse a TimeUnitAmount, using plural rules for the units where available.
*
* <P>
* Code Sample:
- *
* <pre>
- * // create a time unit instance.
- * // only SECOND, MINUTE, HOUR, DAY, WEEK, MONTH, and YEAR are supported
- * TimeUnit timeUnit = TimeUnit.SECOND;
- * // create time unit amount instance - a combination of Number and time unit
- * TimeUnitAmount source = new TimeUnitAmount(2, timeUnit);
- * // create time unit format instance
- * TimeUnitFormat format = new TimeUnitFormat();
- * // set the locale of time unit format
- * format.setLocale(new ULocale("en"));
- * // format a time unit amount
- * String formatted = format.format(source);
- * System.out.println(formatted);
- * try {
- * // parse a string into time unit amount
- * TimeUnitAmount result = (TimeUnitAmount) format.parseObject(formatted);
- * // result should equal to source
- * } catch (ParseException e) {
- * }
+ * // create a time unit instance.
+ * // only SECOND, MINUTE, HOUR, DAY, WEEK, MONTH, and YEAR are supported
+ * TimeUnit timeUnit = TimeUnit.SECOND;
+ * // create time unit amount instance - a combination of Number and time unit
+ * TimeUnitAmount source = new TimeUnitAmount(2, timeUnit);
+ * // create time unit format instance
+ * TimeUnitFormat format = new TimeUnitFormat();
+ * // set the locale of time unit format
+ * format.setLocale(new ULocale("en"));
+ * // format a time unit amount
+ * String formatted = format.format(source);
+ * System.out.println(formatted);
+ * try {
+ * // parse a string into time unit amount
+ * TimeUnitAmount result = (TimeUnitAmount) format.parseObject(formatted);
+ * // result should equal to source
+ * } catch (ParseException e) {
+ * }
* </pre>
*
* <P>
- *
* @see TimeUnitAmount
* @see MeasureFormat
* @author markdavis
public class TimeUnitFormat extends MeasureFormat {
/**
- * Constant for full name style format. For example, the full name for "hour" in English is "hour" or
- * "hours".
- *
+ * Constant for full name style format.
+ * For example, the full name for "hour" in English is "hour" or "hours".
* @deprecated ICU 53 see {@link MeasureFormat.FormatWidth}
*/
@Deprecated
public static final int FULL_NAME = 0;
/**
- * Constant for abbreviated name style format. For example, the abbreviated name for "hour" in
- * English is "hr" or "hrs".
- *
+ * Constant for abbreviated name style format.
+ * For example, the abbreviated name for "hour" in English is "hr" or "hrs".
* @deprecated ICU 53 see {@link MeasureFormat.FormatWidth}
*/
@Deprecated
// is an empty shell. Every public method of the super class is overridden to
// delegate to this field. Each time this object mutates, it replaces this field with
// a new immutable instance.
- private transient MeasureFormat mf;
+// private transient MeasureFormat mf;
private transient Map<TimeUnit, Map<String, Object[]>> timeUnitToCountToPatterns;
private transient PluralRules pluralRules;
private static final String DEFAULT_PATTERN_FOR_YEAR = "{0} y";
/**
- * Create empty format using full name style, for example, "hours". Use setLocale and/or setFormat to
- * modify.
- *
+ * Create empty format using full name style, for example, "hours".
+ * Use setLocale and/or setFormat to modify.
* @deprecated ICU 53 use {@link MeasureFormat} instead.
*/
@Deprecated
public TimeUnitFormat() {
- mf = MeasureFormat.getInstance(ULocale.getDefault(), FormatWidth.WIDE);
- isReady = false;
- style = FULL_NAME;
+ this(ULocale.getDefault(), FULL_NAME);
}
/**
* Create TimeUnitFormat given a ULocale, and using full name style.
- *
- * @param locale
- * locale of this time unit formatter.
+ * @param locale locale of this time unit formatter.
* @deprecated ICU 53 use {@link MeasureFormat} instead.
*/
@Deprecated
/**
* Create TimeUnitFormat given a Locale, and using full name style.
- *
- * @param locale
- * locale of this time unit formatter.
+ * @param locale locale of this time unit formatter.
* @deprecated ICU 53 use {@link MeasureFormat} instead.
*/
@Deprecated
/**
* Create TimeUnitFormat given a ULocale and a formatting style.
- *
- * @param locale
- * locale of this time unit formatter.
- * @param style
- * format style, either FULL_NAME or ABBREVIATED_NAME style.
- * @throws IllegalArgumentException
- * if the style is not FULL_NAME or ABBREVIATED_NAME style.
+ * @param locale locale of this time unit formatter.
+ * @param style format style, either FULL_NAME or ABBREVIATED_NAME style.
+ * @throws IllegalArgumentException if the style is not FULL_NAME or
+ * ABBREVIATED_NAME style.
* @deprecated ICU 53 use {@link MeasureFormat} instead.
*/
@Deprecated
public TimeUnitFormat(ULocale locale, int style) {
+ super(locale, style == FULL_NAME ? FormatWidth.WIDE : FormatWidth.SHORT);
+ format = super.getNumberFormat();
if (style < FULL_NAME || style >= TOTAL_STYLES) {
- throw new IllegalArgumentException(
- "style should be either FULL_NAME or ABBREVIATED_NAME style");
+ throw new IllegalArgumentException("style should be either FULL_NAME or ABBREVIATED_NAME style");
}
- mf = MeasureFormat.getInstance(locale,
- style == FULL_NAME ? FormatWidth.WIDE : FormatWidth.SHORT);
this.style = style;
-
- // Needed for getLocale(ULocale.VALID_LOCALE)
- setLocale(locale, locale);
- this.locale = locale;
isReady = false;
}
/**
* Create TimeUnitFormat given a Locale and a formatting style.
- *
* @deprecated ICU 53 use {@link MeasureFormat} instead.
*/
@Deprecated
public TimeUnitFormat(Locale locale, int style) {
- this(ULocale.forLocale(locale), style);
+ this(ULocale.forLocale(locale), style);
}
/**
* Set the locale used for formatting or parsing.
- *
- * @param locale
- * locale of this time unit formatter.
+ * @param locale locale of this time unit formatter.
* @return this, for chaining.
* @deprecated ICU 53 see {@link MeasureFormat}.
*/
@Deprecated
public TimeUnitFormat setLocale(ULocale locale) {
- if (locale != this.locale) {
- mf = mf.withLocale(locale);
-
- // Needed for getLocale(ULocale.VALID_LOCALE)
- setLocale(locale, locale);
- this.locale = locale;
- isReady = false;
- }
+ setLocale(locale, locale);
+ clearCache();
return this;
}
/**
* Set the locale used for formatting or parsing.
- *
- * @param locale
- * locale of this time unit formatter.
+ * @param locale locale of this time unit formatter.
* @return this, for chaining.
* @deprecated ICU 53 see {@link MeasureFormat}.
*/
/**
* Set the format used for formatting or parsing. Passing null is equivalent to passing
* {@link NumberFormat#getNumberInstance(ULocale)}.
- *
- * @param format
- * the number formatter.
+ * @param format the number formatter.
* @return this, for chaining.
* @deprecated ICU 53 see {@link MeasureFormat}.
*/
if (format == null) {
if (locale == null) {
isReady = false;
- mf = mf.withLocale(ULocale.getDefault());
} else {
this.format = NumberFormat.getNumberInstance(locale);
- mf = mf.withNumberFormat(this.format);
}
} else {
this.format = format;
- mf = mf.withNumberFormat(this.format);
}
+ clearCache();
return this;
}
- /**
- * Format a TimeUnitAmount.
- *
- * @see java.text.Format#format(java.lang.Object, java.lang.StringBuffer, java.text.FieldPosition)
- * @deprecated ICU 53 see {@link MeasureFormat}.
- */
@Override
- @Deprecated
- public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) {
- return mf.format(obj, toAppendTo, pos);
+ public NumberFormat getNumberFormat() {
+ return format;
+ }
+
+ @Override
+ LocalizedNumberFormatter getNumberFormatter() {
+ return ((DecimalFormat)format).toNumberFormatter();
}
/**
* Parse a TimeUnitAmount.
- *
* @see java.text.Format#parseObject(java.lang.String, java.text.ParsePosition)
* @deprecated ICU 53 see {@link MeasureFormat}.
*/
if (tempObj instanceof Number) {
temp = (Number) tempObj;
} else {
- // Since we now format the number ourselves, parseObject will likely give us
- // back a String
+ // Since we now format the number ourselves, parseObject will likely give us back a String
// for
- // the number. When this happens we must parse the formatted number
- // ourselves.
+ // the number. When this happens we must parse the formatted number ourselves.
try {
temp = format.parse(tempObj.toString());
} catch (ParseException e) {
}
}
/*
- * After find the longest match, parse the number. Result number could be null for the pattern
- * without number pattern. such as unit pattern in Arabic. When result number is null, use plural
- * rule to set the number.
+ * After find the longest match, parse the number. Result number could be null for the pattern without number
+ * pattern. such as unit pattern in Arabic. When result number is null, use plural rule to set the number.
*/
if (resultNumber == null && longestParseDistance != 0) {
// set the number using plurrual count
ULocale locale;
boolean beenHere;
- TimeUnitFormatSetupSink(
- Map<TimeUnit, Map<String, Object[]>> timeUnitToCountToPatterns,
- int style,
- Set<String> pluralKeywords,
- ULocale locale) {
+ TimeUnitFormatSetupSink(Map<TimeUnit, Map<String, Object[]>> timeUnitToCountToPatterns,
+ int style, Set<String> pluralKeywords, ULocale locale) {
this.timeUnitToCountToPatterns = timeUnitToCountToPatterns;
this.style = style;
this.pluralKeywords = pluralKeywords;
}
}
- private void setup(
- String resourceKey,
- Map<TimeUnit, Map<String, Object[]>> timeUnitToCountToPatterns,
- int style,
+ private void setup(String resourceKey, Map<TimeUnit, Map<String, Object[]>> timeUnitToCountToPatterns, int style,
Set<String> pluralKeywords) {
// fill timeUnitToCountToPatterns from resource file
try {
- ICUResourceBundle resource = (ICUResourceBundle) UResourceBundle
- .getBundleInstance(ICUData.ICU_UNIT_BASE_NAME, locale);
+ ICUResourceBundle resource = (ICUResourceBundle) UResourceBundle.getBundleInstance(
+ ICUData.ICU_UNIT_BASE_NAME, locale);
- TimeUnitFormatSetupSink sink = new TimeUnitFormatSetupSink(timeUnitToCountToPatterns,
- style,
- pluralKeywords,
- locale);
+ TimeUnitFormatSetupSink sink = new TimeUnitFormatSetupSink(
+ timeUnitToCountToPatterns, style, pluralKeywords, locale);
resource.getAllItemsWithFallback(resourceKey, sink);
} catch (MissingResourceException e) {
}
timeUnitToCountToPatterns.put(timeUnit, countToPatterns);
}
for (String pluralCount : keywords) {
- if (countToPatterns.get(pluralCount) == null
- || countToPatterns.get(pluralCount)[style] == null) {
+ if (countToPatterns.get(pluralCount) == null || countToPatterns.get(pluralCount)[style] == null) {
// look through parents
- searchInTree(resourceKey,
- style,
- timeUnit,
- pluralCount,
- pluralCount,
- countToPatterns);
+ searchInTree(resourceKey, style, timeUnit, pluralCount, pluralCount, countToPatterns);
}
}
}
// if the pattern is not found even in root, fallback to
// using patterns of plural count "other",
// then, "other" is the searchPluralCount.
- private void searchInTree(
- String resourceKey,
- int styl,
- TimeUnit timeUnit,
- String srcPluralCount,
- String searchPluralCount,
- Map<String, Object[]> countToPatterns) {
+ private void searchInTree(String resourceKey, int styl, TimeUnit timeUnit, String srcPluralCount,
+ String searchPluralCount, Map<String, Object[]> countToPatterns) {
ULocale parentLocale = locale;
String srcTimeUnitName = timeUnit.toString();
while (parentLocale != null) {
try {
// look for pattern for srcPluralCount in locale tree
- ICUResourceBundle unitsRes = (ICUResourceBundle) UResourceBundle
- .getBundleInstance(ICUData.ICU_UNIT_BASE_NAME, parentLocale);
+ ICUResourceBundle unitsRes = (ICUResourceBundle) UResourceBundle.getBundleInstance(
+ ICUData.ICU_UNIT_BASE_NAME, parentLocale);
unitsRes = unitsRes.getWithFallback(resourceKey);
ICUResourceBundle oneUnitRes = unitsRes.getWithFallback(srcTimeUnitName);
String pattern = oneUnitRes.getStringWithFallback(searchPluralCount);
// boilerplate code to make TimeUnitFormat otherwise follow the contract of
// MeasureFormat
- /**
- * @internal
- * @deprecated This API is ICU internal only.
- */
- @Deprecated
- @Override
- public StringBuilder formatMeasures(
- StringBuilder appendTo,
- FieldPosition fieldPosition,
- Measure... measures) {
- return mf.formatMeasures(appendTo, fieldPosition, measures);
- }
-
- /**
- * @internal
- * @deprecated This API is ICU internal only.
- */
- @Deprecated
- @Override
- public MeasureFormat.FormatWidth getWidth() {
- return mf.getWidth();
- }
-
- /**
- * @internal
- * @deprecated This API is ICU internal only.
- */
- @Deprecated
- @Override
- public NumberFormat getNumberFormat() {
- return mf.getNumberFormat();
- }
-
/**
* @internal
* @deprecated This API is ICU internal only.
// Serialization
private Object writeReplace() throws ObjectStreamException {
- return mf.toTimeUnitProxy();
+ return super.toTimeUnitProxy();
}
// Preserve backward serialize backward compatibility.