ICU-20568 Add .unit().usage() support to ICU4J NumberFormatter (1/2)
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/LongNameHandler.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/LongNameHandler.java
index 7068e0b..695fc6a 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/LongNameHandler.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/LongNameHandler.java
@@ -26,7 +26,7 @@
 
     private static final int DNAM_INDEX = StandardPlural.COUNT;
     private static final int PER_INDEX = StandardPlural.COUNT + 1;
-    private static final int ARRAY_LENGTH = StandardPlural.COUNT + 2;
+    protected static final int ARRAY_LENGTH = StandardPlural.COUNT + 2;
 
     private static int getIndex(String pluralKeyword) {
         // pluralKeyword can also be "dnam" or "per"
@@ -39,7 +39,7 @@
         }
     }
 
-    private static String getWithPlural(String[] strings, StandardPlural plural) {
+    protected static String getWithPlural(String[] strings, StandardPlural plural) {
         String result = strings[plural.ordinal()];
         if (result == null) {
             result = strings[StandardPlural.OTHER.ordinal()];
@@ -79,7 +79,7 @@
 
     // NOTE: outArray MUST have at least ARRAY_LENGTH entries. No bounds checking is performed.
 
-    private static void getMeasureData(
+    protected static void getMeasureData(
             ULocale locale,
             MeasureUnit unit,
             UnitWidth width,
@@ -101,7 +101,7 @@
 
         // Map duration-year-person, duration-week-person, etc. to duration-year, duration-week, ...
         // TODO(ICU-20400): Get duration-*-person data properly with aliases.
-        if (unit.getSubtype().endsWith("-person")) {
+        if (unit.getSubtype() != null && unit.getSubtype().endsWith("-person")) {
             key.append(unit.getSubtype(), 0, unit.getSubtype().length() - 7);
         } else {
             key.append(unit.getSubtype());
@@ -191,6 +191,22 @@
         return result;
     }
 
+    /**
+     * Construct a localized LongNameHandler for the specified MeasureUnit.
+     * <p>
+     * Compound units can be constructed via `unit` and `perUnit`. Both of these
+     * must then be built-in units.
+     * <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.
+     */
     public static LongNameHandler forMeasureUnit(
             ULocale locale,
             MeasureUnit unit,
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/LongNameMultiplexer.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/LongNameMultiplexer.java
new file mode 100644
index 0000000..9f21d00
--- /dev/null
+++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/LongNameMultiplexer.java
@@ -0,0 +1,82 @@
+// © 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;
+import com.ibm.icu.text.PluralRules;
+import com.ibm.icu.util.MeasureUnit;
+import com.ibm.icu.util.NoUnit;
+import com.ibm.icu.util.ULocale;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class LongNameMultiplexer implements MicroPropsGenerator {
+    private final MicroPropsGenerator fParent;
+
+    private List<MicroPropsGenerator> fHandlers;
+
+    // Each MeasureUnit corresponds to the same-index MicroPropsGenerator
+    // pointed to in fHandlers.
+    private List<MeasureUnit> fMeasureUnits;
+
+    public LongNameMultiplexer(MicroPropsGenerator fParent) {
+        this.fParent = fParent;
+    }
+
+    // Produces a multiplexer for LongNameHandlers, one for each unit in
+    // `units`. An individual unit might be a mixed unit.
+    public static LongNameMultiplexer forMeasureUnits(ULocale locale,
+                                                      List<MeasureUnit> units,
+                                                      NumberFormatter.UnitWidth width,
+                                                      PluralRules rules,
+                                                      MicroPropsGenerator parent) {
+        LongNameMultiplexer result = new LongNameMultiplexer(parent);
+
+        assert (units.size() > 0);
+
+        result.fMeasureUnits = new ArrayList<>();
+        result.fHandlers = new ArrayList<>();
+
+
+        for (int i = 0; i < units.size(); i++) {
+            MeasureUnit unit = units.get(i);
+            result.fMeasureUnits.add(unit);
+            if (unit.getComplexity() == MeasureUnit.Complexity.MIXED) {
+                MixedUnitLongNameHandler mlnh = MixedUnitLongNameHandler
+                        .forMeasureUnit(locale, unit, width, rules, null);
+                result.fHandlers.add(mlnh);
+            } else {
+                LongNameHandler lnh = LongNameHandler
+                        .forMeasureUnit(locale, unit, NoUnit.BASE, width, rules, null );
+                result.fHandlers.add(lnh);
+            }
+        }
+
+        return result;
+    }
+
+    // The output unit must be provided via `micros.outputUnit`, it must match
+    // 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!
+        MicroProps micros = this.fParent.processQuantity(quantity);
+
+        // 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);
+            }
+
+        }
+
+        throw new AssertionError
+                (" We shouldn't receive any outputUnit for which we haven't already got a LongNameHandler");
+    }
+}
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MacroProps.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MacroProps.java
index aa8fa69..5f373b0 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MacroProps.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MacroProps.java
@@ -30,6 +30,7 @@
     public SignDisplay sign;
     public DecimalSeparatorDisplay decimal;
     public Scale scale;
+    public String usage;
     public AffixPatternProvider affixProvider; // not in API; for JDK compatibility mode only
     public PluralRules rules; // not in API; could be made public in the future
     public Long threshold; // not in API; controls internal self-regulation threshold
@@ -70,6 +71,8 @@
             affixProvider = fallback.affixProvider;
         if (scale == null)
             scale = fallback.scale;
+        if (usage == null)
+            usage = fallback.usage;
         if (rules == null)
             rules = fallback.rules;
         if (loc == null)
@@ -92,6 +95,7 @@
                 decimal,
                 affixProvider,
                 scale,
+                usage,
                 rules,
                 loc);
     }
@@ -119,6 +123,7 @@
                 && Objects.equals(decimal, other.decimal)
                 && Objects.equals(affixProvider, other.affixProvider)
                 && Objects.equals(scale, other.scale)
+                && Objects.equals(usage, other.usage)
                 && Objects.equals(rules, other.rules)
                 && Objects.equals(loc, other.loc);
     }
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MicroProps.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MicroProps.java
index 81a1675..3179e6c 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MicroProps.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MicroProps.java
@@ -7,7 +7,16 @@
 import com.ibm.icu.number.NumberFormatter.SignDisplay;
 import com.ibm.icu.number.Precision;
 import com.ibm.icu.text.DecimalFormatSymbols;
+import com.ibm.icu.util.Measure;
+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.
 public class MicroProps implements Cloneable, MicroPropsGenerator {
     // Populated globally:
     public SignDisplay sign;
@@ -17,16 +26,37 @@
     public DecimalSeparatorDisplay decimal;
     public IntegerWidth integerWidth;
 
-    // Populated by notation/unit:
+    // Modifiers provided by the number formatting pipeline (when the value is known):
+
+    // A Modifier provided by LongNameHandler, used for currency long names and
+    // units. If there is no LongNameHandler needed, this should be an
+    // null. (This is typically the third modifier applied.)
     public Modifier modOuter;
+
+    // A Modifier for short currencies and compact notation. (This is typically
+    // the second modifier applied.)
     public Modifier modMiddle;
+
+    // A Modifier provided by ScientificHandler, used for scientific notation.
+    // This is typically the first modifier applied.
     public Modifier modInner;
+
     public Precision rounder;
     public Grouper grouping;
     public boolean useCurrency;
 
     // Internal fields:
     private final boolean immutable;
+
+    // The MeasureUnit with which the output is represented. May also have
+    // MeasureUnit.Complexity.MIXED complexity, in which case mixedMeasures comes into
+    // play.
+    public MeasureUnit outputUnit;
+
+    // In the case of mixed units, this is the set of integer-only units
+    // *preceding* the final unit.
+    public List<Measure> mixedMeasures ;
+
     private volatile boolean exhausted;
 
     /**
@@ -38,17 +68,28 @@
         this.immutable = immutable;
     }
 
+    /**
+     * As MicroProps is the "base instance", this implementation of
+     * MircoPropsGenerator.processQuantity() just ensures that the output
+     * `micros` is correctly initialized.
+     * <p>
+     * For the "safe" invocation of this function, micros must not be *this,
+     * such that a copy of the base instance is made. For the "unsafe" path,
+     * this function can be used only once, because the base MicroProps instance
+     * 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.
+     */
     @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
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MicroPropsGenerator.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MicroPropsGenerator.java
index c975047..1ed6119 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MicroPropsGenerator.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MicroPropsGenerator.java
@@ -19,6 +19,9 @@
  * {@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.
  *
+ * This chain of MicroPropsGenerators is typically constructed by NumberFormatterImpl::macrosToMicroGenerator() when
+ * constructing a NumberFormatter.
+ *
  * <p>
  * A class implementing MicroPropsGenerator looks something like this:
  *
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MixedUnitLongNameHandler.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MixedUnitLongNameHandler.java
new file mode 100644
index 0000000..7ad64bb
--- /dev/null
+++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MixedUnitLongNameHandler.java
@@ -0,0 +1,182 @@
+// © 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.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;
+
+public class MixedUnitLongNameHandler implements MicroPropsGenerator, ModifierStore {
+    // Not owned
+    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.
+    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     Does not take ownership.
+     * @param parent    Does not take ownership.
+     */
+    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 via `micros.mixedMeasures`.
+     */
+    @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.
+    @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:
+
+        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) {
+        // 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) {
+            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: fix this issue https://github.com/icu-units/icu/issues/67
+        }
+
+        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));
+
+        // Combine list into a "premixed" pattern
+        String premixedFormatPattern = this.fListFormatter.format(outputMeasuresList);
+        SimpleFormatter premixedCompiled = SimpleFormatter.compileMinMaxArguments(premixedFormatPattern, 0, 1);
+
+        // Return a SimpleModifier for the "premixed" pattern
+        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});*/
+    }
+}
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/UnitConversionHandler.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/UnitConversionHandler.java
new file mode 100644
index 0000000..cf3fd23
--- /dev/null
+++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/UnitConversionHandler.java
@@ -0,0 +1,62 @@
+// © 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;
+import com.ibm.icu.impl.units.MeasureUnitImpl;
+import com.ibm.icu.impl.units.UnitsData;
+import com.ibm.icu.util.Measure;
+import com.ibm.icu.util.MeasureUnit;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A MicroPropsGenerator which converts a measurement from a simple MeasureUnit
+ * to a Mixed MeasureUnit.
+ */
+public class UnitConversionHandler implements MicroPropsGenerator {
+
+    private final MicroPropsGenerator fParent;
+    private MeasureUnit fOutputUnit;
+    private ComplexUnitsConverter fComplexUnitConverter;
+
+    public UnitConversionHandler(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 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());
+    }
+
+    /**
+     * Obtains the appropriate output values from the Unit Converter.
+     */
+    @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);
+
+        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;
+    }
+}
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/UsagePrefsHandler.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/UsagePrefsHandler.java
new file mode 100644
index 0000000..ee012d2
--- /dev/null
+++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/UsagePrefsHandler.java
@@ -0,0 +1,118 @@
+// © 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;
+import com.ibm.icu.impl.units.MeasureUnitImpl;
+import com.ibm.icu.impl.units.UnitsRouter;
+import com.ibm.icu.number.Precision;
+import com.ibm.icu.util.Measure;
+import com.ibm.icu.util.MeasureUnit;
+import com.ibm.icu.util.ULocale;
+
+import java.math.BigDecimal;
+import java.math.MathContext;
+import java.util.ArrayList;
+import java.util.List;
+
+public class UsagePrefsHandler implements MicroPropsGenerator {
+
+    private final MicroPropsGenerator fParent;
+    private UnitsRouter fUnitsRouter;
+
+    public UsagePrefsHandler(ULocale locale, MeasureUnit inputUnit, String usage, MicroPropsGenerator parent) {
+        assert parent != null;
+
+        this.fParent = parent;
+        this.fUnitsRouter =
+                new UnitsRouter(MeasureUnitImpl.forIdentifier(inputUnit.getIdentifier()), locale.getCountry(), usage);
+    }
+
+    private static Precision parseSkeletonToPrecision(String precisionSkeleton) {
+        final String kSuffixPrefix = "precision-increment/";
+        if (!precisionSkeleton.startsWith(kSuffixPrefix)) {
+            throw new IllegalIcuArgumentException("precisionSkeleton is only precision-increment");
+        }
+
+        String skeleton = precisionSkeleton.substring(kSuffixPrefix.length());
+        String skeletons[] = skeleton.split("/");
+        BigDecimal num = new BigDecimal(skeletons[0]);
+        BigDecimal den =
+                skeletons.length == 2 ?
+                        new BigDecimal(skeletons[1]) :
+                        new BigDecimal("1");
+
+
+        return Precision.increment(num.divide(den, MathContext.DECIMAL128));
+    }
+
+    protected static void mixedMeasuresToMicros(List<Measure> measures, DecimalQuantity quantity, MicroProps micros) {
+        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();
+
+            // Mixed units: except for the last value, we pass all values to the
+            // LongNameHandler via micros->mixedMeasures.
+            for (int i = 0, n = measures.size() - 1; i < n; i++) {
+                micros.mixedMeasures.add(measures.get(i));
+            }
+        }
+
+        // The last value (potentially the only value) gets passed on via quantity.
+        quantity.setToBigDecimal((BigDecimal) measures.get(measures.size()- 1).getNumber());
+    }
+
+    /**
+     * Returns the list of possible output units, i.e. the full set of
+     * preferences, for the localized, usage-specific unit preferences.
+     * <p>
+     * The returned pointer should be valid for the lifetime of the
+     * UsagePrefsHandler instance.
+     */
+    public List<MeasureUnit> getOutputUnits() {
+        return fUnitsRouter.getOutputUnits();
+    }
+
+    /**
+     * Obtains the appropriate output value, MeasureUnit and
+     * rounding/precision behaviour from the UnitsRouter.
+     * <p>
+     * The output unit is passed on to the LongNameHandler via
+     * micros.outputUnit.
+     */
+    @Override
+    public MicroProps processQuantity(DecimalQuantity quantity) {
+        MicroProps micros = this.fParent.processQuantity(quantity);
+
+        quantity.roundToInfinity(); // Enables toDouble
+        final UnitsRouter.RouteResult routed = fUnitsRouter.route(quantity.toBigDecimal());
+
+        final List<Measure> routedMeasures = routed.measures;
+        micros.outputUnit = routed.outputUnit.build();
+        micros.mixedMeasures = new ArrayList<>();
+
+        UsagePrefsHandler.mixedMeasuresToMicros(routedMeasures, quantity, micros);
+
+        String precisionSkeleton = routed.precision;
+
+        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);
+        }
+
+        return micros;
+    }
+}
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/units/UnitsRouter.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/units/UnitsRouter.java
index 19acafc..4f8fbb6 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/impl/units/UnitsRouter.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/units/UnitsRouter.java
@@ -83,13 +83,21 @@
         for (ConverterPreference converterPreference :
                 converterPreferences_) {
             if (converterPreference.converter.greaterThanOrEqual(quantity, converterPreference.limit)) {
-                return new RouteResult(converterPreference.converter.convert(quantity), converterPreference.precision);
+                return new RouteResult(
+                        converterPreference.converter.convert(quantity),
+                        converterPreference.precision,
+                        converterPreference.targetUnit
+                );
             }
         }
 
         // In case of the `quantity` does not fit in any converter limit, use the last converter.
         ConverterPreference lastConverterPreference = converterPreferences_.get(converterPreferences_.size() - 1);
