1069 lines
40 KiB
Java
1069 lines
40 KiB
Java
/* GENERATED SOURCE. DO NOT MODIFY. */
|
||
// © 2016 and later: Unicode, Inc. and others.
|
||
// License & terms of use: http://www.unicode.org/copyright.html
|
||
/*
|
||
**********************************************************************
|
||
* 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.math.RoundingMode;
|
||
import java.text.FieldPosition;
|
||
import java.text.ParsePosition;
|
||
import java.util.Arrays;
|
||
import java.util.Collection;
|
||
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.FormattedStringBuilder;
|
||
import android.icu.impl.FormattedValueStringBuilderImpl;
|
||
import android.icu.impl.ICUData;
|
||
import android.icu.impl.ICUResourceBundle;
|
||
import android.icu.impl.SimpleCache;
|
||
import android.icu.impl.SimpleFormatterImpl;
|
||
import android.icu.impl.Utility;
|
||
import android.icu.impl.number.DecimalQuantity;
|
||
import android.icu.impl.number.DecimalQuantity_DualStorageBCD;
|
||
import android.icu.impl.number.LongNameHandler;
|
||
import android.icu.impl.number.RoundingUtils;
|
||
import android.icu.number.IntegerWidth;
|
||
import android.icu.number.LocalizedNumberFormatter;
|
||
import android.icu.number.NumberFormatter;
|
||
import android.icu.number.NumberFormatter.UnitWidth;
|
||
import android.icu.number.Precision;
|
||
import android.icu.text.ListFormatter.FormattedListBuilder;
|
||
import android.icu.util.Currency;
|
||
import android.icu.util.ICUUncheckedIOException;
|
||
import android.icu.util.Measure;
|
||
import android.icu.util.MeasureUnit;
|
||
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>
|
||
* <strong>IMPORTANT:</strong> New users are strongly encouraged to see if
|
||
* {@link NumberFormatter} fits their use case. Although not deprecated, this
|
||
* class, MeasureFormat, is provided for backwards compatibility only, and has
|
||
* much more limited capabilities.
|
||
* <hr>
|
||
*
|
||
* <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 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 NumberFormat numberFormat;
|
||
|
||
private final transient LocalizedNumberFormatter numberFormatter;
|
||
|
||
private static final SimpleCache<ULocale, NumericFormatters> localeToNumericDurationFormatters = new SimpleCache<>();
|
||
|
||
private static final Map<MeasureUnit, Integer> hmsTo012 = new HashMap<>();
|
||
|
||
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.Width.WIDE, UnitWidth.FULL_NAME, UnitWidth.FULL_NAME),
|
||
|
||
/**
|
||
* Abbreviate when possible.
|
||
*/
|
||
SHORT(ListFormatter.Width.SHORT, UnitWidth.SHORT, UnitWidth.ISO_CODE),
|
||
|
||
/**
|
||
* Brief. Use only a symbol for the unit when possible.
|
||
*/
|
||
NARROW(ListFormatter.Width.NARROW, UnitWidth.NARROW, UnitWidth.SHORT),
|
||
|
||
/**
|
||
* 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.Width.NARROW, UnitWidth.NARROW, UnitWidth.SHORT),
|
||
|
||
/**
|
||
* The default format width for getCurrencyFormat(), which is to show the symbol for currency
|
||
* (UnitWidth.SHORT) but wide for other units.
|
||
*
|
||
* @deprecated ICU 61 This API is ICU internal only.
|
||
* @hide draft / provisional / internal are hidden on Android
|
||
*/
|
||
@Deprecated
|
||
DEFAULT_CURRENCY(ListFormatter.Width.SHORT, UnitWidth.FULL_NAME, UnitWidth.SHORT);
|
||
|
||
final ListFormatter.Width listWidth;
|
||
|
||
/**
|
||
* 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.Width listWidth,
|
||
UnitWidth unitWidth,
|
||
UnitWidth currencyWidth) {
|
||
this.listWidth = listWidth;
|
||
this.unitWidth = unitWidth;
|
||
this.currencyWidth = currencyWidth;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 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) {
|
||
return new MeasureFormat(locale, formatWidth, format, null, null);
|
||
}
|
||
|
||
/**
|
||
* 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 fpos
|
||
* 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 fpos) {
|
||
int prevLength = toAppendTo.length();
|
||
fpos.setBeginIndex(0);
|
||
fpos.setEndIndex(0);
|
||
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;
|
||
}
|
||
formatMeasuresInternal(toAppendTo, fpos, measures);
|
||
} else if (obj instanceof Measure[]) {
|
||
formatMeasuresInternal(toAppendTo, fpos, (Measure[]) obj);
|
||
} else if (obj instanceof Measure) {
|
||
FormattedStringBuilder result = formatMeasure((Measure) obj);
|
||
// No offset: toAppendTo.length() is considered below
|
||
FormattedValueStringBuilderImpl.nextFieldPosition(result, fpos);
|
||
Utility.appendTo(result, toAppendTo);
|
||
} else {
|
||
throw new IllegalArgumentException(obj.toString());
|
||
}
|
||
if (prevLength > 0 && fpos.getEndIndex() != 0) {
|
||
fpos.setBeginIndex(fpos.getBeginIndex() + prevLength);
|
||
fpos.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();
|
||
}
|
||
|
||
// NOTE: For formatMeasureRange(), see https://unicode-org.atlassian.net/browse/ICU-12454
|
||
|
||
/**
|
||
* 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.
|
||
*/
|
||
public StringBuilder formatMeasurePerUnit(
|
||
Measure measure,
|
||
MeasureUnit perUnit,
|
||
StringBuilder appendTo,
|
||
FieldPosition pos) {
|
||
DecimalQuantity dq = new DecimalQuantity_DualStorageBCD(measure.getNumber());
|
||
FormattedStringBuilder string = new FormattedStringBuilder();
|
||
getUnitFormatterFromCache(
|
||
NUMBER_FORMATTER_STANDARD, measure.getUnit(), perUnit
|
||
).formatImpl(dq, string);
|
||
DecimalFormat.fieldPositionHelper(dq, string, pos, appendTo.length());
|
||
Utility.appendTo(string, appendTo);
|
||
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 fpos
|
||
* 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 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;
|
||
}
|
||
if (measures.length == 1) {
|
||
FormattedStringBuilder result = formatMeasure(measures[0]);
|
||
FormattedValueStringBuilderImpl.nextFieldPosition(result, fieldPosition);
|
||
Utility.appendTo(result, appendTo);
|
||
return;
|
||
}
|
||
|
||
if (formatWidth == FormatWidth.NUMERIC) {
|
||
// If we have just hour, minute, or second follow the numeric
|
||
// track.
|
||
Number[] hms = toHMS(measures);
|
||
if (hms != null) {
|
||
formatNumeric(hms, appendTo);
|
||
return;
|
||
}
|
||
}
|
||
|
||
ListFormatter listFormatter = ListFormatter.getInstance(getLocale(),
|
||
ListFormatter.Type.UNITS,
|
||
formatWidth.listWidth);
|
||
if (fieldPosition != DontCareFieldPosition.INSTANCE) {
|
||
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++) {
|
||
if (i == measures.length - 1) {
|
||
results[i] = formatMeasure(measures[i]).toString();
|
||
} else {
|
||
results[i] = formatMeasureInteger(measures[i]).toString();
|
||
}
|
||
}
|
||
FormattedListBuilder builder = listFormatter.formatImpl(Arrays.asList(results), false);
|
||
builder.appendTo(appendTo);
|
||
}
|
||
|
||
/**
|
||
* Gets the display name of the specified {@link MeasureUnit} corresponding to the current locale and
|
||
* format width.
|
||
*
|
||
* @param unit
|
||
* The unit for which to get a display name.
|
||
* @return The display name in the locale and width specified in {@link MeasureFormat#getInstance},
|
||
* or null if there is no display name available for the specified unit.
|
||
*/
|
||
public String getUnitDisplayName(MeasureUnit unit) {
|
||
return LongNameHandler.getUnitDisplayName(getLocale(), unit, formatWidth.unitWidth);
|
||
}
|
||
|
||
/**
|
||
* 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())
|
||
&& getNumberFormatInternal().equals(rhs.getNumberFormatInternal());
|
||
}
|
||
|
||
/**
|
||
* {@inheritDoc}
|
||
*/
|
||
@Override
|
||
public final int hashCode() {
|
||
// A very slow but safe implementation.
|
||
return (getLocale().hashCode() * 31 + getNumberFormatInternal().hashCode()) * 31 + getWidth().hashCode();
|
||
}
|
||
|
||
/**
|
||
* Get the format width this instance is using.
|
||
*/
|
||
public MeasureFormat.FormatWidth getWidth() {
|
||
if (formatWidth == MeasureFormat.FormatWidth.DEFAULT_CURRENCY) {
|
||
return MeasureFormat.FormatWidth.WIDE;
|
||
}
|
||
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) numberFormat.clone();
|
||
}
|
||
|
||
/**
|
||
* Get a copy of the number format without cloning. Internal method.
|
||
*/
|
||
NumberFormat getNumberFormatInternal() {
|
||
return numberFormat;
|
||
}
|
||
|
||
/**
|
||
* 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.formatWidth,
|
||
format,
|
||
this.rules,
|
||
this.numericFormatters);
|
||
}
|
||
|
||
MeasureFormat(ULocale locale, FormatWidth formatWidth) {
|
||
this(locale, formatWidth, null, null, null);
|
||
}
|
||
|
||
private MeasureFormat(
|
||
ULocale locale,
|
||
FormatWidth formatWidth,
|
||
NumberFormat numberFormat,
|
||
PluralRules rules,
|
||
NumericFormatters formatters) {
|
||
// Needed for getLocale(ULocale.VALID_LOCALE).
|
||
setLocale(locale, locale);
|
||
this.formatWidth = formatWidth;
|
||
|
||
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;
|
||
|
||
if (!(numberFormat instanceof DecimalFormat)) {
|
||
throw new IllegalArgumentException();
|
||
}
|
||
numberFormatter = ((DecimalFormat) numberFormat).toNumberFormatter()
|
||
.unitWidth(formatWidth.unitWidth);
|
||
}
|
||
|
||
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 {
|
||
private String hourMinute;
|
||
private String minuteSecond;
|
||
private String hourMinuteSecond;
|
||
|
||
public NumericFormatters(
|
||
String hourMinute,
|
||
String minuteSecond,
|
||
String hourMinuteSecond) {
|
||
this.hourMinute = hourMinute;
|
||
this.minuteSecond = minuteSecond;
|
||
this.hourMinuteSecond = hourMinuteSecond;
|
||
}
|
||
|
||
public String getHourMinute() {
|
||
return hourMinute;
|
||
}
|
||
|
||
public String getMinuteSecond() {
|
||
return minuteSecond;
|
||
}
|
||
|
||
public String 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"));
|
||
}
|
||
|
||
/// BEGIN NUMBER FORMATTER CACHING MACHINERY ///
|
||
|
||
static final int NUMBER_FORMATTER_STANDARD = 1;
|
||
static final int NUMBER_FORMATTER_CURRENCY = 2;
|
||
static final int NUMBER_FORMATTER_INTEGER = 3;
|
||
|
||
static class NumberFormatterCacheEntry {
|
||
int type;
|
||
MeasureUnit unit;
|
||
MeasureUnit perUnit;
|
||
LocalizedNumberFormatter formatter;
|
||
}
|
||
|
||
// 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;
|
||
}
|
||
if (formatter2 != null) {
|
||
if (formatter2.type == type
|
||
&& formatter2.unit == unit
|
||
&& formatter2.perUnit == perUnit) {
|
||
return formatter2.formatter;
|
||
}
|
||
if (formatter3 != null) {
|
||
if (formatter3.type == type
|
||
&& formatter3.unit == unit
|
||
&& formatter3.perUnit == perUnit) {
|
||
return formatter3.formatter;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 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)
|
||
.precision(Precision.integer().withMode(
|
||
RoundingUtils.mathContextUnlimited(RoundingMode.DOWN)));
|
||
}
|
||
formatter3 = formatter2;
|
||
formatter2 = formatter1;
|
||
formatter1 = new NumberFormatterCacheEntry();
|
||
formatter1.type = type;
|
||
formatter1.unit = unit;
|
||
formatter1.perUnit = perUnit;
|
||
formatter1.formatter = formatter;
|
||
return formatter;
|
||
}
|
||
|
||
synchronized void clearCache() {
|
||
formatter1 = null;
|
||
formatter2 = null;
|
||
formatter3 = null;
|
||
}
|
||
|
||
// Can be overridden by subclasses:
|
||
LocalizedNumberFormatter getNumberFormatter() {
|
||
return numberFormatter;
|
||
}
|
||
|
||
/// END NUMBER FORMATTER CACHING MACHINERY ///
|
||
|
||
private FormattedStringBuilder formatMeasure(Measure measure) {
|
||
MeasureUnit unit = measure.getUnit();
|
||
DecimalQuantity dq = new DecimalQuantity_DualStorageBCD(measure.getNumber());
|
||
FormattedStringBuilder string = new FormattedStringBuilder();
|
||
if (unit instanceof Currency) {
|
||
getUnitFormatterFromCache(NUMBER_FORMATTER_CURRENCY, unit, null)
|
||
.formatImpl(dq, string);
|
||
} else {
|
||
getUnitFormatterFromCache(NUMBER_FORMATTER_STANDARD, unit, null)
|
||
.formatImpl(dq, string);
|
||
}
|
||
return string;
|
||
}
|
||
|
||
private FormattedStringBuilder formatMeasureInteger(Measure measure) {
|
||
DecimalQuantity dq = new DecimalQuantity_DualStorageBCD(measure.getNumber());
|
||
FormattedStringBuilder string = new FormattedStringBuilder();
|
||
getUnitFormatterFromCache(NUMBER_FORMATTER_INTEGER, measure.getUnit(), null)
|
||
.formatImpl(dq, string);
|
||
return string;
|
||
}
|
||
|
||
private void formatMeasuresSlowTrack(
|
||
ListFormatter listFormatter,
|
||
Appendable 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) {
|
||
FormattedStringBuilder result;
|
||
if (i == measures.length - 1) {
|
||
result = formatMeasure(measures[i]);
|
||
} else {
|
||
result = formatMeasureInteger(measures[i]);
|
||
}
|
||
if (fieldPositionFoundIndex == -1) {
|
||
FormattedValueStringBuilderImpl.nextFieldPosition(result, fpos);
|
||
if (fpos.getEndIndex() != 0) {
|
||
fieldPositionFoundIndex = i;
|
||
}
|
||
}
|
||
results[i] = result.toString();
|
||
}
|
||
ListFormatter.FormattedListBuilder builder = listFormatter.formatImpl(Arrays.asList(results), true);
|
||
|
||
// Fix up FieldPosition indexes if our field is found.
|
||
int offset = builder.getOffset(fieldPositionFoundIndex);
|
||
if (offset != -1) {
|
||
fieldPosition.setBeginIndex(fpos.getBeginIndex() + offset);
|
||
fieldPosition.setEndIndex(fpos.getEndIndex() + offset);
|
||
}
|
||
builder.appendTo(appendTo);
|
||
}
|
||
|
||
// type is one of "hm", "ms" or "hms"
|
||
private static String 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.
|
||
return r.getString().replace("h", "H");
|
||
}
|
||
|
||
// 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 void formatNumeric(Number[] hms, Appendable appendable) {
|
||
String pattern;
|
||
|
||
// All possible combinations: "h", "m", "s", "hm", "hs", "ms", "hms"
|
||
if (hms[0] != null && hms[2] != null) { // "hms" & "hs" (we add minutes if "hs")
|
||
pattern = numericFormatters.getHourMinuteSecond();
|
||
if (hms[1] == null)
|
||
hms[1] = 0;
|
||
hms[1] = Math.floor(hms[1].doubleValue());
|
||
hms[0] = Math.floor(hms[0].doubleValue());
|
||
} else if (hms[0] != null && hms[1] != null) { // "hm"
|
||
pattern = numericFormatters.getHourMinute();
|
||
hms[0] = Math.floor(hms[0].doubleValue());
|
||
} else if (hms[1] != null && hms[2] != null) { // "ms"
|
||
pattern = numericFormatters.getMinuteSecond();
|
||
hms[1] = Math.floor(hms[1].doubleValue());
|
||
} else { // h m s, handled outside formatNumeric. No value is also an error.
|
||
throw new IllegalStateException();
|
||
}
|
||
|
||
// We can create it on demand, but all of the patterns (right now) have mm and ss.
|
||
// So unless it is hours only we will need a 0-padded 2 digits formatter.
|
||
LocalizedNumberFormatter numberFormatter2 = numberFormatter.integerWidth(IntegerWidth.zeroFillTo(2));
|
||
FormattedStringBuilder fsb = new FormattedStringBuilder();
|
||
|
||
boolean protect = false;
|
||
for (int i = 0; i < pattern.length(); i++) {
|
||
char c = pattern.charAt(i);
|
||
|
||
// Also set the proper field in this switch
|
||
// We don't use DateFormat.Field because this is not a date / time, is a duration.
|
||
Number value = 0;
|
||
switch (c) {
|
||
case 'H': value = hms[0]; break;
|
||
case 'm': value = hms[1]; break;
|
||
case 's': value = hms[2]; break;
|
||
}
|
||
|
||
// There is not enough info to add Field(s) for the unit because all we have are plain
|
||
// text patterns. For example in "21:51" there is no text for something like "hour",
|
||
// while in something like "21h51" there is ("h"). But we can't really tell...
|
||
switch (c) {
|
||
case 'H':
|
||
case 'm':
|
||
case 's':
|
||
if (protect) {
|
||
fsb.appendChar16(c, null);
|
||
} else {
|
||
if ((i + 1 < pattern.length()) && pattern.charAt(i + 1) == c) { // doubled
|
||
fsb.append(numberFormatter2.format(value), null); // TODO: Use proper Field
|
||
i++;
|
||
} else {
|
||
fsb.append(numberFormatter.format(value), null); // TODO: Use proper Field
|
||
}
|
||
}
|
||
break;
|
||
case '\'':
|
||
// '' is escaped apostrophe
|
||
if ((i + 1 < pattern.length()) && pattern.charAt(i + 1) == c) {
|
||
fsb.appendChar16(c, null);
|
||
i++;
|
||
} else {
|
||
protect = !protect;
|
||
}
|
||
break;
|
||
default:
|
||
fsb.appendChar16(c, null);
|
||
}
|
||
}
|
||
|
||
try {
|
||
appendable.append(fsb);
|
||
} catch (IOException e) {
|
||
throw new ICUUncheckedIOException(e);
|
||
}
|
||
}
|
||
|
||
Object toTimeUnitProxy() {
|
||
return new MeasureProxy(getLocale(), formatWidth, getNumberFormatInternal(), TIME_UNIT_FORMAT);
|
||
}
|
||
|
||
Object toCurrencyProxy() {
|
||
return new MeasureProxy(getLocale(), formatWidth, getNumberFormatInternal(), CURRENCY_FORMAT);
|
||
}
|
||
|
||
private Object writeReplace() throws ObjectStreamException {
|
||
return new MeasureProxy(getLocale(), formatWidth, getNumberFormatInternal(), 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<>();
|
||
}
|
||
|
||
// Must have public constructor, to enable Externalizable
|
||
public MeasureProxy() {
|
||
}
|
||
|
||
@Override
|
||
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);
|
||
}
|
||
|
||
@Override
|
||
@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 MeasureFormat.getCurrencyFormat(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<>();
|
||
|
||
/**
|
||
* Return a formatter (compiled SimpleFormatter 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 = SimpleFormatterImpl
|
||
.compileToStringMinMaxArguments(resultString, new StringBuilder(), 2, 2);
|
||
localeIdToRangeFormat.put(forLocale, result);
|
||
if (!forLocale.equals(realLocale)) {
|
||
localeIdToRangeFormat.put(realLocale, result);
|
||
}
|
||
}
|
||
return result;
|
||
}
|
||
}
|