script-astra/Android/Sdk/sources/android-35/android/icu/text/MeasureFormat.java
localadmin 4380f00a78 init
2025-01-20 18:15:20 +03:00

1069 lines
40 KiB
Java
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* 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&lt;? extends Measure&gt;, 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&lt;? extends Measure&gt;, 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 callers
* 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;
}
}