]> granicus.if.org Git - icu/commitdiff
ICU-20568 Add .unit().usage() support to ICU4J NumberFormatter (2/2)
authorHugo van der Merwe <17109322+hugovdm@users.noreply.github.com>
Wed, 16 Sep 2020 17:39:13 +0000 (19:39 +0200)
committerHugo van der Merwe <17109322+hugovdm@users.noreply.github.com>
Fri, 18 Sep 2020 23:31:08 +0000 (01:31 +0200)
* Throw UnsupportedOperationException for "unsanctioned units"
* Deal with the parent issue for LongNameMultiplexer
* Fix NullPointerException: UnitConversionHandler.processQuantity must
  call fParent.processQuantity
* toSkeleton not supported for not-built-in units
* Add and use interface
  LongNameMultiplexer.ParentlessMicroPropsGenerator
* Match up C++ and Java unit tests in NumberFormatterApiTest.java
* Permit user-override of precision() for usage(), closes icu-units#95
* Use BogusRounder to propagate mathContext.
* Port C++ change from PR #1322, commit c84ded050a, to Java.
* Test the usage-without-unit error. Document it in
  NumberFormatterSettings.java
* General review and corrections.

17 files changed:
icu4c/source/i18n/number_longnames.cpp
icu4c/source/i18n/number_longnames.h
icu4c/source/i18n/number_microprops.h
icu4c/source/i18n/number_usageprefs.cpp
icu4c/source/test/intltest/numbertest_api.cpp
icu4j/main/classes/core/src/com/ibm/icu/impl/number/LongNameHandler.java
icu4j/main/classes/core/src/com/ibm/icu/impl/number/LongNameMultiplexer.java
icu4j/main/classes/core/src/com/ibm/icu/impl/number/MicroProps.java
icu4j/main/classes/core/src/com/ibm/icu/impl/number/MicroPropsGenerator.java
icu4j/main/classes/core/src/com/ibm/icu/impl/number/MixedUnitLongNameHandler.java
icu4j/main/classes/core/src/com/ibm/icu/impl/number/UnitConversionHandler.java
icu4j/main/classes/core/src/com/ibm/icu/impl/number/UsagePrefsHandler.java
icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterImpl.java
icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterSettings.java
icu4j/main/classes/core/src/com/ibm/icu/number/NumberSkeletonImpl.java
icu4j/main/classes/core/src/com/ibm/icu/number/Precision.java
icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java