-        return new RouteResult(lastConverterPreference.converter.convert(quantity), lastConverterPreference.precision);
+        return new RouteResult(
+                lastConverterPreference.converter.convert(quantity),
+                lastConverterPreference.precision,
+                lastConverterPreference.targetUnit
+        );
     }
 
     /**
@@ -99,7 +107,7 @@
      * The returned pointer should be valid for the lifetime of the
      * UnitsRouter instance.
      */
-    public ArrayList<MeasureUnit> getOutputUnits() {
+    public List<MeasureUnit> getOutputUnits() {
         return this.outputUnits_;
     }
 
@@ -113,32 +121,52 @@
      * is no limit for the converter.
      */
     public static class ConverterPreference {
-        ComplexUnitsConverter converter;
-        BigDecimal limit;
-        String precision;
+        // The output unit for this ConverterPreference. This may be a MIXED unit -
+        // for example: "yard-and-foot-and-inch".
+        final MeasureUnitImpl targetUnit;
+        final ComplexUnitsConverter converter;
+        final BigDecimal limit;
+        final String precision;
 
         // In case there is no limit, the limit will be -inf.
-        public ConverterPreference(MeasureUnitImpl source, MeasureUnitImpl outputUnits,
+        public ConverterPreference(MeasureUnitImpl source, MeasureUnitImpl targetUnit,
                                    String precision, ConversionRates conversionRates) {
-            this(source, outputUnits, BigDecimal.valueOf(Double.MIN_VALUE), precision,
+            this(source, targetUnit, BigDecimal.valueOf(Double.MIN_VALUE), precision,
                     conversionRates);
         }
 
-        public ConverterPreference(MeasureUnitImpl source, MeasureUnitImpl outputUnits,
+        public ConverterPreference(MeasureUnitImpl source, MeasureUnitImpl targetUnit,
                                    BigDecimal limit, String precision, ConversionRates conversionRates) {
-            this.converter = new ComplexUnitsConverter(source, outputUnits, conversionRates);
+            this.converter = new ComplexUnitsConverter(source, targetUnit, conversionRates);
             this.limit = limit;
             this.precision = precision;
+            this.targetUnit = targetUnit;
+
         }
     }
 
     public class RouteResult {
-        public List<Measure> measures;
-        public String precision;
+        // A list of measures: a single measure for single units, multiple measures
+        // for mixed units.
+        //
+        // TODO(icu-units/icu#21): figure out the right mixed unit API.
+        public final List<Measure> measures;
 
-        RouteResult(List<Measure> measures, String precision) {
+        // A skeleton string starting with a precision-increment.
+        //
+        // TODO(hugovdm): generalise? or narrow down to only a precision-increment?
+        // or document that other skeleton elements are ignored?
+        public final String precision;
+
+        // The output unit for this RouteResult. This may be a MIXED unit - for
+        // example: "yard-and-foot-and-inch", for which `measures` will have three
+        // elements.
+        public final MeasureUnitImpl outputUnit;
+
+        RouteResult(List<Measure> measures, String precision, MeasureUnitImpl outputUnit) {
             this.measures = measures;
             this.precision = precision;
+            this.outputUnit = outputUnit;
         }
     }
 }
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/FormattedNumber.java b/icu4j/main/classes/core/src/com/ibm/icu/number/FormattedNumber.java
index e4fe056..4eb26ec 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/number/FormattedNumber.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/number/FormattedNumber.java
@@ -12,6 +12,7 @@
 import com.ibm.icu.text.ConstrainedFieldPosition;
 import com.ibm.icu.text.FormattedValue;
 import com.ibm.icu.text.PluralRules.IFixedDecimal;
+import com.ibm.icu.util.MeasureUnit;
 
 /**
  * The result of a number formatting operation. This class allows the result to be exported in several
@@ -25,10 +26,12 @@
 public class FormattedNumber implements FormattedValue {
     final FormattedStringBuilder string;
     final DecimalQuantity fq;
+    final MeasureUnit outputUnit;
 
-    FormattedNumber(FormattedStringBuilder nsb, DecimalQuantity fq) {
+    FormattedNumber(FormattedStringBuilder nsb, DecimalQuantity fq, MeasureUnit outputUnit) {
         this.string = nsb;
         this.fq = fq;
+        this.outputUnit = outputUnit;
     }
 
     /**
@@ -115,6 +118,21 @@
     }
 
     /**
+     * Gets the resolved output unit.
+     * <p>
+     * The output unit is dependent upon the localized preferences for the usage
+     * specified via NumberFormatterSettings.usage(), and may be a unit with
+     * MeasureUnit.Complexity.MIXED unit complexity (MeasureUnit.getComplexity()), such
+     * as "foot-and-inch" or "hour-and-minute-and-second".
+     *
+     * @return `MeasureUnit`.
+     * @draft ICU 68
+     */
+    public MeasureUnit getOutputUnit() {
+        return this.outputUnit;
+    }
+
+    /**
      * @internal
      * @deprecated This API is ICU internal only.
      */
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/LocalizedNumberFormatter.java b/icu4j/main/classes/core/src/com/ibm/icu/number/LocalizedNumberFormatter.java
index de0d35a..e6f714d 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/number/LocalizedNumberFormatter.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/number/LocalizedNumberFormatter.java
@@ -13,6 +13,7 @@
 import com.ibm.icu.impl.number.DecimalQuantity_DualStorageBCD;
 import com.ibm.icu.impl.number.LocalizedNumberFormatterAsFormat;
 import com.ibm.icu.impl.number.MacroProps;
+import com.ibm.icu.impl.number.MicroProps;
 import com.ibm.icu.math.BigDecimal;
 import com.ibm.icu.util.CurrencyAmount;
 import com.ibm.icu.util.Measure;
@@ -100,8 +101,8 @@
         DecimalQuantity fq = new DecimalQuantity_DualStorageBCD(input.getNumber());
         MeasureUnit unit = input.getUnit();
         FormattedStringBuilder string = new FormattedStringBuilder();
-        formatImpl(fq, unit, string);
-        return new FormattedNumber(string, fq);
+        MicroProps micros = formatImpl(fq, unit, string);
+        return new FormattedNumber(string, fq, micros.outputUnit);
     }
 
     /**
@@ -120,11 +121,13 @@
         return new LocalizedNumberFormatterAsFormat(this, resolve().loc);
     }
 
-    /** Helper method that creates a FormattedStringBuilder and formats. */
+    /**
+     *  Helper method that creates a FormattedStringBuilder and formats.
+     */
     private FormattedNumber format(DecimalQuantity fq) {
         FormattedStringBuilder string = new FormattedStringBuilder();
-        formatImpl(fq, string);
-        return new FormattedNumber(string, fq);
+        MicroProps micros = formatImpl(fq, string);
+        return new FormattedNumber(string, fq, micros.outputUnit);
     }
 
     /**
@@ -144,12 +147,11 @@
      * @deprecated ICU 60 This API is ICU internal only.
      */
     @Deprecated
-    public void formatImpl(DecimalQuantity fq, FormattedStringBuilder string) {
+    public MicroProps formatImpl(DecimalQuantity fq, FormattedStringBuilder string) {
         if (computeCompiled()) {
-            compiled.format(fq, string);
-        } else {
-            NumberFormatterImpl.formatStatic(resolve(), fq, string);
+            return compiled.format(fq, string);
         }
+        return NumberFormatterImpl.formatStatic(resolve(), fq, string);
     }
 
     /**
@@ -159,11 +161,11 @@
      * @deprecated ICU 67 This API is ICU internal only.
      */
     @Deprecated
-    public void formatImpl(DecimalQuantity fq, MeasureUnit unit, FormattedStringBuilder string) {
+    public MicroProps formatImpl(DecimalQuantity fq, MeasureUnit unit, FormattedStringBuilder string) {
         // Use this formatter if possible
         if (Objects.equals(resolve().unit, unit)) {
-            formatImpl(fq, string);
-            return;
+            return formatImpl(fq, string);
+
         }
         // This mechanism saves the previously used unit, so if the user calls this method with the
         // same unit multiple times in a row, they get a more efficient code path.
@@ -172,7 +174,7 @@
             withUnit = new LocalizedNumberFormatter(this, KEY_UNIT, unit);
             savedWithUnit = withUnit;
         }
-        withUnit.formatImpl(fq, string);
+        return withUnit.formatImpl(fq, string);
     }
 
     /**
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterImpl.java b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterImpl.java
index abb93bb..1b91cbb 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterImpl.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterImpl.java
@@ -3,6 +3,7 @@
 package com.ibm.icu.number;
 
 import com.ibm.icu.impl.FormattedStringBuilder;
+import com.ibm.icu.impl.IllegalIcuArgumentException;
 import com.ibm.icu.impl.StandardPlural;
 import com.ibm.icu.impl.number.CompactData.CompactType;
 import com.ibm.icu.impl.number.ConstantAffixModifier;
@@ -10,9 +11,11 @@
 import com.ibm.icu.impl.number.DecimalQuantity_DualStorageBCD;
 import com.ibm.icu.impl.number.Grouper;
 import com.ibm.icu.impl.number.LongNameHandler;
+import com.ibm.icu.impl.number.LongNameMultiplexer;
 import com.ibm.icu.impl.number.MacroProps;
 import com.ibm.icu.impl.number.MicroProps;
 import com.ibm.icu.impl.number.MicroPropsGenerator;
+import com.ibm.icu.impl.number.MixedUnitLongNameHandler;
 import com.ibm.icu.impl.number.MultiplierFormatHandler;
 import com.ibm.icu.impl.number.MutablePatternModifier;
 import com.ibm.icu.impl.number.MutablePatternModifier.ImmutablePatternModifier;
@@ -20,6 +23,8 @@
 import com.ibm.icu.impl.number.PatternStringParser;
 import com.ibm.icu.impl.number.PatternStringParser.ParsedPatternInfo;
 import com.ibm.icu.impl.number.RoundingUtils;
+import com.ibm.icu.impl.number.UnitConversionHandler;
+import com.ibm.icu.impl.number.UsagePrefsHandler;
 import com.ibm.icu.number.NumberFormatter.DecimalSeparatorDisplay;
 import com.ibm.icu.number.NumberFormatter.GroupingStrategy;
 import com.ibm.icu.number.NumberFormatter.SignDisplay;
@@ -41,7 +46,9 @@
  */
 class NumberFormatterImpl {
 
-    /** Builds a "safe" MicroPropsGenerator, which is thread-safe and can be used repeatedly. */
+    /**
+     * Builds a "safe" MicroPropsGenerator, which is thread-safe and can be used repeatedly.
+     */
     public NumberFormatterImpl(MacroProps macros) {
         micros = new MicroProps(true);
         microPropsGenerator = macrosToMicroGenerator(macros, micros, true);
@@ -50,14 +57,14 @@
     /**
      * Builds and evaluates an "unsafe" MicroPropsGenerator, which is cheaper but can be used only once.
      */
-    public static int formatStatic(
+    public static MicroProps formatStatic(
             MacroProps macros,
             DecimalQuantity inValue,
             FormattedStringBuilder outString) {
         MicroProps micros = preProcessUnsafe(macros, inValue);
         int length = writeNumber(micros, inValue, outString, 0);
-        length += writeAffixes(micros, outString, 0, length);
-        return length;
+        writeAffixes(micros, outString, 0, length);
+        return micros;
     }
 
     /**
@@ -84,11 +91,11 @@
     /**
      * Evaluates the "safe" MicroPropsGenerator created by "fromMacros".
      */
-    public int format(DecimalQuantity inValue, FormattedStringBuilder outString) {
+    public MicroProps format(DecimalQuantity inValue, FormattedStringBuilder outString) {
         MicroProps micros = preProcess(inValue);
         int length = writeNumber(micros, inValue, outString, 0);
-        length += writeAffixes(micros, outString, 0, length);
-        return length;
+        writeAffixes(micros, outString, 0, length);
+        return micros;
     }
 
     /**
@@ -203,6 +210,10 @@
                 || !(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;
         PluralRules rules = macros.rules;
 
         // Select the numbering system.
@@ -255,6 +266,18 @@
         /// START POPULATING THE DEFAULT MICROPROPS AND BUILDING THE MICROPROPS GENERATOR ///
         /////////////////////////////////////////////////////////////////////////////////////
 
+        // Unit Preferences and Conversions as our first step
+        UsagePrefsHandler usagePrefsHandler = null;
+        if (macros.usage != null) {
+            if (!isCldrUnit) {
+                throw new IllegalIcuArgumentException(
+                        "We only support \"usage\" when the input unit is specified, and is a CLDR Unit.");
+            }
+            chain = usagePrefsHandler = new UsagePrefsHandler(macros.loc, macros.unit, macros.usage, chain);
+        } else if (isMixedUnit) {
+            chain = new UnitConversionHandler(macros.unit, chain);
+        }
+
         // Multiplier
         if (macros.scale != null) {
             chain = new MultiplierFormatHandler(macros.scale, chain);
@@ -353,8 +376,33 @@
                 // Lazily create PluralRules
                 rules = PluralRules.forLocale(macros.loc);
             }
-            chain = LongNameHandler
-                    .forMeasureUnit(macros.loc, macros.unit, macros.perUnit, unitWidth, rules, chain);
+            PluralRules pluralRules = macros.rules != null ?
+                    macros.rules :
+                    PluralRules.forLocale(macros.loc);
+
+            if (macros.usage != null) {
+                assert usagePrefsHandler != null;
+                chain = LongNameMultiplexer.forMeasureUnits(
+                        macros.loc,
+                        usagePrefsHandler.getOutputUnits(),
+                        unitWidth,
+                        pluralRules,
+                        chain);
+            } else if (isMixedUnit) {
+                chain = MixedUnitLongNameHandler.forMeasureUnit(
+                        macros.loc,
+                        macros.unit,
+                        unitWidth,
+                        pluralRules,
+                        chain);
+            } else {
+                chain = LongNameHandler.forMeasureUnit(macros.loc,
+                        macros.unit,
+                        macros.perUnit,
+                        unitWidth,
+                        pluralRules,
+                        chain);
+            }
         } else if (isCurrency && unitWidth == UnitWidth.FULL_NAME) {
             if (rules == null) {
                 // Lazily create PluralRules
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterSettings.java b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterSettings.java
index 190885e..fb1072b 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterSettings.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterSettings.java
@@ -45,6 +45,7 @@
     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;
 
     private final NumberFormatterSettings<?> parent;
     private final int key;
@@ -133,6 +134,11 @@
      * <p>
      * The default is to render without units (equivalent to {@link NoUnit#BASE}).
      *
+     * <P>
+     * If the input usage is correctly set the output unit <b>will change</b>
+     * according to `usage`, `locale` and `unit` value.
+     * </p>
+     *
      * @param unit
      *            The unit to render.
      * @return The fluent chain.
@@ -486,6 +492,50 @@
         return create(KEY_SCALE, scale);
     }
 
+   /**
+     * Specifies the usage for which numbers will be formatted ("person-height",
+     * "road", "rainfall", etc.)
+     *
+     * 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".
+     *
+     * 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.
+     *
+     * The output unit can be found via FormattedNumber.getOutputUnit().
+     *
+     * 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".
+     *
+     * Setting usage to an empty string clears the usage (disables usage-based
+     * localized formatting).
+     *
+     *
+     * 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
+     * @provisional This API might change or be removed in a future release.
+     */
+    public T usage(String usage) {
+        return create(KEY_USAGE, usage);
+    }
+
     /**
      * Internal method to set a starting macros.
      *
@@ -632,6 +682,11 @@
                     macros.perUnit = (MeasureUnit) current.value;
                 }
                 break;
+            case KEY_USAGE:
+                if(macros.usage == null) {
+                    macros.usage = (String) current.value;
+                }
+                break;
             default:
                 throw new AssertionError("Unknown key: " + current.key);
             }
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberSkeletonImpl.java b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberSkeletonImpl.java
index 76a18f8..02565f0 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberSkeletonImpl.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberSkeletonImpl.java
@@ -33,10 +33,12 @@
  */
 class NumberSkeletonImpl {
 
-    ///////////////////////////////////////////////////////////////////////////////////////
-    // NOTE: For an example of how to add a new stem to the number skeleton parser, see: //
-    // http://bugs.icu-project.org/trac/changeset/41193                                  //
-    ///////////////////////////////////////////////////////////////////////////////////////
+    //////////////////////////////////////////////////////////////////////////////////////////
+    // NOTE: For examples of how to add a new stem to the number skeleton parser, see:      //
+    // https://github.com/unicode-org/icu/commit/a2a7982216b2348070dc71093775ac7195793d73   //
+    // and                                                                                  //
+    // https://github.com/unicode-org/icu/commit/6fe86f3934a8a5701034f648a8f7c5087e84aa28   //
+    //////////////////////////////////////////////////////////////////////////////////////////
 
     /**
      * While parsing a skeleton, this enum records what type of option we expect to find next.
@@ -54,6 +56,7 @@
         STATE_MEASURE_UNIT,
         STATE_PER_MEASURE_UNIT,
         STATE_IDENTIFIER_UNIT,
+        STATE_UNIT_USAGE,
         STATE_CURRENCY_UNIT,
         STATE_INTEGER_WIDTH,
         STATE_NUMBERING_SYSTEM,
@@ -118,6 +121,7 @@
         STEM_MEASURE_UNIT,
         STEM_PER_MEASURE_UNIT,
         STEM_UNIT,
+        STEM_UNIT_USAGE,
         STEM_CURRENCY,
         STEM_INTEGER_WIDTH,
         STEM_NUMBERING_SYSTEM,
@@ -193,6 +197,7 @@
         b.add("measure-unit", StemEnum.STEM_MEASURE_UNIT.ordinal());
         b.add("per-measure-unit", StemEnum.STEM_PER_MEASURE_UNIT.ordinal());
         b.add("unit", StemEnum.STEM_UNIT.ordinal());
+        b.add("usage", StemEnum.STEM_UNIT_USAGE.ordinal());
         b.add("currency", StemEnum.STEM_CURRENCY.ordinal());
         b.add("integer-width", StemEnum.STEM_INTEGER_WIDTH.ordinal());
         b.add("numbering-system", StemEnum.STEM_NUMBERING_SYSTEM.ordinal());
@@ -613,6 +618,7 @@
                 case STATE_INCREMENT_PRECISION:
                 case STATE_MEASURE_UNIT:
                 case STATE_PER_MEASURE_UNIT:
+                case STATE_UNIT_USAGE:
                 case STATE_CURRENCY_UNIT:
                 case STATE_INTEGER_WIDTH:
                 case STATE_NUMBERING_SYSTEM:
@@ -786,6 +792,10 @@
             checkNull(macros.perUnit, segment);
             return ParseState.STATE_IDENTIFIER_UNIT;
 
+        case STEM_UNIT_USAGE:
+            checkNull(macros.usage, segment);
+            return ParseState.STATE_UNIT_USAGE;
+
         case STEM_CURRENCY:
             checkNull(macros.unit, segment);
             return ParseState.STATE_CURRENCY_UNIT;
@@ -830,6 +840,9 @@
         case STATE_IDENTIFIER_UNIT:
             BlueprintHelpers.parseIdentifierUnitOption(segment, macros);
             return ParseState.STATE_NULL;
+        case STATE_UNIT_USAGE:
+            BlueprintHelpers.parseUnitUsageOption(segment, macros);
+            return ParseState.STATE_NULL;
         case STATE_INCREMENT_PRECISION:
             BlueprintHelpers.parseIncrementOption(segment, macros);
             return ParseState.STATE_NULL;
@@ -894,6 +907,9 @@
         if (macros.perUnit != null && GeneratorHelpers.perUnit(macros, sb)) {
             sb.append(' ');
         }
+        if (macros.usage != null && GeneratorHelpers.usage(macros, sb)) {
+            sb.append(' ');
+        }
         if (macros.precision != null && GeneratorHelpers.precision(macros, sb)) {
             sb.append(' ');
         }
@@ -1047,6 +1063,10 @@
             macros.unit = numerator;
         }
 
+        /**
+         * Parses unit identifiers like "meter-per-second" and "foot-and-inch", as
+         * specified via a "unit/" concise skeleton.
+         */
         private static void parseIdentifierUnitOption(StringSegment segment, MacroProps macros) {
             MeasureUnit[] units = MeasureUnit.parseCoreUnitIdentifier(segment.asString());
             if (units == null) {
@@ -1058,6 +1078,12 @@
             }
         }
 
+        private static void parseUnitUsageOption(StringSegment segment, MacroProps macros) {
+            macros.usage = segment.asString();
+            // We do not do any validation of the usage string: it depends on the
+            // unitPreferenceData in the units resources.
+        }
+
         private static void parseFractionStem(StringSegment segment, MacroProps macros) {
             assert segment.charAt(0) == '.';
             int offset = 1;
@@ -1461,6 +1487,16 @@
             }
         }
 
+        private static boolean usage(MacroProps macros, StringBuilder sb) {
+            if (macros.usage != null  && macros.usage.length() > 0) {
+                sb.append("usage/");
+                sb.append(macros.usage);
+
+                return true;
+            }
+            return false;
+        }
+
         private static boolean precision(MacroProps macros, StringBuilder sb) {
             if (macros.precision instanceof Precision.InfiniteRounderImpl) {
                 sb.append("precision-unlimited");
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/Precision.java b/icu4j/main/classes/core/src/com/ibm/icu/number/Precision.java
index 8f9c7db..d2dfb6b 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/number/Precision.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/number/Precision.java
@@ -20,6 +20,7 @@
  *
  * @stable ICU 62
  * @see NumberFormatter
+ * @internal
  */
 public abstract class Precision {
 
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/util/MeasureUnit.java b/icu4j/main/classes/core/src/com/ibm/icu/util/MeasureUnit.java
index 3e81417..9f23eaa 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/util/MeasureUnit.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/util/MeasureUnit.java
@@ -73,7 +73,7 @@
      *
      * @internal
      */
-    private MeasureUnitImpl measureUnitImpl = null;
+    private MeasureUnitImpl measureUnitImpl;
 
     /**
      * Enumeration for unit complexity. There are three levels:
@@ -346,6 +346,7 @@
     /**
      * @internal
      * @param measureUnitImpl
+     * @deprecated Internal API for ICU use only.
      */
     public static MeasureUnit fromMeasureUnitImpl(MeasureUnitImpl measureUnitImpl) {
         measureUnitImpl.serialize();
@@ -2094,4 +2095,4 @@
             return MeasureUnit.internalGetInstance(type, subType);
         }
     }
-}
\ No newline at end of file
+}
diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java
index 5198c59..b10b414 100644
--- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java
+++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java
@@ -41,6 +41,7 @@
 import com.ibm.icu.number.Precision;
 import com.ibm.icu.number.Scale;
 import com.ibm.icu.number.ScientificNotation;
+import com.ibm.icu.number.SkeletonSyntaxException;
 import com.ibm.icu.number.UnlocalizedNumberFormatter;
 import com.ibm.icu.text.ConstrainedFieldPosition;
 import com.ibm.icu.text.DecimalFormatSymbols;
@@ -642,6 +643,81 @@
                 ULocale.forLanguageTag("es-MX"),
                 1,
                 "kelvin");
+
+        // TODO(icu-units#35): skeleton generation.
+        assertFormatSingle(
+                "Mixed unit",
+                null,
+                "unit/yard-and-foot-and-inch",
+                NumberFormatter.with()
+                        .unit(MeasureUnit.forIdentifier("yard-and-foot-and-inch")),
+                new ULocale("en-US"),
+                3.65,
+                "3 yd, 1 ft, 11.4 in");
+
+        // TODO(icu-units#35): skeleton generation.
+        assertFormatSingle(
+                "Mixed unit, Scientific",
+                null,
+                "unit/yard-and-foot-and-inch E0",
+                NumberFormatter.with()
+                        .unit(MeasureUnit.forIdentifier("yard-and-foot-and-inch"))
+                        .notation(Notation.scientific()),
+                new ULocale("en-US"),
+                3.65,
+                "3 yd, 1 ft, 1.14E1 in");
+
+        // TODO(icu-units#35): skeleton generation.
+        assertFormatSingle(
+                "Mixed Unit (Narrow Version)",
+                null,
+                "unit/metric-ton-and-kilogram-and-gram unit-width-narrow",
+                NumberFormatter.with()
+                        .unit(MeasureUnit.forIdentifier("metric-ton-and-kilogram-and-gram"))
+                        .unitWidth(UnitWidth.NARROW),
+                new ULocale("en-US"),
+                4.28571,
+                "4t 285kg 710g");
+
+        // TODO(icu-units#35): skeleton generation.
+        assertFormatSingle(
+                "Mixed Unit (Short Version)",
+                null,
+                "unit/metric-ton-and-kilogram-and-gram unit-width-short",
+                NumberFormatter.with()
+                        .unit(MeasureUnit.forIdentifier("metric-ton-and-kilogram-and-gram"))
+                        .unitWidth(UnitWidth.SHORT),
+                new ULocale("en-US"),
+                4.28571,
+                "4 t, 285 kg, 710 g");
+
+        // TODO(icu-units#35): skeleton generation.
+        assertFormatSingle(
+                "Mixed Unit (Full Name Version)",
+                null,
+                "unit/metric-ton-and-kilogram-and-gram unit-width-full-name",
+                NumberFormatter.with()
+                        .unit(MeasureUnit.forIdentifier("metric-ton-and-kilogram-and-gram"))
+                        .unitWidth(UnitWidth.FULL_NAME),
+                new ULocale("en-US"),
+                4.28571,
+                "4 metric tons, 285 kilograms, 710 grams");
+
+//     // TODO(icu-units#73): deal with this "1 foot 12 inches" problem.
+//     // At the time of writing, this test would pass, but is commented out
+//     // because it reflects undesired behaviour:
+//     assertFormatSingle(
+//             u"Demonstrating the \"1 foot 12 inches\" problem",
+//             nullptr,
+//             u"unit/foot-and-inch",
+//             NumberFormatter::with()
+//                 .unit(MeasureUnit::forIdentifier("foot-and-inch"))
+//                 .precision(Precision::maxSignificantDigits(4))
+//                 .unitWidth(UNUM_UNIT_WIDTH_FULL_NAME),
+//             Locale("en-US"),
+//             1.9999,
+//             // This is undesireable but current behaviour:
+//             u"1 foot, 12 inches");
     }
 
     @Test
@@ -693,8 +769,468 @@
                 "0.08765 J/fur",
                 "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-units#59): THIS UNIT TEST DEMONSTRATES UNDESIRABLE BEHAVIOUR!
+        // When specifying built-in types, one can give both a unit and a perUnit.
+        // Resolving to a built-in unit does not always work.
+        //
+        // (Unit-testing philosophy: do we leave this enabled to demonstrate current
+        // behaviour, and changing behaviour in the future? Or comment it out to
+        // avoid asserting this is "correct"?)
+        assertFormatSingle(
+                "DEMONSTRATING BAD BEHAVIOUR, TODO(icu-units#59)",
+                "measure-unit/speed-meter-per-second per-measure-unit/duration-second",
+                "measure-unit/speed-meter-per-second per-measure-unit/duration-second",
+                NumberFormatter.with()
+                        .unit(MeasureUnit.METER_PER_SECOND)
+                        .perUnit(MeasureUnit.SECOND),
+                new ULocale("en-GB"),
+                2.4,
+                "2.4 m/s/s");
+
+        // Testing the rejection of invalid specifications
+
+        // If .unit() is not given a built-in type, .perUnit() is not allowed
+        // (because .unit is now flexible enough to handle compound units,
+        // .perUnit() is supported for backward compatibility).
+        LocalizedNumberFormatter nf = NumberFormatter.with()
+                .unit(MeasureUnit.forIdentifier("furlong-pascal"))
+                .perUnit(MeasureUnit.METER)
+                .locale(new ULocale("en-GB"));
+
+        try {
+            nf.format(2.4d);
+            fail("Expected failure, got: " + nf.format(2.4d) + ".");
+        } catch (UnsupportedOperationException e) {
+            // Pass
+        }
+
+        // .perUnit() may only be passed a built-in type, "square-second" is not a
+        // built-in type.
+        nf = NumberFormatter.with()
+                .unit(MeasureUnit.METER)
+                .perUnit(MeasureUnit.forIdentifier("square-second"))
+                .locale(new ULocale("en-GB"));
+
+        try {
+            nf.format(2.4d);
+            fail("Expected failure, got: " + nf.format(2.4d) + ".");
+        } catch (UnsupportedOperationException e) {
+            // pass
+        }
     }
 
+
+    @Test
+    public void unitUsage() {
+        UnlocalizedNumberFormatter unloc_formatter;
+        LocalizedNumberFormatter formatter;
+        FormattedNumber formattedNum;
+        String uTestCase;
+
+        unloc_formatter = NumberFormatter.with().usage("road").unit(MeasureUnit.METER);
+
+        uTestCase = "unitUsage() en-ZA road";
+        formatter = unloc_formatter.locale(new ULocale("en-ZA"));
+        formattedNum = formatter.format(321d);
+
+
+        assertTrue(
+                uTestCase + ", got outputUnit: \"" + formattedNum.getOutputUnit().getIdentifier() + "\"",
+                MeasureUnit.METER.equals(formattedNum.getOutputUnit()));
+        assertEquals(uTestCase, "300 m", formattedNum.toString());
+        {
+            final Object[][] expectedFieldPositions = {
+                    {NumberFormat.Field.INTEGER, 0, 3},
+                    {NumberFormat.Field.MEASURE_UNIT, 4, 5}
+            };
+
+            assertNumberFieldPositions(
+                    uTestCase + " field positions",
+                    formattedNum,
+                    expectedFieldPositions);
+        }
+
+        assertFormatDescendingBig(
+                uTestCase,
+                "measure-unit/length-meter usage/road",
+                "unit/meter usage/road",
+                unloc_formatter,
+                new ULocale("en-ZA"),
+                "87\u00A0650 km",
+                "8\u00A0765 km",
+                "876 km", // 6.5 rounds down, 7.5 rounds up.
+                "88 km",
+                "8,8 km",
+                "900 m",
+                "90 m",
+                "10 m",
+                "0 m");
+
+        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},
+                    {NumberFormat.Field.MEASURE_UNIT, 4, 6}};
+            assertNumberFieldPositions(
+                    (uTestCase + " field positions"),
+                    formattedNum,
+                    expectedFieldPositions);
+        }
+
+        assertFormatDescendingBig(
+                uTestCase,
+                "measure-unit/length-meter usage/road",
+                "unit/meter usage/road",
+                unloc_formatter,
+                new ULocale("en-GB"),
+                "54,463 mi",
+                "5,446 mi",
+                "545 mi",
+                "54 mi",
+                "5.4 mi",
+                "0.54 mi",
+                "96 yd",
+                "9.6 yd",
+                "0 yd");
+
+        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},
+                    {NumberFormat.Field.INTEGER, 0, 5},
+                    {NumberFormat.Field.MEASURE_UNIT, 6, 8}};
+            assertNumberFieldPositions(
+                    uTestCase + " field positions",
+                    formattedNum,
+                    expectedFieldPositions);
+        }
+        assertFormatDescendingBig(
+                uTestCase,
+                "measure-unit/length-meter usage/road",
+                "unit/meter usage/road",
+                unloc_formatter,
+                new ULocale("en-US"),
+                "54,463 mi",
+                "5,446 mi",
+                "545 mi",
+                "54 mi",
+                "5.4 mi",
+                "0.54 mi",
+                "300 ft",
+                "30 ft",
+                "0 ft");
+
+        unloc_formatter = NumberFormatter.with().usage("person").unit(MeasureUnit.KILOGRAM);
+        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)
+                    // {NumberFormat.Field.INTEGER, 0, 2},
+                    // {NumberFormat.Field.MEASURE_UNIT, 3, 5},
+                    // {NumberFormat.ULISTFMT_LITERAL_FIELD, 5, 6},
+                    // {NumberFormat.Field.INTEGER, 7, 8},
+                    // {NumberFormat.DECIMAL_SEPARATOR_FIELD, 8, 9},
+                    // {NumberFormat.FRACTION_FIELD, 9, 10},
+                    // {NumberFormat.Field.MEASURE_UNIT, 11, 13}};
+
+                    // Current output: rather no fields than wrong fields
+                    {NumberFormat.Field.INTEGER, 7, 8},
+                    {NumberFormat.Field.DECIMAL_SEPARATOR, 8, 9},
+                    {NumberFormat.Field.FRACTION, 9, 10},
+            };
+
+            assertNumberFieldPositions(
+                    uTestCase + " field positions",
+                    formattedNum,
+                    expectedFieldPositions);
+        }
+        assertFormatDescending(
+                uTestCase,
+                "measure-unit/mass-kilogram usage/person",
+                "unit/kilogram usage/person",
+                unloc_formatter,
+                new ULocale("en-GB"),
+                "13,802 st, 7.2 lb",
+                "1,380 st, 3.5 lb",
+                "138 st, 0.35 lb",
+                "13 st, 11 lb",
+                "1 st, 5.3 lb",
+                "1 lb, 15 oz",
+                "0 lb, 3.1 oz",
+                "0 lb, 0.31 oz",
+                "0 lb, 0 oz");
+
+        assertFormatDescending(
+                uTestCase,
+                "usage/person unit-width-narrow measure-unit/mass-kilogram",
+                "usage/person unit-width-narrow unit/kilogram",
+                unloc_formatter.unitWidth(UnitWidth.NARROW),
+                new ULocale("en-GB"),
+                "13,802st 7.2lb",
+                "1,380st 3.5lb",
+                "138st 0.35lb",
+                "13st 11lb",
+                "1st 5.3lb",
+                "1lb 15oz",
+                "0lb 3.1oz",
+                "0lb 0.31oz",
+                "0lb 0oz");
+
+        assertFormatDescending(
+                uTestCase,
+                "usage/person unit-width-short measure-unit/mass-kilogram",
+                "usage/person unit-width-short unit/kilogram",
+                unloc_formatter.unitWidth(UnitWidth.SHORT),
+                new ULocale("en-GB"),
+                "13,802 st, 7.2 lb",
+                "1,380 st, 3.5 lb",
+                "138 st, 0.35 lb",
+                "13 st, 11 lb",
+                "1 st, 5.3 lb",
+                "1 lb, 15 oz",
+                "0 lb, 3.1 oz",
+                "0 lb, 0.31 oz",
+                "0 lb, 0 oz");
+
+        assertFormatDescending(
+                uTestCase,
+                "usage/person unit-width-full-name measure-unit/mass-kilogram",
+                "usage/person unit-width-full-name unit/kilogram",
+                unloc_formatter.unitWidth(UnitWidth.FULL_NAME),
+                new ULocale("en-GB"),
+                "13,802 stone, 7.2 pounds",
+                "1,380 stone, 3.5 pounds",
+                "138 stone, 0.35 pounds",
+                "13 stone, 11 pounds",
+                "1 stone, 5.3 pounds",
+                "1 pound, 15 ounces",
+                "0 pounds, 3.1 ounces",
+                "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");
+    }
+
+
+    @Test
+    public void unitUsageErrorCodes() {
+        UnlocalizedNumberFormatter unloc_formatter;
+
+        try {
+            NumberFormatter.forSkeleton("unit/foobar");
+            fail("should give an error, because foobar is an invalid unit");
+        } catch (SkeletonSyntaxException e) {
+            // Pass
+        }
+
+        unloc_formatter = NumberFormatter.forSkeleton("usage/foobar");
+        // This does not give an error, because usage is not looked up yet.
+        //status.errIfFailureAndReset("Expected behaviour: no immediate error for invalid usage");
+
+        try {
+            // Lacking a unit results in a failure. The skeleton is "incomplete", but we
+            // support adding the unit via the fluent API, so it is not an error until
+            // we build the formatting pipeline itself.
+            unloc_formatter.locale(new ULocale("en-GB")).format(1);
+            fail("should throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // Pass
+        }
+
+        // Adding the unit as part of the fluent chain leads to success.
+        unloc_formatter.unit(MeasureUnit.METER).locale(new ULocale("en-GB")).format(1); /* No Exception should be thrown */
+
+    }
+
+
+    // Tests for the "skeletons" field in unitPreferenceData, as well as precision
+    // and notation overrides.
+    @Test
+    public void unitUsageSkeletons() {
+        assertFormatSingle(
+                "Default >300m road preference skeletons round to 50m",
+                "usage/road measure-unit/length-meter",
+                "usage/road unit/meter",
+                NumberFormatter.with().unit(MeasureUnit.METER).usage("road"),
+                new ULocale("en-ZA"),
+                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(
+                "Compact notation with Usage: bizarre, but possible (short)",
+                "compact-short usage/road measure-unit/length-meter",
+                "compact-short usage/road unit/meter",
+                NumberFormatter.with()
+                        .unit(MeasureUnit.METER)
+                        .usage("road")
+                        .notation(Notation.compactShort()),
+                new ULocale("en-ZA"),
+                987654321L,
+                "988K km");
+
+
+
+        // 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/default measure-unit/length-astronomical-unit unit-width-full-name",
+                "scientific usage/default unit/astronomical-unit unit-width-full-name",
+                NumberFormatter.with()
+                        .unit(MeasureUnit.forIdentifier("astronomical-unit"))
+                        .usage("default")
+                        .notation(Notation.scientific())
+                        .unitWidth(UnitWidth.FULL_NAME),
+                new ULocale("en-ZA"),
+                1e20,
+                "1,5E28 kilometres");
+    }
+
+
     @Test
     public void unitCurrency() {
         assertFormatDescending(
@@ -3332,6 +3868,7 @@
                 conciseSkeleton = conciseSkeleton.substring(1);
                 shouldRoundTrip = false;
             }
+
             LocalizedNumberFormatter l4 = NumberFormatter.forSkeleton(conciseSkeleton).locale(locale);
             if (shouldRoundTrip) {
                 assertEquals(message + ": Concise Skeleton:", normalized, l4.toSkeleton());