| // © 2020 and later: Unicode, Inc. and others. |
| // License & terms of use: http://www.unicode.org/copyright.html |
| package com.ibm.icu.impl.number; |
| |
| import com.ibm.icu.impl.FormattedStringBuilder; |
| import com.ibm.icu.impl.SimpleFormatterImpl; |
| import com.ibm.icu.impl.StandardPlural; |
| import com.ibm.icu.number.LocalizedNumberFormatter; |
| import com.ibm.icu.number.NumberFormatter; |
| import com.ibm.icu.text.ListFormatter; |
| import com.ibm.icu.text.PluralRules; |
| import com.ibm.icu.text.SimpleFormatter; |
| import com.ibm.icu.util.MeasureUnit; |
| import com.ibm.icu.util.ULocale; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| /** Similar to LongNameHandler, but only for MIXED units. */ |
| public class MixedUnitLongNameHandler |
| implements MicroPropsGenerator, ModifierStore, LongNameMultiplexer.ParentlessMicroPropsGenerator { |
| private final PluralRules rules; |
| private final MicroPropsGenerator parent; |
| |
| /** |
| * Stores unit data for each of the individual units. For each unit, it |
| * stores ARRAY_LENGTH strings, as returned by getMeasureData. |
| */ |
| private List<String[]> fMixedUnitData; |
| |
| /** |
| * A localized NumberFormatter used to format the integer-valued bigger |
| * units of Mixed Unit measurements. |
| */ |
| private LocalizedNumberFormatter fIntegerFormatter; |
| |
| /** A localised list formatter for joining mixed units together. */ |
| private ListFormatter fListFormatter; |
| |
| private MixedUnitLongNameHandler(PluralRules rules, MicroPropsGenerator parent) { |
| this.rules = rules; |
| this.parent = parent; |
| } |
| |
| /** |
| * Construct a localized MixedUnitLongNameHandler for the specified |
| * MeasureUnit. It must be a MIXED unit. |
| * <p> |
| * |
| * @param locale The desired locale. |
| * @param mixedUnit The mixed measure unit to construct a |
| * MixedUnitLongNameHandler for. |
| * @param width Specifies the desired unit rendering. |
| * @param rules PluralRules instance. |
| * @param parent MicroPropsGenerator instance. |
| */ |
| public static MixedUnitLongNameHandler forMeasureUnit(ULocale locale, MeasureUnit mixedUnit, |
| NumberFormatter.UnitWidth width, PluralRules rules, |
| MicroPropsGenerator parent) { |
| assert (mixedUnit.getComplexity() == MeasureUnit.Complexity.MIXED); |
| |
| MixedUnitLongNameHandler result = new MixedUnitLongNameHandler(rules, parent); |
| List<MeasureUnit> individualUnits = mixedUnit.splitToSingleUnits(); |
| |
| result.fMixedUnitData = new ArrayList<>(); |
| for (int i = 0; i < individualUnits.size(); i++) { |
| // Grab data for each of the components. |
| String[] unitData = new String[LongNameHandler.ARRAY_LENGTH]; |
| LongNameHandler.getMeasureData(locale, individualUnits.get(i), width, unitData); |
| result.fMixedUnitData.add(unitData); |
| } |
| |
| ListFormatter.Width listWidth = ListFormatter.Width.SHORT; |
| if (width == NumberFormatter.UnitWidth.NARROW) { |
| listWidth = ListFormatter.Width.NARROW; |
| } else if (width == NumberFormatter.UnitWidth.FULL_NAME) { |
| // This might be the same as SHORT in most languages: |
| listWidth = ListFormatter.Width.WIDE; |
| } |
| |
| result.fListFormatter = ListFormatter.getInstance(locale, ListFormatter.Type.UNITS, listWidth); |
| |
| |
| // We need a localised NumberFormatter for the integers of the bigger units |
| // (providing Arabic numerals, for example). |
| result.fIntegerFormatter = NumberFormatter.withLocale(locale); |
| |
| return result; |
| } |
| |
| /** |
| * Produces a plural-appropriate Modifier for a mixed unit: `quantity` is |
| * taken as the final smallest unit, while the larger unit values must be |
| * provided by `micros.mixedMeasures`, micros being the MicroProps instance |
| * returned by the parent. |
| * |
| * This function must not be called if this instance has no parent: call |
| * processQuantityWithMicros() instead. |
| */ |
| @Override |
| public MicroProps processQuantity(DecimalQuantity quantity) { |
| assert (fMixedUnitData.size() > 1); |
| MicroProps micros; |
| micros = parent.processQuantity(quantity); |
| micros.modOuter = getMixedUnitModifier(quantity, micros); |
| return micros; |
| } |
| |
| /** |
| * Produces a plural-appropriate Modifier for a mixed unit: `quantity` is |
| * taken as the final smallest unit, while the larger unit values must be |
| * provided via `micros.mixedMeasures`. |
| * |
| * Does not call parent.processQuantity, so cannot get a MicroProps instance |
| * that way. Instead, the instance is passed in as a parameter. |
| */ |
| public MicroProps processQuantityWithMicros(DecimalQuantity quantity, MicroProps micros) { |
| assert (fMixedUnitData.size() > 1); |
| micros.modOuter = getMixedUnitModifier(quantity, micros); |
| return micros; |
| } |
| |
| /** |
| * Required for ModifierStore. And ModifierStore is required by |
| * SimpleModifier constructor's last parameter. We assert his will never get |
| * called though. |
| */ |
| @Override |
| public Modifier getModifier(Modifier.Signum signum, StandardPlural plural) { |
| // TODO(units): investigate this method while investigating where |
| // LongNameHandler.getModifier() gets used. To be sure it remains |
| // unreachable: |
| assert false : "should be unreachable"; |
| return null; |
| } |
| |
| /** |
| * For a mixed unit, returns a Modifier that takes only one parameter: the |
| * smallest and final unit of the set. The bigger units' values and labels |
| * get baked into this Modifier, together with the unit label of the final |
| * unit. |
| */ |
| private Modifier getMixedUnitModifier(DecimalQuantity quantity, MicroProps micros) { |
| if (micros.mixedMeasures.size() == 0) { |
| assert false : "Mixed unit: we must have more than one unit value"; |
| throw new UnsupportedOperationException(); |
| } |
| |
| // Algorithm: |
| // |
| // For the mixed-units measurement of: "3 yard, 1 foot, 2.6 inch", we should |
| // find "3 yard" and "1 foot" in micros.mixedMeasures. |
| // |
| // Obtain long-names with plural forms corresponding to measure values: |
| // * {0} yards, {0} foot, {0} inches |
| // |
| // Format the integer values appropriately and modify with the format |
| // strings: |
| // - 3 yards, 1 foot |
| // |
| // Use ListFormatter to combine, with one placeholder: |
| // - 3 yards, 1 foot and {0} inches /* TODO: how about the case of `1 inch` */ |
| // |
| // Return a SimpleModifier for this pattern, letting the rest of the |
| // pipeline take care of the remaining inches. |
| |
| List<String> outputMeasuresList = new ArrayList<>(); |
| |
| for (int i = 0; i < micros.mixedMeasures.size(); i++) { |
| DecimalQuantity fdec = new DecimalQuantity_DualStorageBCD(micros.mixedMeasures.get(i).getNumber()); |
| StandardPlural pluralForm = fdec.getStandardPlural(rules); |
| |
| String simpleFormat = LongNameHandler.getWithPlural(this.fMixedUnitData.get(i), pluralForm); |
| SimpleFormatter compiledFormatter = SimpleFormatter.compileMinMaxArguments(simpleFormat, 0, 1); |
| |
| FormattedStringBuilder appendable = new FormattedStringBuilder(); |
| this.fIntegerFormatter.formatImpl(fdec, appendable); |
| outputMeasuresList.add(compiledFormatter.format(appendable.toString())); |
| // TODO(icu-units#67): fix field positions |
| } |
| |
| String[] finalSimpleFormats = this.fMixedUnitData.get(this.fMixedUnitData.size() - 1); |
| StandardPlural finalPlural = RoundingUtils.getPluralSafe(micros.rounder, rules, quantity); |
| String finalSimpleFormat = LongNameHandler.getWithPlural(finalSimpleFormats, finalPlural); |
| SimpleFormatter finalFormatter = SimpleFormatter.compileMinMaxArguments(finalSimpleFormat, 0, 1); |
| outputMeasuresList.add(finalFormatter.format("{0}")); |
| |
| // Combine list into a "premixed" pattern |
| String premixedFormatPattern = this.fListFormatter.format(outputMeasuresList); |
| StringBuilder sb = new StringBuilder(); |
| String premixedCompiled = |
| SimpleFormatterImpl.compileToStringMinMaxArguments(premixedFormatPattern, sb, 0, 1); |
| |
| // TODO(icu-units#67): fix field positions |
| Modifier.Parameters params = new Modifier.Parameters(); |
| params.obj = this; |
| params.signum = Modifier.Signum.POS_ZERO; |
| params.plural = finalPlural; |
| // Return a SimpleModifier for the "premixed" pattern |
| return new SimpleModifier(premixedCompiled, null, false, params); |
| } |
| } |