index 953f12f215e73448cb2e9370df9049acdca0cafb..97a59730e9ee16583594e7c2a4b99c466875bcf4 100644 (file)
@@ -450,10 +450,8 @@ void MixedUnitLongNameHandler::processQuantity(DecimalQuantity &quantity, MicroP
 const Modifier *MixedUnitLongNameHandler::getMixedUnitModifier(DecimalQuantity &quantity,
                                                                MicroProps &micros,
                                                                UErrorCode &status) const {
-    // TODO(icu-units#21): mixed units without usage() is not yet supported.
-    // That should be the only reason why this happens, so delete this whole if
-    // once fixed:
     if (micros.mixedMeasuresCount == 0) {
+        U_ASSERT(micros.mixedMeasuresCount > 0); // Mixed unit: we must have more than one unit value
         status = U_UNSUPPORTED_ERROR;
         return &micros.helpers.emptyWeakModifier;
     }
@@ -498,6 +496,7 @@ const Modifier *MixedUnitLongNameHandler::getMixedUnitModifier(DecimalQuantity &
         auto appendable = UnicodeStringAppendable(num);
         fIntegerFormatter.formatDecimalQuantity(fdec, status).appendTo(appendable, status);
         compiledFormatter.format(num, outputMeasuresList[i], status);
+        // TODO(icu-units#67): fix field positions
     }
 
     UnicodeString *finalSimpleFormats = &fMixedUnitData[(fMixedUnitCount - 1) * ARRAY_LENGTH];
@@ -515,6 +514,7 @@ const Modifier *MixedUnitLongNameHandler::getMixedUnitModifier(DecimalQuantity &
         return &micros.helpers.emptyWeakModifier;
     }
 
+    // TODO(icu-units#67): fix field positions
     // Return a SimpleModifier for the "premixed" pattern
     micros.helpers.mixedUnitModifier =
         SimpleModifier(premixedCompiled, kUndefinedField, false, {this, SIGNUM_POS_ZERO, finalPlural});
index 4146f76c2089f71fde34fe57c2babda97200e919..67f2316a9cd4bdfb72f2ee8a325167ebd78162d0 100644 (file)
@@ -159,14 +159,13 @@ class MixedUnitLongNameHandler : public MicroPropsGenerator, public ModifierStor
     // Not owned
     const MicroPropsGenerator *parent;
 
-    // Total number of units in the MeasureUnit this LongNameHandler was
-    // configured for: for "foot-and-inch", this will be 2. (If not a mixed unit,
-    // this will be 1.)
+    // Total number of units in the MeasureUnit this handler was configured for:
+    // for "foot-and-inch", this will be 2.
     int32_t fMixedUnitCount = 1;
-    // If this LongNameHandler is for a mixed unit, this stores unit data for
-    // each of the individual units. For each unit, it stores ARRAY_LENGTH
-    // strings, as returned by getMeasureData. (Each unit with index `i` has
-    // ARRAY_LENGTH strings starting at index `i*ARRAY_LENGTH` in this array.)
+    // Stores unit data for each of the individual units. For each unit, it
+    // stores ARRAY_LENGTH strings, as returned by getMeasureData. (Each unit
+    // with index `i` has ARRAY_LENGTH strings starting at index
+    // `i*ARRAY_LENGTH` in this array.)
     LocalArray<UnicodeString> fMixedUnitData;
     // A localized NumberFormatter used to format the integer-valued bigger
     // units of Mixed Unit measurements.
index 7258cc2e640b77ff2bb4fcf2b046f4cdacd47deb..058c5923b4567b64db2571fe992870e83a363e89 100644 (file)
@@ -67,11 +67,11 @@ class IntMeasures : public MaybeStackArray<int64_t, 2> {
     UErrorCode status = U_ZERO_ERROR;
 };
 
-// TODO(units): generated by MicroPropsGenerator, but inherits from it too. Do
-// we want to better document why? There's an explanation for processQuantity:
-//  * As MicroProps is the "base instance", this implementation of
-//  * MicroPropsGenerator::processQuantity() just ensures that the output
-//  * `micros` is correctly initialized.
+/**
+ * MicroProps is the first MicroPropsGenerator that should be should be called,
+ * producing an initialized MicroProps instance that will be passed on and
+ * modified throughout the rest of the chain of MicroPropsGenerator instances.
+ */
 struct MicroProps : public MicroPropsGenerator {
 
     // NOTE: All of these fields are properly initialized in NumberFormatterImpl.
index a1347e672a876b4b02f5b7a0ab5c94cfb2aa2b7a..494b9687b289ef6957ad4f6fcead85c08a3690a5 100644 (file)
@@ -103,6 +103,8 @@ void Usage::set(StringPiece value) {
     fUsage[fLength] = 0;
 }
 
+// Populates micros.mixedMeasures and modifies quantity, based on the values in
+// measures.
 void mixedMeasuresToMicros(const MaybeStackVector<Measure> &measures, DecimalQuantity *quantity,
                            MicroProps *micros, UErrorCode status) {
     micros->mixedMeasuresCount = measures.length() - 1;
@@ -159,13 +161,13 @@ void UsagePrefsHandler::processQuantity(DecimalQuantity &quantity, MicroProps &m
     if (U_FAILURE(status)) {
         return;
     }
-    const MaybeStackVector<Measure>& routedUnits = routed.measures;
+    const MaybeStackVector<Measure>& routedMeasures = routed.measures;
     micros.outputUnit = routed.outputUnit.copy(status).build(status);
     if (U_FAILURE(status)) {
         return;
     }
 
-    mixedMeasuresToMicros(routedUnits, &quantity, &micros, status);
+    mixedMeasuresToMicros(routedMeasures, &quantity, &micros, status);
 
     UnicodeString precisionSkeleton = routed.precision;
     if (micros.rounder.fPrecision.isBogus()) {
index cce8ddfac3f8216cf18f318dca177ea8c70d65e8..ecc0dcab18955a98ba3b5abd5d512586ab261556 100644 (file)
@@ -906,6 +906,11 @@ void NumberFormatterApiTest::unitUsage() {
     FormattedNumber formattedNum;
     UnicodeString uTestCase;
 
+    status.assertSuccess();
+    formattedNum =
+        NumberFormatter::with().usage("road").locale(Locale::getEnglish()).formatInt(1, status);
+    status.expectErrorAndReset(U_ILLEGAL_ARGUMENT_ERROR);
+
     unloc_formatter = NumberFormatter::with().usage("road").unit(MeasureUnit::getMeter());
 
     uTestCase = u"unitUsage() en-ZA road";
@@ -1113,25 +1118,49 @@ void NumberFormatterApiTest::unitUsage() {
             u"0 pounds, 0 ounces");
 
     assertFormatDescendingBig(
-        u"Scientific notation with Usage: possible when using a reasonable Precision",
-        u"scientific @### usage/default measure-unit/area-square-meter unit-width-full-name",
-        u"scientific @### usage/default unit/square-meter unit-width-full-name",
-        NumberFormatter::with()
-            .unit(SQUARE_METER)
-            .usage("default")
-            .notation(Notation::scientific())
-            .precision(Precision::minMaxSignificantDigits(1, 4))
-            .unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME),
-        Locale("en-ZA"),
-        u"8,765E1 square kilometres",
-        u"8,765E0 square kilometres",
-        u"8,765E1 hectares",
-        u"8,765E0 hectares",
-        u"8,765E3 square metres",
-        u"8,765E2 square metres",
-        u"8,765E1 square metres",
-        u"8,765E0 square metres",
-        u"0E0 square centimetres");
+            u"Scientific notation with Usage: possible when using a reasonable Precision",
+            u"scientific @### usage/default measure-unit/area-square-meter unit-width-full-name",
+            u"scientific @### usage/default unit/square-meter unit-width-full-name",
+            NumberFormatter::with()
+                    .unit(SQUARE_METER)
+                    .usage("default")
+                    .notation(Notation::scientific())
+                    .precision(Precision::minMaxSignificantDigits(1, 4))
+                    .unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME),
+            Locale("en-ZA"),
+            u"8,765E1 square kilometres",
+            u"8,765E0 square kilometres",
+            u"8,765E1 hectares",
+            u"8,765E0 hectares",
+            u"8,765E3 square metres",
+            u"8,765E2 square metres",
+            u"8,765E1 square metres",
+            u"8,765E0 square metres",
+            u"0E0 square centimetres");
+
+    assertFormatSingle(
+            u"Rounding Mode propagates: rounding down",
+            u"usage/road measure-unit/length-centimeter rounding-mode-floor",
+            u"usage/road unit/centimeter rounding-mode-floor",
+            NumberFormatter::with()
+                .unit(MeasureUnit::forIdentifier("centimeter", status))
+                .usage("road")
+                .roundingMode(UNUM_ROUND_FLOOR),
+            Locale("en-ZA"),
+            34500,
+            u"300 m");
+
+    assertFormatSingle(
+            u"Rounding Mode propagates: rounding up",
+            u"usage/road measure-unit/length-centimeter rounding-mode-ceiling",
+            u"usage/road unit/centimeter rounding-mode-ceiling",
+            NumberFormatter::with()
+                .unit(MeasureUnit::forIdentifier("centimeter", status))
+                .usage("road")
+                .roundingMode(UNUM_ROUND_CEILING),
+            Locale("en-ZA"),
+            30500,
+            u"350 m");
 }
 
 void NumberFormatterApiTest::unitUsageErrorCodes() {
index 695fc6abef72363b3e12777d04b6742e60cf1176..e320575ff1abc52131016844dc541ec1657c17b7 100644 (file)
@@ -22,11 +22,12 @@ import com.ibm.icu.util.MeasureUnit;
 import com.ibm.icu.util.ULocale;
 import com.ibm.icu.util.UResourceBundle;
 
-public class LongNameHandler implements MicroPropsGenerator, ModifierStore {
+public class LongNameHandler
+    implements MicroPropsGenerator, ModifierStore, LongNameMultiplexer.ParentlessMicroPropsGenerator {
 
     private static final int DNAM_INDEX = StandardPlural.COUNT;
     private static final int PER_INDEX = StandardPlural.COUNT + 1;
-    protected static final int ARRAY_LENGTH = StandardPlural.COUNT + 2;
+    static final int ARRAY_LENGTH = StandardPlural.COUNT + 2;
 
     private static int getIndex(String pluralKeyword) {
         // pluralKeyword can also be "dnam" or "per"
@@ -39,7 +40,7 @@ public class LongNameHandler implements MicroPropsGenerator, ModifierStore {
         }
     }
 
-    protected static String getWithPlural(String[] strings, StandardPlural plural) {
+    static String getWithPlural(String[] strings, StandardPlural plural) {
         String result = strings[plural.ordinal()];
         if (result == null) {
             result = strings[StandardPlural.OTHER.ordinal()];
@@ -79,7 +80,7 @@ public class LongNameHandler implements MicroPropsGenerator, ModifierStore {
 
     // NOTE: outArray MUST have at least ARRAY_LENGTH entries. No bounds checking is performed.
 
-    protected static void getMeasureData(
+    static void getMeasureData(
             ULocale locale,
             MeasureUnit unit,
             UnitWidth width,
@@ -199,13 +200,13 @@ public class LongNameHandler implements MicroPropsGenerator, ModifierStore {
      * <p>
      * Mixed units are not supported, use MixedUnitLongNameHandler.forMeasureUnit.
      *
-     * @param locale  The desired locale.
-     * @param unit    The measure unit to construct a LongNameHandler for. If
-     *                `perUnit` is also defined, `unit` must not be a mixed unit.
-     * @param perUnit If `unit` is a mixed unit, `perUnit` must be "none".
-     * @param width   Specifies the desired unit rendering.
-     * @param rules   Does not take ownership.
-     * @param parent  Does not take ownership.
+     * @param locale The desired locale.
+     * @param unit The measure unit to construct a LongNameHandler for. If
+     *     `perUnit` is also defined, `unit` must not be a mixed unit.
+     * @param perUnit If `unit` is a mixed unit, `perUnit` must be null.
+     * @param width Specifies the desired unit rendering.
+     * @param rules Plural rules.
+     * @param parent Plural rules.
      */
     public static LongNameHandler forMeasureUnit(
             ULocale locale,
@@ -214,6 +215,12 @@ public class LongNameHandler implements MicroPropsGenerator, ModifierStore {
             UnitWidth width,
             PluralRules rules,
             MicroPropsGenerator parent) {
+        if (unit.getType() == null || (perUnit != null && perUnit.getType() == null)) {
+            // TODO(ICU-20941): Unsanctioned unit. Not yet fully supported. Set an
+            // error code. Once we support not-built-in units here, unitRef may be
+            // anything, but if not built-in, perUnit has to be "none".
+            throw new UnsupportedOperationException("Unsanctioned units, not yet supported");
+        }
         if (perUnit != null) {
             // Compound unit: first try to simplify (e.g., meters per second is its own unit).
             MeasureUnit simplified = MeasureUnit.resolveUnitPerUnit(unit, perUnit);
@@ -314,6 +321,20 @@ public class LongNameHandler implements MicroPropsGenerator, ModifierStore {
         return micros;
     }
 
+    /**
+     * Produces a plural-appropriate Modifier for a 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) {
+        StandardPlural pluralForm = RoundingUtils.getPluralSafe(micros.rounder, rules, quantity);
+        micros.modOuter = modifiers.get(pluralForm);
+        return micros;
+    }
+
     @Override
     public Modifier getModifier(Signum signum, StandardPlural plural) {
         // Signum ignored
index 9f21d009b573fb41d481ed1c77a288c41f609708..a3f03c96aba34ba468af0bd3a2ce5a2e597212ab 100644 (file)
@@ -1,7 +1,5 @@
 // Â© 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.number.NumberFormatter;
@@ -13,10 +11,27 @@ import com.ibm.icu.util.ULocale;
 import java.util.ArrayList;
 import java.util.List;
 
+/**
+ * A MicroPropsGenerator that multiplexes between different LongNameHandlers,
+ * depending on the outputUnit.
+ *
+ * See processQuantity() for the input requirements.
+ */
 public class LongNameMultiplexer implements MicroPropsGenerator {
+    /**
+     * LongNameMultiplexer calls the parent MicroPropsGenerator itself,
+     * receiving the MicroProps instance in use for this formatting pipeline.
+     * Next it multiplexes between name handlers (fHandlers) which are not given
+     * access to a parent. Consequently LongNameMultiplexer must give these
+     * handlers the MicroProps instance.
+     */
+    public static interface ParentlessMicroPropsGenerator {
+        public MicroProps processQuantityWithMicros(DecimalQuantity quantity, MicroProps micros);
+    }
+
     private final MicroPropsGenerator fParent;
 
-    private List<MicroPropsGenerator> fHandlers;
+    private List<ParentlessMicroPropsGenerator> fHandlers;
 
     // Each MeasureUnit corresponds to the same-index MicroPropsGenerator
     // pointed to in fHandlers.
@@ -50,7 +65,7 @@ public class LongNameMultiplexer implements MicroPropsGenerator {
                 result.fHandlers.add(mlnh);
             } else {
                 LongNameHandler lnh = LongNameHandler
-                        .forMeasureUnit(locale, unit, NoUnit.BASE, width, rules, null );
+                        .forMeasureUnit(locale, unit, NoUnit.BASE, width, rules, null);
                 result.fHandlers.add(lnh);
             }
         }
@@ -62,7 +77,6 @@ public class LongNameMultiplexer implements MicroPropsGenerator {
     // one of the units provided to the factory function.
     @Override
     public MicroProps processQuantity(DecimalQuantity quantity) {
-
         // We call parent->processQuantity() from the Multiplexer, instead of
         // letting LongNameHandler handle it: we don't know which LongNameHandler to
         // call until we've called the parent!
@@ -70,12 +84,11 @@ public class LongNameMultiplexer implements MicroPropsGenerator {
 
         // Call the correct LongNameHandler based on outputUnit
         for (int i = 0; i < this.fHandlers.size(); i++) {
-            if (fMeasureUnits.get(i).equals( micros.outputUnit)) {
-                return fHandlers.get(i).processQuantity(quantity);
+            if (fMeasureUnits.get(i).equals(micros.outputUnit)) {
+                ParentlessMicroPropsGenerator handler = fHandlers.get(i);
+                return handler.processQuantityWithMicros(quantity, micros);
             }
-
         }
-
         throw new AssertionError
                 (" We shouldn't receive any outputUnit for which we haven't already got a LongNameHandler");
     }
index 3179e6c4a7596343cc1b0d719fa1b7bd794aea8e..dfb5ffdec4801aeeb8bc608197a0bc712af9553f 100644 (file)
@@ -12,11 +12,11 @@ import com.ibm.icu.util.MeasureUnit;
 
 import java.util.List;
 
-// TODO(units): generated by MicroPropsGenerator, but inherits from it too. Do we want to better document why?
-//  There's an explanation for processQuantity:
-//  - As MicroProps is the "base instance", this implementation of
-//  - MicoPropsGenerator::processQuantity() just ensures that the output
-//  - `micros` is correctly initialized.
+/**
+ * MicroProps is the first MicroPropsGenerator that should be should be called,
+ * producing an initialized MicroProps instance that will be passed on and
+ * modified throughout the rest of the chain of MicroPropsGenerator instances.
+ */
 public class MicroProps implements Cloneable, MicroPropsGenerator {
     // Populated globally:
     public SignDisplay sign;
@@ -55,7 +55,7 @@ public class MicroProps implements Cloneable, MicroPropsGenerator {
 
     // In the case of mixed units, this is the set of integer-only units
     // *preceding* the final unit.
-    public List<Measure> mixedMeasures ;
+    public List<Measure> mixedMeasures;
 
     private volatile boolean exhausted;
 
@@ -79,17 +79,19 @@ public class MicroProps implements Cloneable, MicroPropsGenerator {
      * will be modified and thus not be available for re-use.
      *
      * @param quantity The quantity for consideration and optional mutation.
-     * @return a MicroProps instance to populate.
+     * @return an initialized MicroProps instance.
      */
     @Override
     public MicroProps processQuantity(DecimalQuantity quantity) {
         if (immutable) {
             return (MicroProps) this.clone();
+        } else if (exhausted) {
+            // Safety check
+            throw new AssertionError("Cannot re-use a mutable MicroProps in the quantity chain");
+        } else {
+            exhausted = true;
+            return this;
         }
-
-        assert !exhausted : "Cannot re-use a mutable MicroProps in the quantity chain";
-        exhausted = true;
-        return this;
     }
 
     @Override
index 1ed61199b45dde631f4c3ca8702b1f187ce6e2b6..8580c54ca8603ccf16a155c520fd162a700f3604 100644 (file)
@@ -19,6 +19,7 @@ package com.ibm.icu.impl.number;
  * {@link MicroProps} with properties that are not quantity-dependent. Each element in the linked list
  * calls {@link #processQuantity} on its "parent", then does its work, and then returns the result.
  *
+ * <p>
  * This chain of MicroPropsGenerators is typically constructed by NumberFormatterImpl::macrosToMicroGenerator() when
  * constructing a NumberFormatter.
  *
index 7ad64bb82f3501de8c1f1b54b38d58b161d1f290..1c01c358748edb2b0d783c1ef0c2f30ae5b8b8f9 100644 (file)
@@ -1,10 +1,9 @@
 // Â© 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;
@@ -13,26 +12,28 @@ 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;
 
-public class MixedUnitLongNameHandler implements MicroPropsGenerator, ModifierStore {
-    // Not owned
+/** Similar to LongNameHandler, but only for MIXED units. */
+public class MixedUnitLongNameHandler
+    implements MicroPropsGenerator, ModifierStore, LongNameMultiplexer.ParentlessMicroPropsGenerator {
     private final PluralRules rules;
-    // Not owned
     private final MicroPropsGenerator parent;
 
-    // If this LongNameHandler is for a mixed unit, this stores unit data for
-    // each of the individual units. For each unit, it stores ARRAY_LENGTH
-    // strings, as returned by getMeasureData.
+    /**
+     * 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.
+    /**
+     * 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.
+    /** A localised list formatter for joining mixed units together. */
     private ListFormatter fListFormatter;
 
     private MixedUnitLongNameHandler(PluralRules rules, MicroPropsGenerator parent) {
@@ -49,8 +50,8 @@ public class MixedUnitLongNameHandler implements MicroPropsGenerator, ModifierSt
      * @param mixedUnit The mixed measure unit to construct a
      *                  MixedUnitLongNameHandler for.
      * @param width     Specifies the desired unit rendering.
-     * @param rules     Does not take ownership.
-     * @param parent    Does not take ownership.
+     * @param rules     PluralRules instance.
+     * @param parent    MicroPropsGenerator instance.
      */
     public static MixedUnitLongNameHandler forMeasureUnit(ULocale locale, MeasureUnit mixedUnit,
                                                           NumberFormatter.UnitWidth width, PluralRules rules,
@@ -89,43 +90,61 @@ public class MixedUnitLongNameHandler implements MicroPropsGenerator, ModifierSt
     /**
      * 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`.
+     * 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;
-        // if (parent != null)
         micros = parent.processQuantity(quantity);
         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.
+    /**
+     * 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.
+    /**
+     * 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) {
-        // TODO(icu-units#21): mixed units without usage() is not yet supported.
-        // That should be the only reason why this happens, so delete this whole if
-        // once fixed:
         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
@@ -153,30 +172,30 @@ public class MixedUnitLongNameHandler implements MicroPropsGenerator, ModifierSt
             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: fix this issue https://github.com/icu-units/icu/issues/67
+            // 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);
-        finalFormatter.format("{0}", outputMeasuresList.get(outputMeasuresList.size() -1));
+        outputMeasuresList.add(finalFormatter.format("{0}"));
 
         // Combine list into a "premixed" pattern
         String premixedFormatPattern = this.fListFormatter.format(outputMeasuresList);
-        SimpleFormatter premixedCompiled = SimpleFormatter.compileMinMaxArguments(premixedFormatPattern, 0, 1);
+        StringBuilder sb = new StringBuilder();
+        String premixedCompiled =
+            SimpleFormatterImpl.compileToStringMinMaxArguments(premixedFormatPattern, sb, 0, 1);
 
-        // Return a SimpleModifier for the "premixed" pattern
+        // 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 new SimpleModifier(premixedCompiled.getTextWithNoArguments(), null, false, params);
-        /*TODO: it was SimpleModifier(premixedCompiled, kUndefinedField, false, {this, SIGNUM_POS_ZERO, finalPlural});*/
+        // Return a SimpleModifier for the "premixed" pattern
+        return new SimpleModifier(premixedCompiled, null, false, params);
     }
 }
index cf3fd236a90204d0fe996408142862c1e84133b9..db270596c575c86ac90999aaa217568164b9f5c8 100644 (file)
@@ -1,6 +1,5 @@
 // Â© 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.units.ComplexUnitsConverter;
@@ -13,8 +12,9 @@ import java.util.ArrayList;
 import java.util.List;
 
 /**
- * A MicroPropsGenerator which converts a measurement from a simple MeasureUnit
- * to a Mixed MeasureUnit.
+ * A MicroPropsGenerator which converts a measurement from one MeasureUnit to
+ * another. In particular, the output MeasureUnit may be a mixed unit. (The
+ * input unit may not be a mixed unit.)
  */
 public class UnitConversionHandler implements MicroPropsGenerator {
 
@@ -22,22 +22,23 @@ public class UnitConversionHandler implements MicroPropsGenerator {
     private MeasureUnit fOutputUnit;
     private ComplexUnitsConverter fComplexUnitConverter;
 
-    public UnitConversionHandler(MeasureUnit outputUnit, MicroPropsGenerator parent) {
+    /**
+     * Constructor.
+     *
+     * @param inputUnit Specifies the input MeasureUnit. Mixed units are not
+     *     supported as input (because input is just a single decimal quantity).
+     * @param outputUnit Specifies the output MeasureUnit.
+     * @param parent The parent MicroPropsGenerator.
+     */
+    public UnitConversionHandler(MeasureUnit inputUnit,
+                                 MeasureUnit outputUnit,
+                                 MicroPropsGenerator parent) {
         this.fOutputUnit = outputUnit;
         this.fParent = parent;
-
-        List<MeasureUnit> singleUnits = outputUnit.splitToSingleUnits();
-
-        assert outputUnit.getComplexity() == MeasureUnit.Complexity.MIXED;
-        assert singleUnits.size() > 1;
-
+        MeasureUnitImpl inputUnitImpl = MeasureUnitImpl.forIdentifier(inputUnit.getIdentifier());
         MeasureUnitImpl outputUnitImpl = MeasureUnitImpl.forIdentifier(outputUnit.getIdentifier());
-       // TODO(icu-units#97): The input unit should be the largest unit, not the first unit, in the identifier.
-        this.fComplexUnitConverter =
-                new ComplexUnitsConverter(
-                        new MeasureUnitImpl(outputUnitImpl.getSingleUnits().get(0)),
-                        outputUnitImpl,
-                        new UnitsData().getConversionRates());
+        this.fComplexUnitConverter = new ComplexUnitsConverter(inputUnitImpl, outputUnitImpl,
+                                                               new UnitsData().getConversionRates());
     }
 
     /**
@@ -45,16 +46,12 @@ public class UnitConversionHandler implements MicroPropsGenerator {
      */
     @Override
     public MicroProps processQuantity(DecimalQuantity quantity) {
-        /*TODO: Questions : shall we check the parent if it is equals null */
-        MicroProps result = this.fParent == null?
-                this.fParent.processQuantity(quantity):
-                new MicroProps(false);
+        MicroProps result = this.fParent.processQuantity(quantity);
 
         quantity.roundToInfinity(); // Enables toDouble
         List<Measure> measures = this.fComplexUnitConverter.convert(quantity.toBigDecimal());
 
         result.outputUnit = this.fOutputUnit;
-        result.mixedMeasures = new ArrayList<>();
         UsagePrefsHandler.mixedMeasuresToMicros(measures, quantity, result);
 
         return result;
index ee012d297d3b58bfe5fa858e6bfe127854d5ca51..74c13b660bc9d95647c3ae746dfbdcaaef19f544 100644 (file)
@@ -1,6 +1,5 @@
 // Â© 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.IllegalIcuArgumentException;
@@ -47,15 +46,18 @@ public class UsagePrefsHandler implements MicroPropsGenerator {
         return Precision.increment(num.divide(den, MathContext.DECIMAL128));
     }
 
+    /**
+     * Populates micros.mixedMeasures and modifies quantity, based on the values
+     * in measures.
+     */
     protected static void mixedMeasuresToMicros(List<Measure> measures, DecimalQuantity quantity, MicroProps micros) {
+        micros.mixedMeasures = new ArrayList<>();
         if (measures.size() > 1) {
             // For debugging
             assert (micros.outputUnit.getComplexity() == MeasureUnit.Complexity.MIXED);
 
-            // Check that we received measurements with the expected MeasureUnits:
-            List<MeasureUnit> singleUnits = micros.outputUnit.splitToSingleUnits();
-
-            assert measures.size() == singleUnits.size();
+            // Check that we received the expected number of measurements:
+            assert measures.size() == micros.outputUnit.splitToSingleUnits().size();
 
             // Mixed units: except for the last value, we pass all values to the
             // LongNameHandler via micros->mixedMeasures.
@@ -95,7 +97,6 @@ public class UsagePrefsHandler implements MicroPropsGenerator {
 
         final List<Measure> routedMeasures = routed.measures;
         micros.outputUnit = routed.outputUnit.build();
-        micros.mixedMeasures = new ArrayList<>();
 
         UsagePrefsHandler.mixedMeasuresToMicros(routedMeasures, quantity, micros);
 
@@ -103,14 +104,16 @@ public class UsagePrefsHandler implements MicroPropsGenerator {
 
         assert micros.rounder != null;
 
-        // TODO: use the user precision if the user already set precision.
-        if (precisionSkeleton != null && precisionSkeleton.length() > 0) {
-            micros.rounder = parseSkeletonToPrecision(precisionSkeleton);
-        } else {
-            // We use the same rounding mode as COMPACT notation: known to be a
-            // human-friendly rounding mode: integers, but add a decimal digit
-            // as needed to ensure we have at least 2 significant digits.
-            micros.rounder = Precision.integer().withMinDigits(2);
+        if (micros.rounder instanceof Precision.BogusRounder) {
+            Precision.BogusRounder rounder = (Precision.BogusRounder)micros.rounder;
+            if (precisionSkeleton != null && precisionSkeleton.length() > 0) {
+                micros.rounder = rounder.into(parseSkeletonToPrecision(precisionSkeleton));
+            } else {
+                // We use the same rounding mode as COMPACT notation: known to be a
+                // human-friendly rounding mode: integers, but add a decimal digit
+                // as needed to ensure we have at least 2 significant digits.
+                micros.rounder = rounder.into(Precision.integer().withMinDigits(2));
+            }
         }
 
         return micros;
index 1b91cbb0b6bc14579de1e47bb92c030e46ea7249..b639c8f71f0213c74e15a49dd076d2c67f44b6fd 100644 (file)
@@ -210,10 +210,9 @@ class NumberFormatterImpl {
                 || !(isPercent || isPermille)
                 || isCompactNotation
             );
-
-        // TODO(icu-units#95): Add the logic in this file that sets the rounder to bogus/pass-through if isMixedUnit is true.
         boolean isMixedUnit = isCldrUnit && macros.unit.getType() == null &&
-                macros.unit.getComplexity() == MeasureUnit.Complexity.MIXED;
+                              macros.unit.getComplexity() == MeasureUnit.Complexity.MIXED;
+
         PluralRules rules = macros.rules;
 
         // Select the numbering system.
@@ -275,7 +274,9 @@ class NumberFormatterImpl {
             }
             chain = usagePrefsHandler = new UsagePrefsHandler(macros.loc, macros.unit, macros.usage, chain);
         } else if (isMixedUnit) {
-            chain = new UnitConversionHandler(macros.unit, chain);
+            // TODO(icu-units#97): The input unit should be the largest unit, not the first unit, in the identifier.
+            MeasureUnit inputUnit = macros.unit.splitToSingleUnits().get(0);
+            chain = new UnitConversionHandler(inputUnit, macros.unit, chain);
         }
 
         // Multiplier
@@ -290,6 +291,9 @@ class NumberFormatterImpl {
             micros.rounder = Precision.COMPACT_STRATEGY;
         } else if (isCurrency) {
             micros.rounder = Precision.MONETARY_STANDARD;
+        } else if (macros.usage != null) {
+            // Bogus Precision - it will get set in the UsagePrefsHandler instead
+            micros.rounder = Precision.BOGUS_PRECISION;
         } else {
             micros.rounder = Precision.DEFAULT_MAX_FRAC_6;
         }
index fb1072bea24014dc3901c9f041a2e7f0aab1a2dd..6d223bfdb37b963307db798fa9d1663e265d45e6 100644 (file)
@@ -44,8 +44,8 @@ public abstract class NumberFormatterSettings<T extends NumberFormatterSettings<
     static final int KEY_SCALE = 13;
     static final int KEY_THRESHOLD = 14;
     static final int KEY_PER_UNIT = 15;
-    static final int KEY_MAX = 16;
-    static final int KEY_USAGE = 17;
+    static final int KEY_USAGE = 16;
+    static final int KEY_MAX = 17;
 
     private final NumberFormatterSettings<?> parent;
     private final int key;
@@ -134,7 +134,7 @@ public abstract class NumberFormatterSettings<T extends NumberFormatterSettings<
      * <p>
      * The default is to render without units (equivalent to {@link NoUnit#BASE}).
      *
-     * <P>
+     * <p>
      * If the input usage is correctly set the output unit <b>will change</b>
      * according to `usage`, `locale` and `unit` value.
      * </p>
@@ -492,44 +492,57 @@ public abstract class NumberFormatterSettings<T extends NumberFormatterSettings<
         return create(KEY_SCALE, scale);
     }
 
-   /**
+    /**
      * Specifies the usage for which numbers will be formatted ("person-height",
      * "road", "rainfall", etc.)
      *
+     * <p>
      * When a `usage` is specified, the output unit will change depending on the
      * `Locale` and the unit quantity. For example, formatting length
      * measurements specified in meters:
      *
-     * `NumberFormatter.with().usage("person").unit(MeasureUnit.METER).locale(new ULocale("en-US"))`
-     *   * When formatting 0.25, the output will be "10 inches".
-     *   * When formatting 1.50, the output will be "4 feet and 11 inches".
+     * <p>
+     * <pre>
+     * NumberFormatter.with().usage("person").unit(MeasureUnit.METER).locale(new ULocale("en-US"))
+     * </pre>
+     * <ul>
+     *   <li> When formatting 0.25, the output will be "10 inches".
+     *   <li> When formatting 1.50, the output will be "4 feet and 11 inches".
+     * </li>
      *
+     * <p>
      * The input unit specified via unit() determines the type of measurement
      * being formatted (e.g. "length" when the unit is "foot"). The usage
      * requested will be looked for only within this category of measurement
      * units.
      *
+     * <p>
      * The output unit can be found via FormattedNumber.getOutputUnit().
      *
+     * <p>
      * If the usage has multiple parts (e.g. "land-agriculture-grain") and does
      * not match a known usage preference, the last part will be dropped
      * repeatedly until a match is found (e.g. trying "land-agriculture", then
      * "land"). If a match is still not found, usage will fall back to
      * "default".
      *
+     * <p>
      * Setting usage to an empty string clears the usage (disables usage-based
      * localized formatting).
      *
+     * <p>
+     * Setting a usage string but not a correct input unit will result in an
+     * U_ILLEGAL_ARGUMENT_ERROR.
      *
+     * <p>
      * When using usage, specifying rounding or precision is unnecessary.
      * Specifying a precision in some manner will override the default
      * formatting.
      *
-     *
      * @param usage A usage parameter from the units resource.
      * @return The fluent chain
      * @throws IllegalArgumentException in case of Setting a usage string but not a correct input unit.
-     * @draft ICU 67
+     * @draft ICU 68
      * @provisional This API might change or be removed in a future release.
      */
     public T usage(String usage) {
index 02565f040e4ed98d38150ce4d87b60f526fd23d6..07e81c45ec9e87187d2a7a96a7295d04543e3e64 100644 (file)
@@ -1468,10 +1468,13 @@ class NumberSkeletonImpl {
             } else if (macros.unit == MeasureUnit.PERMILLE) {
                 sb.append("permille");
                 return true;
-            } else {
+            } else if (macros.unit.getType() != null) {
                 sb.append("measure-unit/");
                 BlueprintHelpers.generateMeasureUnitOption(macros.unit, sb);
                 return true;
+            } else {
+                // TODO(icu-units#35): add support for not-built-in units.
+                throw new UnsupportedOperationException();
             }
         }
 
index d2dfb6bb15be8448e753c82ec262c625b426f8ad..a984ae06502531fcaac803b8de3704dbe6ace455 100644 (file)
@@ -20,7 +20,6 @@ import com.ibm.icu.util.Currency.CurrencyUsage;
  *
  * @stable ICU 62
  * @see NumberFormatter
- * @internal
  */
 public abstract class Precision {
 
@@ -366,6 +365,13 @@ public abstract class Precision {
     // PACKAGE-PRIVATE APIS //
     //////////////////////////
 
+    /**
+     * @internal
+     * @deprecated ICU internal only.
+     */
+    @Deprecated
+    public static final BogusRounder BOGUS_PRECISION = new BogusRounder();
+
     static final InfiniteRounderImpl NONE = new InfiniteRounderImpl();
 
     static final FractionRounderImpl FIXED_FRAC_0 = new FractionRounderImpl(0, 0);
@@ -545,6 +551,40 @@ public abstract class Precision {
     // INTERNALS //
     ///////////////
 
+    /**
+     * An BogusRounder's MathContext into precision.
+     *
+     * @internal
+     * @deprecated This API is ICU internal only.
+     */
+    @Deprecated
+    public static class BogusRounder extends Precision {
+        @Override
+        public void apply(DecimalQuantity value) {
+            throw new AssertionError("BogusRounder must not be applied");
+        }
+
+        @Override
+        BogusRounder createCopy() {
+            BogusRounder copy = new BogusRounder();
+            copy.mathContext = mathContext;
+            return copy;
+        }
+
+        /**
+         * Copies the BogusRounder's MathContext into precision.
+         *
+         * @internal
+         * @deprecated This API is ICU internal only.
+         */
+        @Deprecated
+        public Precision into(Precision precision) {
+            Precision copy = precision.createCopy();
+            copy.mathContext = mathContext;
+            return copy;
+        }
+    }
+
     static class InfiniteRounderImpl extends Precision {
 
         public InfiniteRounderImpl() {
index b10b4143054912448418996edd79d5e9bc4ed732..3bed7ba80dee6792a5f52c7d2a4b0c9d21d618e1 100644 (file)
@@ -21,6 +21,7 @@ import org.junit.Test;
 import com.ibm.icu.dev.test.TestFmwk;
 import com.ibm.icu.dev.test.format.FormattedValueTest;
 import com.ibm.icu.dev.test.serializable.SerializableTestUtility;
+import com.ibm.icu.impl.IllegalIcuArgumentException;
 import com.ibm.icu.impl.number.Grouper;
 import com.ibm.icu.impl.number.LocalizedNumberFormatterAsFormat;
 import com.ibm.icu.impl.number.MacroProps;
@@ -644,6 +645,17 @@ public class NumberFormatterApiTest extends TestFmwk {
                 1,
                 "kelvin");
 
+        assertFormatSingle(
+                "Person unit not in short form",
+                "measure-unit/duration-year-person unit-width-full-name",
+                "unit/year-person unit-width-full-name",
+                NumberFormatter.with()
+                        .unit(MeasureUnit.YEAR_PERSON)
+                        .unitWidth(UnitWidth.FULL_NAME),
+                ULocale.forLanguageTag("es-MX"),
+                5,
+                "5 a\u00F1os");
+            
         // TODO(icu-units#35): skeleton generation.
         assertFormatSingle(
                 "Mixed unit",
@@ -738,6 +750,17 @@ public class NumberFormatterApiTest extends TestFmwk {
                 "0.008765 m/s",
                 "0 m/s");
 
+        // TODO(icu-units#35): does not normalize as desired: while "unit/*" does
+        // get split into unit/perUnit, ".unit(*)" and "measure-unit/*" don't:
+        assertFormatSingle(
+                "Built-in unit, meter-per-second",
+                "measure-unit/speed-meter-per-second",
+                "~unit/meter-per-second",
+                NumberFormatter.with().unit(MeasureUnit.METER_PER_SECOND),
+                new ULocale("en-GB"),
+                2.4,
+                "2.4 m/s");
+
         assertFormatDescending(
                 "Pounds Per Square Mile Short (secondary unit has per-format)",
                 "measure-unit/mass-pound per-measure-unit/area-square-mile",
@@ -770,16 +793,22 @@ public class NumberFormatterApiTest extends TestFmwk {
                 "0.008765 J/fur",
                 "0 J/fur");
 
-        // TODO(icu-units#35): does not normalize as desired: while "unit/*" does
-        // get split into unit/perUnit, ".unit(*)" and "measure-unit/*" don't:
-        assertFormatSingle(
-                "Built-in unit, meter-per-second",
-                "measure-unit/speed-meter-per-second",
-                "~unit/meter-per-second",
-                NumberFormatter.with().unit(MeasureUnit.METER_PER_SECOND),
-                new ULocale("en-GB"),
-                2.4,
-                "2.4 m/s");
+        // // TODO(ICU-20941): Support constructions such as this one.
+        // assertFormatDescending(
+        //         "Joules Per Furlong Short with unit identifier via API",
+        //         "measure-unit/energy-joule per-measure-unit/length-furlong",
+        //         "unit/joule-per-furlong",
+        //         NumberFormatter.with().unit(MeasureUnit.forIdentifier("joule-per-furlong")),
+        //         ULocale.ENGLISH,
+        //         "87,650 J/fur",
+        //         "8,765 J/fur",
+        //         "876.5 J/fur",
+        //         "87.65 J/fur",
+        //         "8.765 J/fur",
+        //         "0.8765 J/fur",
+        //         "0.08765 J/fur",
+        //         "0.008765 J/fur",
+        //         "0 J/fur");
 
         // TODO(icu-units#59): THIS UNIT TEST DEMONSTRATES UNDESIRABLE BEHAVIOUR!
         // When specifying built-in types, one can give both a unit and a perUnit.
@@ -839,6 +868,13 @@ public class NumberFormatterApiTest extends TestFmwk {
         FormattedNumber formattedNum;
         String uTestCase;
 
+        try {
+            NumberFormatter.with().usage("road").locale(ULocale.ENGLISH).format(1);
+            fail("should give an error, usage() without unit() is invalid");
+        } catch (IllegalIcuArgumentException e) {
+            // Pass
+        }
+
         unloc_formatter = NumberFormatter.with().usage("road").unit(MeasureUnit.METER);
 
         uTestCase = "unitUsage() en-ZA road";
@@ -881,15 +917,10 @@ public class NumberFormatterApiTest extends TestFmwk {
         uTestCase = "unitUsage() en-GB road";
         formatter = unloc_formatter.locale(new ULocale("en-GB"));
         formattedNum = formatter.format(321d);
-
-        // status.errIfFailureAndReset("unitUsage() en-GB road, formatDouble(...)");
-
         assertTrue(
                 uTestCase + ", got outputUnit: \"" + formattedNum.getOutputUnit().getIdentifier() + "\"",
                 MeasureUnit.YARD.equals(formattedNum.getOutputUnit()));
-        // status.errIfFailureAndReset("unitUsage() en-GB road, getOutputUnit(...)");
         assertEquals(uTestCase, "350 yd", formattedNum.toString());
-        //status.errIfFailureAndReset("unitUsage() en-GB road, toString(...)");
         {
             final Object[][] expectedFieldPositions = {
                     {NumberFormat.Field.INTEGER, 0, 3},
@@ -919,15 +950,10 @@ public class NumberFormatterApiTest extends TestFmwk {
         uTestCase = "unitUsage() en-US road";
         formatter = unloc_formatter.locale(new ULocale("en-US"));
         formattedNum = formatter.format(321d);
-        // status.errIfFailureAndReset("unitUsage() en-US road, formatDouble(...)");
-
         assertTrue(
                 uTestCase + ", got outputUnit: \"" + formattedNum.getOutputUnit().getIdentifier() + "\"",
                 MeasureUnit.FOOT == formattedNum.getOutputUnit());
-        // status.errIfFailureAndReset("unitUsage() en-US road, getOutputUnit(...)");
-
         assertEquals(uTestCase, "1,050 ft", formattedNum.toString());
-        // status.errIfFailureAndReset("unitUsage() en-US road, toString(...)");
         {
             final Object[][] expectedFieldPositions = {
                     {NumberFormat.Field.GROUPING_SEPARATOR, 1, 2},
@@ -958,15 +984,10 @@ public class NumberFormatterApiTest extends TestFmwk {
         uTestCase = "unitUsage() en-GB person";
         formatter = unloc_formatter.locale(new ULocale("en-GB"));
         formattedNum = formatter.format(80d);
-        // status.errIfFailureAndReset("unitUsage() en-GB person formatDouble");
-
         assertTrue(
                 uTestCase + ", got outputUnit: \"" + formattedNum.getOutputUnit().getIdentifier() + "\"",
                 MeasureUnit.forIdentifier("stone-and-pound").equals(formattedNum.getOutputUnit()));
-        // status.errIfFailureAndReset("unitUsage() en-GB person - formattedNum.getOutputUnit(status)");
-
         assertEquals(uTestCase, "12 st, 8.4 lb", formattedNum.toString());
-        //status.errIfFailureAndReset("unitUsage() en-GB person, toString(...)");
         {
             final Object[][] expectedFieldPositions = {
                     // // Desired output: TODO(icu-units#67)
@@ -1053,29 +1074,51 @@ public class NumberFormatterApiTest extends TestFmwk {
                 "0 pounds, 0.31 ounces",
                 "0 pounds, 0 ounces");
 
-        // TODO: this is about the user overriding the usage precision.
-        // TODO: should be done!
-//        assertFormatDescendingBig(
-//                "Scientific notation with Usage: possible when using a reasonable Precision",
-//                "scientific @### usage/default measure-unit/area-square-meter unit-width-full-name",
-//                "scientific @### usage/default unit/square-meter unit-width-full-name",
-//                NumberFormatter.with()
-//                        .unit(MeasureUnit.SQUARE_METER)
-//                        .usage("default")
-//                        .notation(Notation.scientific())
-//                        .precision(Precision.minMaxSignificantDigits(1, 4))
-//                        .unitWidth(UnitWidth.FULL_NAME),
-//                new ULocale("en-ZA"),
-//                "8,765E1 square kilometres",
-//                "8,765E0 square kilometres",
-//                "8,765E1 hectares",
-//                "8,765E0 hectares",
-//                "8,765E3 square metres",
-//                "8,765E2 square metres",
-//                "8,765E1 square metres",
-//                "8,765E0 square metres",
-//                "0E0 square centimetres");
-    }
+       assertFormatDescendingBig(
+               "Scientific notation with Usage: possible when using a reasonable Precision",
+               "scientific @### usage/default measure-unit/area-square-meter unit-width-full-name",
+               "scientific @### usage/default unit/square-meter unit-width-full-name",
+               NumberFormatter.with()
+                       .unit(MeasureUnit.SQUARE_METER)
+                       .usage("default")
+                       .notation(Notation.scientific())
+                       .precision(Precision.minMaxSignificantDigits(1, 4))
+                       .unitWidth(UnitWidth.FULL_NAME),
+               new ULocale("en-ZA"),
+               "8,765E1 square kilometres",
+               "8,765E0 square kilometres",
+               "8,765E1 hectares",
+               "8,765E0 hectares",
+               "8,765E3 square metres",
+               "8,765E2 square metres",
+               "8,765E1 square metres",
+               "8,765E0 square metres",
+               "0E0 square centimetres");
+
+        assertFormatSingle(
+                "Rounding Mode propagates: rounding down",
+                "usage/road measure-unit/length-centimeter rounding-mode-floor",
+                "usage/road unit/centimeter rounding-mode-floor",
+                NumberFormatter.with()
+                        .unit(MeasureUnit.forIdentifier("centimeter"))
+                        .usage("road")
+                        .roundingMode(RoundingMode.FLOOR),
+                new ULocale("en-ZA"),
+                34500,
+                "300 m");
+
+        assertFormatSingle(
+                "Rounding Mode propagates: rounding up",
+                "usage/road measure-unit/length-centimeter rounding-mode-ceiling",
+                "usage/road unit/centimeter rounding-mode-ceiling",
+                NumberFormatter.with()
+                        .unit(MeasureUnit.forIdentifier("centimeter"))
+                        .usage("road")
+                        .roundingMode(RoundingMode.CEILING),
+                new ULocale("en-ZA"),
+                30500,
+                "350 m");
+}
 
 
     @Test
@@ -1122,18 +1165,17 @@ public class NumberFormatterApiTest extends TestFmwk {
                 321,
                 "300 m");
 
-        // TODO(younies): enable this test case
-//        assertFormatSingle(
-//                "Precision can be overridden: override takes precedence",
-//                "usage/road measure-unit/length-meter @#",
-//                "usage/road unit/meter @#",
-//                NumberFormatter.with()
-//                .unit(MeasureUnit.METER)
-//                .usage("road")
-//                .precision(Precision.maxSignificantDigits(2)),
-//       new ULocale("en-ZA"),
-//                321,
-//                "320 m");
+        assertFormatSingle(
+                "Precision can be overridden: override takes precedence",
+                "usage/road measure-unit/length-meter @#",
+                "usage/road unit/meter @#",
+                NumberFormatter.with()
+                        .unit(MeasureUnit.METER)
+                        .usage("road")
+                        .precision(Precision.maxSignificantDigits(2)),
+                new ULocale("en-ZA"),
+                321,
+                "320 m");
 
         assertFormatSingle(
                 "Compact notation with Usage: bizarre, but possible (short)",
@@ -1147,74 +1189,70 @@ public class NumberFormatterApiTest extends TestFmwk {
                 987654321L,
                 "988K km");
 
+        assertFormatSingle(
+                "Compact notation with Usage: bizarre, but possible (short, precision override)",
+                "compact-short usage/road measure-unit/length-meter @#",
+                "compact-short usage/road unit/meter @#",
+                NumberFormatter.with()
+                        .unit(MeasureUnit.METER)
+                        .usage("road")
+                        .notation(Notation.compactShort())
+                        .precision(Precision.maxSignificantDigits(2)),
+                new ULocale("en-ZA"),
+                987654321L,
+                "990K km");
+
+        assertFormatSingle(
+                "Compact notation with Usage: unusual but possible (long)",
+                "compact-long usage/road measure-unit/length-meter @#",
+                "compact-long usage/road unit/meter @#",
+                NumberFormatter.with()
+                        .unit(MeasureUnit.METER)
+                        .usage("road")
+                        .notation(Notation.compactLong())
+                        .precision(Precision.maxSignificantDigits(2)),
+                new ULocale("en-ZA"),
+                987654321,
+                "990 thousand km");
+
+        assertFormatSingle(
+                "Compact notation with Usage: unusual but possible (long, precision override)",
+                "compact-long usage/road measure-unit/length-meter @#",
+                "compact-long usage/road unit/meter @#",
+                NumberFormatter.with()
+                        .unit(MeasureUnit.METER)
+                        .usage("road")
+                        .notation(Notation.compactLong())
+                        .precision(Precision.maxSignificantDigits(2)),
+                new ULocale("en-ZA"),
+                987654321,
+                "990 thousand km");
 
+        assertFormatSingle(
+                "Scientific notation, not recommended, requires precision override for road",
+                "scientific usage/road measure-unit/length-meter",
+                "scientific usage/road unit/meter",
+                NumberFormatter.with()
+                        .unit(MeasureUnit.METER)
+                        .usage("road")
+                        .notation(Notation.scientific()),
+                new ULocale("en-ZA"),
+                321.45,
+                // Rounding to the nearest "50" is not exponent-adjusted in scientific notation:
+                "0E2 m");
 
-        // TODO(younies): enable override precision test cases.
-//        assertFormatSingle(
-//                "Compact notation with Usage: bizarre, but possible (short, precision override)",
-//                "compact-short usage/road measure-unit/length-meter @#",
-//                "compact-short usage/road unit/meter @#",
-//                NumberFormatter.with()
-//                .unit(MeasureUnit.METER)
-//                .usage("road")
-//                .notation(Notation.compactShort())
-//                .precision(Precision.maxSignificantDigits(2)),
-//        new ULocale("en-ZA"),
-//                987654321L,
-//                "990K km");
-
-                // TODO(younies): enable override precision test cases.
-//        assertFormatSingle(
-//                "Compact notation with Usage: unusual but possible (long)",
-//                "compact-long usage/road measure-unit/length-meter @#",
-//                "compact-long usage/road unit/meter @#",
-//                NumberFormatter.with()
-//                .unit(MeasureUnit.METER)
-//                .usage("road")
-//                .notation(Notation.compactLong())
-//                .precision(Precision.maxSignificantDigits(2)),
-//        new ULocale("en-ZA"),
-//                987654321,
-//                "990 thousand km");
-
-        // TODO(younies): enable override precision test cases.
-//        assertFormatSingle(
-//                "Compact notation with Usage: unusual but possible (long, precision override)",
-//                "compact-long usage/road measure-unit/length-meter @#",
-//                "compact-long usage/road unit/meter @#",
-//                NumberFormatter.with()
-//                .unit(MeasureUnit.METER)
-//                .usage("road")
-//                .notation(Notation.compactLong())
-//                .precision(Precision.maxSignificantDigits(2)),
-//        new ULocale("en-ZA"),
-//                987654321,
-//                "990 thousand km");
-
-        // TODO(younies): enable override precision test cases.
-//        assertFormatSingle(
-//                "Scientific notation, not recommended, requires precision override for road",
-//                "scientific usage/road measure-unit/length-meter",
-//                "scientific usage/road unit/meter",
-//                NumberFormatter.with().unit(MeasureUnit.METER).usage("road").notation(Notation.scientific()),
-//        new ULocale("en-ZA"),
-//                321.45,
-//                // Rounding to the nearest "50" is not exponent-adjusted in scientific notation:
-//                "0E2 m");
-
-        // TODO(younies): enable override precision test cases.
-//        assertFormatSingle(
-//                "Scientific notation with Usage: possible when using a reasonable Precision",
-//                "scientific usage/road measure-unit/length-meter @###",
-//                "scientific usage/road unit/meter @###",
-//                NumberFormatter.with()
-//                .unit(MeasureUnit.METER)
-//                .usage("road")
-//                .notation(Notation.scientific())
-//                .precision(Precision.maxSignificantDigits(4)),
-//        new ULocale("en-ZA"),
-//                321.45, // 0.45 rounds down, 0.55 rounds up.
-//                "3,214E2 m");
+        assertFormatSingle(
+                "Scientific notation with Usage: possible when using a reasonable Precision",
+                "scientific usage/road measure-unit/length-meter @###",
+                "scientific usage/road unit/meter @###",
+                NumberFormatter.with()
+                        .unit(MeasureUnit.METER)
+                        .usage("road")
+                        .notation(Notation.scientific())
+                        .precision(Precision.maxSignificantDigits(4)),
+                new ULocale("en-ZA"),
+                321.45, // 0.45 rounds down, 0.55 rounds up.
+                "3,214E2 m");
 
         assertFormatSingle(
                 "Scientific notation with Usage: possible when using a reasonable Precision",