| /* GENERATED SOURCE. DO NOT MODIFY. */ |
| // © 2020 and later: Unicode, Inc. and others. |
| // License & terms of use: http://www.unicode.org/copyright.html |
| package android.icu.impl.number; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import android.icu.impl.FormattedStringBuilder; |
| import android.icu.impl.SimpleFormatterImpl; |
| import android.icu.impl.StandardPlural; |
| import android.icu.number.LocalizedNumberFormatter; |
| import android.icu.number.NumberFormatter; |
| import android.icu.text.ListFormatter; |
| import android.icu.text.PluralRules; |
| import android.icu.text.SimpleFormatter; |
| import android.icu.util.MeasureUnit; |
| import android.icu.util.ULocale; |
| |
| /** Similar to LongNameHandler, but only for MIXED units. |
| * @hide Only a subset of ICU is exposed in Android*/ |
| 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 unitDisplayCase Specifies the desired grammatical case. If the |
| * specified case is not found, we fall back to nominative or no-case. |
| * @param rules PluralRules instance. |
| * @param parent MicroPropsGenerator instance. |
| */ |
| public static MixedUnitLongNameHandler forMeasureUnit(ULocale locale, |
| MeasureUnit mixedUnit, |
| NumberFormatter.UnitWidth width, |
| String unitDisplayCase, |
| PluralRules rules, |
| MicroPropsGenerator parent) { |
| assert mixedUnit.getComplexity() == MeasureUnit.Complexity.MIXED |
| : "MixedUnitLongNameHandler only supports MIXED units"; |
| // In ICU4C, in addition to an assert, we return a failure status if the |
| // unit is not mixed (commented by: "Defensive, for production code"). |
| // In Java, we don't have efficient access to MeasureUnitImpl, so we |
| // skip this check - relying on unit tests and the assert above to help |
| // enforce the invariant. |
| |
| 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, unitDisplayCase, |
| unitData); |
| // TODO(ICU-21494): if we add support for gender for mixed units, we may |
| // need LongNameHandler.maybeCalculateGender() here. |
| 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: {@code quantity} is |
| * taken as the final smallest unit, while the larger unit values must be |
| * provided by {@code 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: {@code quantity} is |
| * taken as the final smallest unit, while the larger unit values must be |
| * provided via {@code 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(icu-units#28): 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 we don't have at least one mixedMeasure, the LongNameHandler would be |
| // sufficient and we shouldn't be running MixedUnitLongNameHandler code: |
| 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<>(); |
| |
| StandardPlural quantityPlural = StandardPlural.OTHER; |
| for (int i = 0; i < micros.mixedMeasures.size(); i++) { |
| |
| if ( i == micros.indexOfQuantity) { |
| if (i > 0 && quantity.isNegative()) { |
| // If numbers are negative, only the first number needs to have its |
| // negative sign formatted. |
| quantity.negate(); |
| } |
| |
| quantityPlural = RoundingUtils.getPluralSafe(micros.rounder, rules, quantity); |
| String quantitySimpleFormat = LongNameHandler.getWithPlural(this.fMixedUnitData.get(i), quantityPlural); |
| SimpleFormatter finalFormatter = SimpleFormatter.compileMinMaxArguments(quantitySimpleFormat, 0, 1); |
| outputMeasuresList.add(finalFormatter.format("{0}")); |
| |
| continue; |
| } |
| |
| |
| DecimalQuantity fdec = new DecimalQuantity_DualStorageBCD(micros.mixedMeasures.get(i).getNumber()); |
| if (i > 0 && fdec.isNegative()) { |
| // If numbers are negative, only the first number needs to have its |
| // negative sign formatted. |
| fdec.negate(); |
| } |
| |
| StandardPlural pluralForm = RoundingUtils.getPluralSafe(micros.rounder, rules, fdec); |
| |
| 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 |
| } |
| |
| |
| // 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 = quantityPlural; |
| // Return a SimpleModifier for the "premixed" pattern |
| return new SimpleModifier(premixedCompiled, null, false, params); |
| } |
| } |