| /* GENERATED SOURCE. DO NOT MODIFY. */ |
| /* |
| ********************************************************************** |
| * Copyright (c) 2004-2016, International Business Machines |
| * Corporation and others. All Rights Reserved. |
| ********************************************************************** |
| * Author: Alan Liu |
| * Created: April 20, 2004 |
| * Since: ICU 3.0 |
| ********************************************************************** |
| */ |
| package android.icu.text; |
| |
| import java.io.Externalizable; |
| import java.io.IOException; |
| import java.io.InvalidObjectException; |
| import java.io.ObjectInput; |
| import java.io.ObjectOutput; |
| import java.io.ObjectStreamException; |
| 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 java.util.MissingResourceException; |
| import java.util.concurrent.ConcurrentHashMap; |
| |
| import android.icu.impl.DontCareFieldPosition; |
| import android.icu.impl.ICUData; |
| import android.icu.impl.ICUResourceBundle; |
| import android.icu.impl.SimpleCache; |
| import android.icu.impl.SimplePatternFormatter; |
| import android.icu.impl.StandardPlural; |
| import android.icu.impl.UResource; |
| import android.icu.math.BigDecimal; |
| import android.icu.text.PluralRules.Factory; |
| import android.icu.util.Currency; |
| import android.icu.util.CurrencyAmount; |
| import android.icu.util.ICUException; |
| import android.icu.util.Measure; |
| import android.icu.util.MeasureUnit; |
| import android.icu.util.TimeZone; |
| import android.icu.util.ULocale; |
| import android.icu.util.ULocale.Category; |
| import android.icu.util.UResourceBundle; |
| |
| // If you update the examples in the doc, don't forget to update MesaureUnitTest.TestExamplesInDocs too. |
| /** |
| * A formatter for Measure objects. |
| * |
| * <p>To format a Measure object, first create a formatter |
| * object using a MeasureFormat factory method. Then use that |
| * object's format or formatMeasures methods. |
| * |
| * Here is sample code: |
| * <pre> |
| * MeasureFormat fmtFr = MeasureFormat.getInstance( |
| * ULocale.FRENCH, FormatWidth.SHORT); |
| * Measure measure = new Measure(23, MeasureUnit.CELSIUS); |
| * |
| * // Output: 23 °C |
| * System.out.println(fmtFr.format(measure)); |
| * |
| * Measure measureF = new Measure(70, MeasureUnit.FAHRENHEIT); |
| * |
| * // Output: 70 °F |
| * System.out.println(fmtFr.format(measureF)); |
| * |
| * MeasureFormat fmtFrFull = MeasureFormat.getInstance( |
| * ULocale.FRENCH, FormatWidth.WIDE); |
| * // Output: 70 pieds et 5,3 pouces |
| * System.out.println(fmtFrFull.formatMeasures( |
| * new Measure(70, MeasureUnit.FOOT), |
| * new Measure(5.3, MeasureUnit.INCH))); |
| * |
| * // Output: 1 pied et 1 pouce |
| * System.out.println(fmtFrFull.formatMeasures( |
| * new Measure(1, MeasureUnit.FOOT), |
| * new Measure(1, MeasureUnit.INCH))); |
| * |
| * MeasureFormat fmtFrNarrow = MeasureFormat.getInstance( |
| ULocale.FRENCH, FormatWidth.NARROW); |
| * // Output: 1′ 1″ |
| * System.out.println(fmtFrNarrow.formatMeasures( |
| * new Measure(1, MeasureUnit.FOOT), |
| * new Measure(1, MeasureUnit.INCH))); |
| * |
| * |
| * MeasureFormat fmtEn = MeasureFormat.getInstance(ULocale.ENGLISH, FormatWidth.WIDE); |
| * |
| * // Output: 1 inch, 2 feet |
| * fmtEn.formatMeasures( |
| * new Measure(1, MeasureUnit.INCH), |
| * new Measure(2, MeasureUnit.FOOT)); |
| * </pre> |
| * <p> |
| * This class does not do conversions from one unit to another. It simply formats |
| * whatever units it is given |
| * <p> |
| * This class is immutable and thread-safe so long as its deprecated subclass, |
| * TimeUnitFormat, is never used. TimeUnitFormat is not thread-safe, and is |
| * mutable. Although this class has existing subclasses, this class does not support new |
| * sub-classes. |
| * |
| * @see android.icu.text.UFormat |
| * @author Alan Liu |
| */ |
| 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; |
| |
| // PluralRules is documented as being immutable which implies thread-safety. |
| private final transient PluralRules rules; |
| |
| private final transient NumericFormatters numericFormatters; |
| |
| private final transient ImmutableNumberFormat currencyFormat; |
| |
| private final transient ImmutableNumberFormat integerFormat; |
| |
| private static final SimpleCache<ULocale, MeasureFormatData> localeMeasureFormatData |
| = new SimpleCache<ULocale, MeasureFormatData>(); |
| |
| private static final SimpleCache<ULocale, NumericFormatters> localeToNumericDurationFormatters |
| = new SimpleCache<ULocale,NumericFormatters>(); |
| |
| private static final Map<MeasureUnit, Integer> hmsTo012 = |
| new HashMap<MeasureUnit, Integer>(); |
| |
| static { |
| hmsTo012.put(MeasureUnit.HOUR, 0); |
| hmsTo012.put(MeasureUnit.MINUTE, 1); |
| hmsTo012.put(MeasureUnit.SECOND, 2); |
| } |
| |
| // For serialization: sub-class types. |
| private static final int MEASURE_FORMAT = 0; |
| private static final int TIME_UNIT_FORMAT = 1; |
| private static final int CURRENCY_FORMAT = 2; |
| |
| /** |
| * Formatting width enum. |
| */ |
| // Be sure to update MeasureUnitTest.TestSerialFormatWidthEnum |
| // when adding an enum value. |
| public enum FormatWidth { |
| |
| /** |
| * Spell out everything. |
| */ |
| WIDE(ListFormatter.Style.DURATION, NumberFormat.PLURALCURRENCYSTYLE), |
| |
| /** |
| * Abbreviate when possible. |
| */ |
| SHORT(ListFormatter.Style.DURATION_SHORT, NumberFormat.ISOCURRENCYSTYLE), |
| |
| /** |
| * Brief. Use only a symbol for the unit when possible. |
| */ |
| NARROW(ListFormatter.Style.DURATION_NARROW, NumberFormat.CURRENCYSTYLE), |
| |
| /** |
| * Identical to NARROW except when formatMeasures is called with |
| * an hour and minute; minute and second; or hour, minute, and second Measures. |
| * In these cases formatMeasures formats as 5:37:23 instead of 5h, 37m, 23s. |
| */ |
| 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 |
| |
| private final ListFormatter.Style listFormatterStyle; |
| private final int currencyStyle; |
| |
| private FormatWidth(ListFormatter.Style style, int currencyStyle) { |
| this.listFormatterStyle = style; |
| this.currencyStyle = currencyStyle; |
| } |
| |
| ListFormatter.Style getListFormatterStyle() { |
| return listFormatterStyle; |
| } |
| |
| int getCurrencyStyle() { |
| return currencyStyle; |
| } |
| } |
| |
| /** |
| * Create a format from the locale, formatWidth, and format. |
| * |
| * @param locale the locale. |
| * @param formatWidth hints how long formatted strings should be. |
| * @return The new MeasureFormat object. |
| */ |
| public static MeasureFormat getInstance(ULocale locale, FormatWidth formatWidth) { |
| return getInstance(locale, formatWidth, NumberFormat.getInstance(locale)); |
| } |
| |
| /** |
| * Create a format from the {@link java.util.Locale} and formatWidth. |
| * |
| * @param locale the {@link java.util.Locale}. |
| * @param formatWidth hints how long formatted strings should be. |
| * @return The new MeasureFormat object. |
| */ |
| public static MeasureFormat getInstance(Locale locale, FormatWidth formatWidth) { |
| return getInstance(ULocale.forLocale(locale), formatWidth); |
| } |
| |
| /** |
| * Create a format from the locale, formatWidth, and format. |
| * |
| * @param locale the locale. |
| * @param formatWidth hints how long formatted strings should be. |
| * @param format This is defensively copied. |
| * @return The new MeasureFormat object. |
| */ |
| public static MeasureFormat getInstance(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)); |
| } |
| |
| /** |
| * Create a format from the {@link java.util.Locale}, formatWidth, and format. |
| * |
| * @param locale the {@link java.util.Locale}. |
| * @param formatWidth hints how long formatted strings should be. |
| * @param format This is defensively copied. |
| * @return The new MeasureFormat object. |
| */ |
| public static MeasureFormat getInstance(Locale locale, FormatWidth formatWidth, NumberFormat format) { |
| return getInstance(ULocale.forLocale(locale), formatWidth, format); |
| } |
| |
| /** |
| * Able to format Collection<? extends Measure>, Measure[], and Measure |
| * by delegating to formatMeasures. |
| * If the pos argument identifies a NumberFormat field, |
| * then its indices are set to the beginning and end of the first such field |
| * encountered. MeasureFormat itself does not supply any fields. |
| * |
| * Calling a |
| * <code>formatMeasures</code> method is preferred over calling |
| * this method as they give better performance. |
| * |
| * @param obj must be a Collection<? extends Measure>, Measure[], or Measure object. |
| * @param toAppendTo Formatted string appended here. |
| * @param pos Identifies a field in the formatted text. |
| * @see java.text.Format#format(java.lang.Object, java.lang.StringBuffer, java.text.FieldPosition) |
| */ |
| @Override |
| public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) { |
| int prevLength = toAppendTo.length(); |
| FieldPosition fpos = |
| new FieldPosition(pos.getFieldAttribute(), pos.getField()); |
| if (obj instanceof Collection) { |
| Collection<?> coll = (Collection<?>) obj; |
| Measure[] measures = new Measure[coll.size()]; |
| int idx = 0; |
| for (Object o : coll) { |
| if (!(o instanceof Measure)) { |
| throw new IllegalArgumentException(obj.toString()); |
| } |
| measures[idx++] = (Measure) o; |
| } |
| toAppendTo.append(formatMeasures(new StringBuilder(), fpos, measures)); |
| } else if (obj instanceof Measure[]) { |
| toAppendTo.append(formatMeasures(new StringBuilder(), fpos, (Measure[]) obj)); |
| } else if (obj instanceof Measure){ |
| toAppendTo.append(formatMeasure((Measure) obj, numberFormat, new StringBuilder(), fpos)); |
| } else { |
| throw new IllegalArgumentException(obj.toString()); |
| } |
| if (fpos.getBeginIndex() != 0 || fpos.getEndIndex() != 0) { |
| pos.setBeginIndex(fpos.getBeginIndex() + prevLength); |
| pos.setEndIndex(fpos.getEndIndex() + prevLength); |
| } |
| return toAppendTo; |
| } |
| |
| /** |
| * Parses text from a string to produce a <code>Measure</code>. |
| * @see java.text.Format#parseObject(java.lang.String, java.text.ParsePosition) |
| * @throws UnsupportedOperationException Not supported. |
| * @hide draft / provisional / internal are hidden on Android |
| */ |
| @Override |
| public Measure parseObject(String source, ParsePosition pos) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| /** |
| * Format a sequence of measures. Uses the ListFormatter unit lists. |
| * So, for example, one could format “3 feet, 2 inches”. |
| * Zero values are formatted (eg, “3 feet, 0 inches”). It is the caller’s |
| * responsibility to have the appropriate values in appropriate order, |
| * and using the appropriate Number values. Typically the units should be |
| * in descending order, with all but the last Measure having integer values |
| * (eg, not “3.2 feet, 2 inches”). |
| * |
| * @param measures a sequence of one or more measures. |
| * @return the formatted string. |
| */ |
| public final String formatMeasures(Measure... measures) { |
| 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. |
| * @deprecated This API is ICU internal only. |
| * @hide original deprecated declaration |
| * @hide draft / provisional / internal are hidden on Android |
| */ |
| @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 = SimplePatternFormatter.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 SimplePatternFormatter.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())); |
| } |
| } |
| |
| /** |
| * Formats a single measure per unit. |
| * |
| * An example of such a formatted string is "3.5 meters per second." |
| * |
| * @param measure the measure object. In above example, 3.5 meters. |
| * @param perUnit the per unit. In above example, it is MeasureUnit.SECOND |
| * @param appendTo formatted string appended here. |
| * @param pos The field position. |
| * @return appendTo. |
| * @hide draft / provisional / internal are hidden on Android |
| */ |
| public StringBuilder formatMeasurePerUnit( |
| Measure measure, |
| 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); |
| } |
| return appendTo; |
| } |
| |
| /** |
| * Formats a sequence of measures. |
| * |
| * If the fieldPosition argument identifies a NumberFormat field, |
| * then its indices are set to the beginning and end of the first such field |
| * encountered. MeasureFormat itself does not supply any fields. |
| * |
| * @param appendTo the formatted string appended here. |
| * @param fieldPosition Identifies a field in the formatted text. |
| * @param measures the measures to format. |
| * @return appendTo. |
| * @see MeasureFormat#formatMeasures(Measure...) |
| */ |
| public StringBuilder formatMeasures( |
| StringBuilder appendTo, FieldPosition fieldPosition, Measure... measures) { |
| // fast track for trivial cases |
| if (measures.length == 0) { |
| return appendTo; |
| } |
| if (measures.length == 1) { |
| return formatMeasure(measures[0], numberFormat, appendTo, fieldPosition); |
| } |
| |
| if (formatWidth == FormatWidth.NUMERIC) { |
| // If we have just hour, minute, or second follow the numeric |
| // track. |
| Number[] hms = toHMS(measures); |
| if (hms != null) { |
| return formatNumeric(hms, appendTo); |
| } |
| } |
| |
| ListFormatter listFormatter = ListFormatter.getInstance( |
| getLocale(), formatWidth.getListFormatterStyle()); |
| if (fieldPosition != DontCareFieldPosition.INSTANCE) { |
| return formatMeasuresSlowTrack(listFormatter, appendTo, fieldPosition, measures); |
| } |
| // 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); |
| } |
| return appendTo.append(listFormatter.format((Object[]) results)); |
| |
| } |
| |
| /** |
| * Two MeasureFormats, a and b, are equal if and only if they have the same formatWidth, |
| * locale, and equal number formats. |
| */ |
| @Override |
| public final boolean equals(Object other) { |
| if (this == other) { |
| return true; |
| } |
| if (!(other instanceof MeasureFormat)) { |
| return false; |
| } |
| MeasureFormat rhs = (MeasureFormat) other; |
| // A very slow but safe implementation. |
| return getWidth() == rhs.getWidth() |
| && getLocale().equals(rhs.getLocale()) |
| && getNumberFormat().equals(rhs.getNumberFormat()); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public final int hashCode() { |
| // A very slow but safe implementation. |
| return (getLocale().hashCode() * 31 |
| + getNumberFormat().hashCode()) * 31 + getWidth().hashCode(); |
| } |
| |
| /** |
| * Get the format width this instance is using. |
| */ |
| public MeasureFormat.FormatWidth getWidth() { |
| return formatWidth; |
| } |
| |
| /** |
| * Get the locale of this instance. |
| */ |
| public final ULocale getLocale() { |
| return getLocale(ULocale.VALID_LOCALE); |
| } |
| |
| /** |
| * Get a copy of the number format. |
| */ |
| public NumberFormat getNumberFormat() { |
| return numberFormat.get(); |
| } |
| |
| /** |
| * Return a formatter for CurrencyAmount objects in the given |
| * locale. |
| * @param locale desired locale |
| * @return a formatter object |
| */ |
| public static MeasureFormat getCurrencyFormat(ULocale locale) { |
| return new CurrencyFormat(locale); |
| } |
| |
| /** |
| * Return a formatter for CurrencyAmount objects in the given |
| * {@link java.util.Locale}. |
| * @param locale desired {@link java.util.Locale} |
| * @return a formatter object |
| */ |
| public static MeasureFormat getCurrencyFormat(Locale locale) { |
| return getCurrencyFormat(ULocale.forLocale(locale)); |
| } |
| |
| /** |
| * Return a formatter for CurrencyAmount objects in the default |
| * <code>FORMAT</code> locale. |
| * @return a formatter object |
| * @see Category#FORMAT |
| */ |
| public static MeasureFormat getCurrencyFormat() { |
| return getCurrencyFormat(ULocale.getDefault(Category.FORMAT)); |
| } |
| |
| // This method changes the NumberFormat object as well to match the new locale. |
| MeasureFormat withLocale(ULocale locale) { |
| return MeasureFormat.getInstance(locale, getWidth()); |
| } |
| |
| MeasureFormat withNumberFormat(NumberFormat format) { |
| return new MeasureFormat( |
| getLocale(), |
| this.cache, |
| this.formatWidth, |
| new ImmutableNumberFormat(format), |
| this.rules, |
| this.numericFormatters, |
| this.currencyFormat, |
| this.integerFormat); |
| } |
| |
| private MeasureFormat( |
| ULocale locale, |
| MeasureFormatData data, |
| FormatWidth formatWidth, |
| ImmutableNumberFormat format, |
| PluralRules rules, |
| NumericFormatters formatters, |
| ImmutableNumberFormat currencyFormat, |
| ImmutableNumberFormat integerFormat) { |
| setLocale(locale, locale); |
| this.cache = data; |
| this.formatWidth = formatWidth; |
| this.numberFormat = format; |
| this.rules = rules; |
| this.numericFormatters = formatters; |
| this.currencyFormat = currencyFormat; |
| this.integerFormat = integerFormat; |
| } |
| |
| 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; |
| } |
| |
| static class NumericFormatters { |
| private DateFormat hourMinute; |
| private DateFormat minuteSecond; |
| private DateFormat hourMinuteSecond; |
| |
| public NumericFormatters( |
| DateFormat hourMinute, |
| DateFormat minuteSecond, |
| DateFormat hourMinuteSecond) { |
| this.hourMinute = hourMinute; |
| this.minuteSecond = minuteSecond; |
| this.hourMinuteSecond = hourMinuteSecond; |
| } |
| |
| public DateFormat getHourMinute() { return hourMinute; } |
| public DateFormat getMinuteSecond() { return minuteSecond; } |
| public DateFormat getHourMinuteSecond() { return hourMinuteSecond; } |
| } |
| |
| private static NumericFormatters loadNumericFormatters( |
| ULocale locale) { |
| ICUResourceBundle r = (ICUResourceBundle)UResourceBundle. |
| getBundleInstance(ICUData.ICU_UNIT_BASE_NAME, locale); |
| return new NumericFormatters( |
| loadNumericDurationFormat(r, "hm"), |
| loadNumericDurationFormat(r, "ms"), |
| 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.TableSink { |
| /** |
| * Sink for a table of display patterns. For example, |
| * unitsShort/duration/hour contains other{"{0} hrs"}. |
| */ |
| class UnitPatternSink extends UResource.TableSink { |
| String[] patterns; |
| |
| 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] = SimplePatternFormatter.compileToStringMinMaxPlaceholders( |
| value.getString(), sb, minPlaceholders, 1); |
| } |
| } |
| |
| @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". |
| // TODO: Set minPlaceholders=1 |
| // after http://unicode.org/cldr/trac/ticket/9129 is fixed. |
| setFormatterIfAbsent(MeasureFormatData.PER_UNIT_INDEX, value, 0); |
| } 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); |
| } |
| } |
| } |
| 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 |
| // Trigger a fresh lookup of the patterns for this unit+width. |
| patternSink.patterns = null; |
| 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.compileToStringMinMaxPlaceholders( |
| value.getString(), sb, 2, 2)); |
| } |
| } |
| } |
| 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; |
| |
| // Temporary |
| StringBuilder sb = new StringBuilder(); |
| } |
| |
| /** |
| * 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 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; |
| } |
| |
| private 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); |
| } |
| |
| 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); |
| } |
| |
| 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) { |
| SimplePatternFormatter.formatAndAppend(perUnitPattern, appendTo, offsets, formatted); |
| return offsets[0]; |
| } |
| String perPattern = getPerFormatter(formatWidth); |
| String pattern = getPluralFormatter(perUnit, formatWidth, StandardPlural.ONE.ordinal()); |
| String perUnitString = SimplePatternFormatter.getTextWithNoPlaceholders(pattern).trim(); |
| SimplePatternFormatter.formatAndAppend( |
| perPattern, appendTo, offsets, formatted, perUnitString); |
| return offsets[0]; |
| } |
| |
| private String formatMeasure(Measure measure, ImmutableNumberFormat nf) { |
| return formatMeasure( |
| measure, nf, new StringBuilder(), |
| DontCareFieldPosition.INSTANCE).toString(); |
| } |
| |
| private StringBuilder formatMeasure( |
| Measure measure, |
| ImmutableNumberFormat nf, |
| StringBuilder appendTo, |
| FieldPosition fieldPosition) { |
| Number n = measure.getNumber(); |
| 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 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).getPositiveSuffix(); |
| } |
| } |
| |
| 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); |
| } |
| } |
| public String toString() { |
| return prefix + "; " + suffix; |
| } |
| |
| } |
| |
| Object toTimeUnitProxy() { |
| return new MeasureProxy(getLocale(), formatWidth, numberFormat.get(), TIME_UNIT_FORMAT); |
| } |
| |
| Object toCurrencyProxy() { |
| return new MeasureProxy(getLocale(), formatWidth, numberFormat.get(), CURRENCY_FORMAT); |
| } |
| |
| private StringBuilder formatMeasuresSlowTrack( |
| ListFormatter listFormatter, |
| StringBuilder appendTo, |
| FieldPosition fieldPosition, |
| Measure... measures) { |
| String[] results = new String[measures.length]; |
| |
| // Zero out our field position so that we can tell when we find our field. |
| FieldPosition fpos = new FieldPosition( |
| fieldPosition.getFieldAttribute(), fieldPosition.getField()); |
| |
| int fieldPositionFoundIndex = -1; |
| for (int i = 0; i < measures.length; ++i) { |
| ImmutableNumberFormat nf = (i == measures.length - 1 ? numberFormat : integerFormat); |
| if (fieldPositionFoundIndex == -1) { |
| results[i] = formatMeasure(measures[i], nf, new StringBuilder(), fpos).toString(); |
| if (fpos.getBeginIndex() != 0 || fpos.getEndIndex() != 0) { |
| fieldPositionFoundIndex = i; |
| } |
| } else { |
| results[i] = formatMeasure(measures[i], nf); |
| } |
| } |
| 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()); |
| } |
| return appendTo.append(builder.toString()); |
| } |
| |
| // type is one of "hm", "ms" or "hms" |
| private static DateFormat loadNumericDurationFormat( |
| ICUResourceBundle r, String type) { |
| r = r.getWithFallback(String.format("durationUnits/%s", type)); |
| // We replace 'h' with 'H' because 'h' does not make sense in the context of durations. |
| DateFormat result = new SimpleDateFormat(r.getString().replace("h", "H")); |
| result.setTimeZone(TimeZone.GMT_ZONE); |
| return result; |
| } |
| |
| // Returns hours in [0]; minutes in [1]; seconds in [2] out of measures array. If |
| // unsuccessful, e.g measures has other measurements besides hours, minutes, seconds; |
| // hours, minutes, seconds are out of order; or have negative values, returns null. |
| // If hours, minutes, or seconds is missing from measures the corresponding element in |
| // returned array will be null. |
| private static Number[] toHMS(Measure[] measures) { |
| Number[] result = new Number[3]; |
| int lastIdx = -1; |
| for (Measure m : measures) { |
| if (m.getNumber().doubleValue() < 0.0) { |
| return null; |
| } |
| Integer idxObj = hmsTo012.get(m.getUnit()); |
| if (idxObj == null) { |
| return null; |
| } |
| int idx = idxObj.intValue(); |
| if (idx <= lastIdx) { |
| // hour before minute before second |
| return null; |
| } |
| lastIdx = idx; |
| result[idx] = m.getNumber(); |
| } |
| return result; |
| } |
| |
| // Formats numeric time duration as 5:00:47 or 3:54. In the process, it replaces any null |
| // values in hms with 0. |
| private StringBuilder formatNumeric(Number[] hms, StringBuilder appendable) { |
| |
| // find the start and end of non-nil values in hms array. We have to know if we |
| // have hour-minute; minute-second; or hour-minute-second. |
| int startIndex = -1; |
| int endIndex = -1; |
| for (int i = 0; i < hms.length; i++) { |
| if (hms[i] != null) { |
| endIndex = i; |
| if (startIndex == -1) { |
| startIndex = endIndex; |
| } |
| } else { |
| // Replace nil value with 0. |
| hms[i] = Integer.valueOf(0); |
| } |
| } |
| // convert hours, minutes, seconds into milliseconds. |
| 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, |
| numericFormatters.getHourMinuteSecond(), |
| DateFormat.Field.SECOND, |
| hms[endIndex], |
| appendable); |
| } |
| // if minute-second |
| if (startIndex == 1 && endIndex == 2) { |
| return formatNumeric( |
| d, |
| numericFormatters.getMinuteSecond(), |
| DateFormat.Field.SECOND, |
| hms[endIndex], |
| appendable); |
| } |
| // if hour-minute |
| if (startIndex == 0 && endIndex == 1) { |
| return formatNumeric( |
| d, |
| numericFormatters.getHourMinute(), |
| DateFormat.Field.MINUTE, |
| hms[endIndex], |
| appendable); |
| } |
| throw new IllegalStateException(); |
| } |
| |
| // Formats a duration as 5:00:37 or 23:59. |
| // duration is a particular duration after epoch. |
| // formatter is a hour-minute-second, hour-minute, or minute-second formatter. |
| // smallestField denotes what the smallest field is in duration: either |
| // hour, minute, or second. |
| // smallestAmount is the value of that smallest field. for 5:00:37.3, |
| // 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( |
| Date duration, |
| DateFormat formatter, |
| DateFormat.Field smallestField, |
| Number smallestAmount, |
| StringBuilder appendTo) { |
| // Format the smallest amount ahead of time. |
| String smallestAmountFormatted; |
| |
| // Format the smallest amount using this object's number format, but keep track |
| // of the integer portion of this formatted amount. We have to replace just the |
| // integer part with the corresponding value from formatting the date. Otherwise |
| // 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(); |
| // Give up if there is no integer field. |
| if (intFieldPosition.getBeginIndex() == 0 && intFieldPosition.getEndIndex() == 0) { |
| throw new IllegalStateException(); |
| } |
| // Format our duration as a date, but keep track of where the smallest field is |
| // so that we can use it to replace the integer portion of the smallest value. |
| 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); |
| } |
| return appendTo; |
| } |
| |
| private Object writeReplace() throws ObjectStreamException { |
| return new MeasureProxy( |
| getLocale(), formatWidth, numberFormat.get(), MEASURE_FORMAT); |
| } |
| |
| static class MeasureProxy implements Externalizable { |
| private static final long serialVersionUID = -6033308329886716770L; |
| |
| private ULocale locale; |
| private FormatWidth formatWidth; |
| private NumberFormat numberFormat; |
| private int subClass; |
| private HashMap<Object, Object> keyValues; |
| |
| public MeasureProxy( |
| ULocale locale, |
| FormatWidth width, |
| NumberFormat numberFormat, |
| int subClass) { |
| this.locale = locale; |
| this.formatWidth = width; |
| this.numberFormat = numberFormat; |
| this.subClass = subClass; |
| this.keyValues = new HashMap<Object, Object>(); |
| } |
| |
| // Must have public constructor, to enable Externalizable |
| public MeasureProxy() { |
| } |
| |
| public void writeExternal(ObjectOutput out) throws IOException { |
| out.writeByte(0); // version |
| out.writeUTF(locale.toLanguageTag()); |
| out.writeByte(formatWidth.ordinal()); |
| out.writeObject(numberFormat); |
| out.writeByte(subClass); |
| out.writeObject(keyValues); |
| } |
| |
| @SuppressWarnings("unchecked") |
| public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { |
| in.readByte(); // version. |
| locale = ULocale.forLanguageTag(in.readUTF()); |
| formatWidth = fromFormatWidthOrdinal(in.readByte() & 0xFF); |
| numberFormat = (NumberFormat) in.readObject(); |
| if (numberFormat == null) { |
| throw new InvalidObjectException("Missing number format."); |
| } |
| subClass = in.readByte() & 0xFF; |
| |
| // This cast is safe because the serialized form of hashtable can have |
| // any object as the key and any object as the value. |
| keyValues = (HashMap<Object, Object>) in.readObject(); |
| if (keyValues == null) { |
| throw new InvalidObjectException("Missing optional values map."); |
| } |
| } |
| |
| private TimeUnitFormat createTimeUnitFormat() throws InvalidObjectException { |
| int style; |
| if (formatWidth == FormatWidth.WIDE) { |
| style = TimeUnitFormat.FULL_NAME; |
| } else if (formatWidth == FormatWidth.SHORT) { |
| style = TimeUnitFormat.ABBREVIATED_NAME; |
| } else { |
| throw new InvalidObjectException("Bad width: " + formatWidth); |
| } |
| TimeUnitFormat result = new TimeUnitFormat(locale, style); |
| result.setNumberFormat(numberFormat); |
| return result; |
| } |
| |
| private Object readResolve() throws ObjectStreamException { |
| switch (subClass) { |
| case MEASURE_FORMAT: |
| return MeasureFormat.getInstance(locale, formatWidth, numberFormat); |
| case TIME_UNIT_FORMAT: |
| return createTimeUnitFormat(); |
| case CURRENCY_FORMAT: |
| return new CurrencyFormat(locale); |
| default: |
| throw new InvalidObjectException("Unknown subclass: " + subClass); |
| } |
| } |
| } |
| |
| private static FormatWidth fromFormatWidthOrdinal(int ordinal) { |
| FormatWidth[] values = FormatWidth.values(); |
| if (ordinal < 0 || ordinal >= values.length) { |
| return FormatWidth.SHORT; |
| } |
| return values[ordinal]; |
| } |
| |
| private static final Map<ULocale, String> localeIdToRangeFormat = |
| new ConcurrentHashMap<ULocale, String>(); |
| |
| /** |
| * Return a formatter (compiled SimplePatternFormatter pattern) for a range, such as "{0}–{1}". |
| * @param forLocale locale to get the format for |
| * @param width the format width |
| * @return range formatter, such as "{0}–{1}" |
| * @deprecated This API is ICU internal only. |
| * @hide original deprecated declaration |
| * @hide draft / provisional / internal are hidden on Android |
| */ |
| @Deprecated |
| public static String getRangeFormat(ULocale forLocale, FormatWidth width) { |
| // TODO fix Hack for French |
| if (forLocale.getLanguage().equals("fr")) { |
| return getRangeFormat(ULocale.ROOT, width); |
| } |
| String result = localeIdToRangeFormat.get(forLocale); |
| if (result == null) { |
| ICUResourceBundle rb = (ICUResourceBundle)UResourceBundle. |
| getBundleInstance(ICUData.ICU_BASE_NAME, forLocale); |
| ULocale realLocale = rb.getULocale(); |
| if (!forLocale.equals(realLocale)) { // if the child would inherit, then add a cache entry for it. |
| result = localeIdToRangeFormat.get(forLocale); |
| if (result != null) { |
| localeIdToRangeFormat.put(forLocale, result); |
| return result; |
| } |
| } |
| // At this point, both the forLocale and the realLocale don't have an item |
| // So we have to make one. |
| NumberingSystem ns = NumberingSystem.getInstance(forLocale); |
| |
| String resultString = null; |
| try { |
| resultString = rb.getStringWithFallback("NumberElements/" + ns.getName() + "/miscPatterns/range"); |
| } catch ( MissingResourceException ex ) { |
| resultString = rb.getStringWithFallback("NumberElements/latn/patterns/range"); |
| } |
| result = SimplePatternFormatter.compileToStringMinMaxPlaceholders(resultString, new StringBuilder(), 2, 2); |
| localeIdToRangeFormat.put(forLocale, result); |
| if (!forLocale.equals(realLocale)) { |
| localeIdToRangeFormat.put(realLocale, result); |
| } |
| } |
| return result; |
| } |
| } |