All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.ibm.icu.text.MeasureFormat Maven / Gradle / Ivy

Go to download

International Component for Unicode for Java (ICU4J) is a mature, widely used Java library providing Unicode and Globalization support

There is a newer version: 76.1
Show newest version
// © 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 com.ibm.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 com.ibm.icu.impl.DontCareFieldPosition;
import com.ibm.icu.impl.FormattedStringBuilder;
import com.ibm.icu.impl.FormattedValueStringBuilderImpl;
import com.ibm.icu.impl.ICUData;
import com.ibm.icu.impl.ICUResourceBundle;
import com.ibm.icu.impl.SimpleCache;
import com.ibm.icu.impl.SimpleFormatterImpl;
import com.ibm.icu.impl.Utility;
import com.ibm.icu.impl.number.DecimalQuantity;
import com.ibm.icu.impl.number.DecimalQuantity_DualStorageBCD;
import com.ibm.icu.impl.number.LongNameHandler;
import com.ibm.icu.impl.number.RoundingUtils;
import com.ibm.icu.number.IntegerWidth;
import com.ibm.icu.number.LocalizedNumberFormatter;
import com.ibm.icu.number.NumberFormatter;
import com.ibm.icu.number.NumberFormatter.UnitWidth;
import com.ibm.icu.number.Precision;
import com.ibm.icu.text.ListFormatter.FormattedListBuilder;
import com.ibm.icu.util.Currency;
import com.ibm.icu.util.ICUUncheckedIOException;
import com.ibm.icu.util.Measure;
import com.ibm.icu.util.MeasureUnit;
import com.ibm.icu.util.ULocale;
import com.ibm.icu.util.ULocale.Category;
import com.ibm.icu.util.UResourceBundle;

// If you update the examples in the doc, don't forget to update MesaureUnitTest.TestExamplesInDocs too.
/**
 * A formatter for Measure objects.
 *
 * 

* IMPORTANT: 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. *


* *

* 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: * *

 * 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));
 * 
*

* This class does not do conversions from one unit to another. It simply formats whatever units it is * given *

* 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 com.ibm.icu.text.UFormat * @author Alan Liu * @stable ICU 3.0 */ 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 localeToNumericDurationFormatters = new SimpleCache<>(); private static final Map 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. * * @stable ICU 53 */ // Be sure to update MeasureUnitTest.TestSerialFormatWidthEnum // when adding an enum value. public enum FormatWidth { /** * Spell out everything. * * @stable ICU 53 */ WIDE(ListFormatter.Width.WIDE, UnitWidth.FULL_NAME, UnitWidth.FULL_NAME), /** * Abbreviate when possible. * * @stable ICU 53 */ SHORT(ListFormatter.Width.SHORT, UnitWidth.SHORT, UnitWidth.ISO_CODE), /** * Brief. Use only a symbol for the unit when possible. * * @stable ICU 53 */ 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. * * @stable ICU 53 */ 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. * * @internal Use {@link #getCurrencyFormat()} * @deprecated ICU 61 This API is ICU internal only. */ @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. * @stable ICU 53 */ 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. * @stable ICU 54 */ 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. * @stable ICU 53 */ 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. * @stable ICU 54 */ 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 formatMeasures 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) * * @stable ICU53 */ @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 Measure. * * @see java.text.Format#parseObject(java.lang.String, java.text.ParsePosition) * @throws UnsupportedOperationException * Not supported. * @draft ICU 53 (Retain) */ @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. * @stable ICU 53 */ 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. * @stable ICU 55 */ 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...) * @stable ICU 53 */ 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. * * @stable ICU 58 */ 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. * * @stable ICU 3.0 */ @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} * * @stable ICU 3.0 */ @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. * * @stable ICU 53 */ public MeasureFormat.FormatWidth getWidth() { if (formatWidth == MeasureFormat.FormatWidth.DEFAULT_CURRENCY) { return MeasureFormat.FormatWidth.WIDE; } return formatWidth; } /** * Get the locale of this instance. * * @stable ICU 53 */ public final ULocale getLocale() { return getLocale(ULocale.VALID_LOCALE); } /** * Get a copy of the number format. * * @stable ICU 53 */ 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 * @stable ICU 3.0 */ 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 * @stable ICU 54 */ public static MeasureFormat getCurrencyFormat(Locale locale) { return getCurrencyFormat(ULocale.forLocale(locale)); } /** * Return a formatter for CurrencyAmount objects in the default FORMAT locale. * * @return a formatter object * @see Category#FORMAT * @stable ICU 3.0 */ 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 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) 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 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}" * @internal * @deprecated This API is ICU internal only. */ @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; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy