]> granicus.if.org Git - icu/commitdiff
ICU-8610 Full support for skeletons in ICU4J. Needs a few more tests.
authorShane Carr <shane@unicode.org>
Thu, 1 Mar 2018 09:24:37 +0000 (09:24 +0000)
committerShane Carr <shane@unicode.org>
Thu, 1 Mar 2018 09:24:37 +0000 (09:24 +0000)
X-SVN-Rev: 41038

icu4j/main/classes/core/src/com/ibm/icu/number/NumberSkeletonImpl.java
icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java
icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberSkeletonTest.java

index 1841ad700c8fe573494453fd6177f3bf471c0737..896c245eb6a6501b78a613e955d489db1f82c594 100644 (file)
@@ -12,7 +12,11 @@ import java.util.concurrent.ConcurrentHashMap;
 import com.ibm.icu.impl.PatternProps;
 import com.ibm.icu.impl.StringSegment;
 import com.ibm.icu.impl.number.MacroProps;
+import com.ibm.icu.number.NumberFormatter.DecimalSeparatorDisplay;
+import com.ibm.icu.number.NumberFormatter.GroupingStrategy;
+import com.ibm.icu.number.NumberFormatter.SignDisplay;
 import com.ibm.icu.number.NumberFormatter.UnitWidth;
+import com.ibm.icu.text.NumberingSystem;
 import com.ibm.icu.util.Currency;
 import com.ibm.icu.util.Currency.CurrencyUsage;
 import com.ibm.icu.util.MeasureUnit;
@@ -25,7 +29,25 @@ import com.ibm.icu.util.NoUnit;
 class NumberSkeletonImpl {
 
     static enum StemType {
-        OTHER, ROUNDER, FRACTION_ROUNDER, MAYBE_INCREMENT_ROUNDER, CURRENCY_ROUNDER, MEASURE_UNIT, UNIT_WIDTH
+        OTHER,
+        COMPACT_NOTATION,
+        SCIENTIFIC_NOTATION,
+        SIMPLE_NOTATION,
+        NO_UNIT,
+        CURRENCY,
+        MEASURE_UNIT,
+        PER_MEASURE_UNIT,
+        ROUNDER,
+        FRACTION_ROUNDER,
+        MAYBE_INCREMENT_ROUNDER,
+        CURRENCY_ROUNDER,
+        GROUPING,
+        INTEGER_WIDTH,
+        LATIN,
+        NUMBERING_SYSTEM,
+        UNIT_WIDTH,
+        SIGN_DISPLAY,
+        DECIMAL_DISPLAY
     }
 
     static class SkeletonDataStructure {
@@ -61,6 +83,16 @@ class NumberSkeletonImpl {
     static final SkeletonDataStructure skeletonData = new SkeletonDataStructure();
 
     static {
+        skeletonData.put(StemType.COMPACT_NOTATION, "compact-short", Notation.compactShort());
+        skeletonData.put(StemType.COMPACT_NOTATION, "compact-long", Notation.compactLong());
+        skeletonData.put(StemType.SCIENTIFIC_NOTATION, "scientific", Notation.scientific());
+        skeletonData.put(StemType.SCIENTIFIC_NOTATION, "engineering", Notation.engineering());
+        skeletonData.put(StemType.SIMPLE_NOTATION, "simple-notation", Notation.simple());
+
+        skeletonData.put(StemType.NO_UNIT, "base-unit", NoUnit.BASE);
+        skeletonData.put(StemType.NO_UNIT, "percent", NoUnit.PERCENT);
+        skeletonData.put(StemType.NO_UNIT, "permille", NoUnit.PERMILLE);
+
         skeletonData.put(StemType.ROUNDER, "round-integer", Rounder.integer());
         skeletonData.put(StemType.ROUNDER, "round-unlimited", Rounder.unlimited());
         skeletonData.put(StemType.ROUNDER,
@@ -68,11 +100,32 @@ class NumberSkeletonImpl {
                 Rounder.currency(CurrencyUsage.STANDARD));
         skeletonData.put(StemType.ROUNDER, "round-currency-cash", Rounder.currency(CurrencyUsage.CASH));
 
+        skeletonData.put(StemType.GROUPING, "group-off", GroupingStrategy.OFF);
+        skeletonData.put(StemType.GROUPING, "group-min2", GroupingStrategy.MIN2);
+        skeletonData.put(StemType.GROUPING, "group-auto", GroupingStrategy.AUTO);
+        skeletonData.put(StemType.GROUPING, "group-on-aligned", GroupingStrategy.ON_ALIGNED);
+        skeletonData.put(StemType.GROUPING, "group-thousands", GroupingStrategy.THOUSANDS);
+
+        skeletonData.put(StemType.LATIN, "latin", NumberingSystem.LATIN);
+
         skeletonData.put(StemType.UNIT_WIDTH, "unit-width-narrow", UnitWidth.NARROW);
         skeletonData.put(StemType.UNIT_WIDTH, "unit-width-short", UnitWidth.SHORT);
         skeletonData.put(StemType.UNIT_WIDTH, "unit-width-full-name", UnitWidth.FULL_NAME);
         skeletonData.put(StemType.UNIT_WIDTH, "unit-width-iso-code", UnitWidth.ISO_CODE);
         skeletonData.put(StemType.UNIT_WIDTH, "unit-width-hidden", UnitWidth.HIDDEN);
+
+        skeletonData.put(StemType.SIGN_DISPLAY, "sign-auto", SignDisplay.AUTO);
+        skeletonData.put(StemType.SIGN_DISPLAY, "sign-always", SignDisplay.ALWAYS);
+        skeletonData.put(StemType.SIGN_DISPLAY, "sign-never", SignDisplay.NEVER);
+        skeletonData.put(StemType.SIGN_DISPLAY, "sign-accounting", SignDisplay.ACCOUNTING);
+        skeletonData.put(StemType.SIGN_DISPLAY, "sign-accounting-always", SignDisplay.ACCOUNTING_ALWAYS);
+        skeletonData.put(StemType.SIGN_DISPLAY, "sign-except-zero", SignDisplay.EXCEPT_ZERO);
+        skeletonData.put(StemType.SIGN_DISPLAY,
+                "sign-accounting-except-zero",
+                SignDisplay.ACCOUNTING_EXCEPT_ZERO);
+
+        skeletonData.put(StemType.DECIMAL_DISPLAY, "decimal-auto", DecimalSeparatorDisplay.AUTO);
+        skeletonData.put(StemType.DECIMAL_DISPLAY, "decimal-always", DecimalSeparatorDisplay.ALWAYS);
     }
 
     private static final Map<String, UnlocalizedNumberFormatter> cache = new ConcurrentHashMap<String, UnlocalizedNumberFormatter>();
@@ -183,6 +236,10 @@ class NumberSkeletonImpl {
                 switch (stem) {
                 case MAYBE_INCREMENT_ROUNDER:
                 case MEASURE_UNIT:
+                case PER_MEASURE_UNIT:
+                case CURRENCY:
+                case INTEGER_WIDTH:
+                case NUMBERING_SYSTEM:
                     throw new SkeletonSyntaxException("Stem requires an option", segment);
                 default:
                     break;
@@ -200,14 +257,40 @@ class NumberSkeletonImpl {
         if (stem != null) {
             Object value = skeletonData.stemToValue(content);
             switch (stem) {
+            case COMPACT_NOTATION:
+            case SCIENTIFIC_NOTATION:
+            case SIMPLE_NOTATION:
+                checkNull(macros.notation, content);
+                macros.notation = (Notation) value;
+                break;
+            case NO_UNIT:
+                checkNull(macros.unit, content);
+                macros.unit = (NoUnit) value;
+                break;
             case ROUNDER:
                 checkNull(macros.rounder, content);
                 macros.rounder = (Rounder) value;
                 break;
+            case GROUPING:
+                checkNull(macros.grouping, content);
+                macros.grouping = value;
+                break;
+            case LATIN:
+                checkNull(macros.symbols, content);
+                macros.symbols = value;
+                break;
             case UNIT_WIDTH:
                 checkNull(macros.unitWidth, content);
                 macros.unitWidth = (UnitWidth) value;
                 break;
+            case SIGN_DISPLAY:
+                checkNull(macros.sign, content);
+                macros.sign = (SignDisplay) value;
+                break;
+            case DECIMAL_DISPLAY:
+                checkNull(macros.decimal, content);
+                macros.decimal = (DecimalSeparatorDisplay) value;
+                break;
             default:
                 assert false;
             }
@@ -216,19 +299,35 @@ class NumberSkeletonImpl {
 
         // Second try: literal stems that require an option
         if (content.equals("round-increment")) {
+            checkNull(macros.rounder, content);
             return StemType.MAYBE_INCREMENT_ROUNDER;
         } else if (content.equals("measure-unit")) {
+            checkNull(macros.unit, content);
             return StemType.MEASURE_UNIT;
-        }
-
-        // Second try: stem "blueprint" syntax
+        } else if (content.equals("per-measure-unit")) {
+            checkNull(macros.perUnit, content);
+            return StemType.PER_MEASURE_UNIT;
+        } else if (content.equals("currency")) {
+            checkNull(macros.unit, content);
+            return StemType.CURRENCY;
+        } else if (content.equals("integer-width")) {
+            checkNull(macros.integerWidth, content);
+            return StemType.INTEGER_WIDTH;
+        } else if (content.equals("numbering-system")) {
+            checkNull(macros.symbols, content);
+            return StemType.NUMBERING_SYSTEM;
+        }
+
+        // Third try: stem "blueprint" syntax
         switch (content.charAt(0)) {
         case '.':
             stem = StemType.FRACTION_ROUNDER;
+            checkNull(macros.rounder, content);
             parseFractionStem(content, macros);
             break;
         case '@':
             stem = StemType.ROUNDER;
+            checkNull(macros.rounder, content);
             parseDigitsStem(content, macros);
             break;
         }
@@ -241,6 +340,43 @@ class NumberSkeletonImpl {
     }
 
     private static StemType parseOption(StemType stem, CharSequence content, MacroProps macros) {
+
+        ///// Required options: /////
+
+        switch (stem) {
+        case CURRENCY:
+            parseCurrencyOption(content, macros);
+            return StemType.OTHER;
+        case MEASURE_UNIT:
+            parseMeasureUnitOption(content, macros);
+            return StemType.OTHER;
+        case PER_MEASURE_UNIT:
+            parseMeasurePerUnitOption(content, macros);
+            return StemType.OTHER;
+        case MAYBE_INCREMENT_ROUNDER:
+            parseIncrementOption(content, macros);
+            return StemType.ROUNDER;
+        case INTEGER_WIDTH:
+            parseIntegerWidthOption(content, macros);
+            return StemType.OTHER;
+        case NUMBERING_SYSTEM:
+            parseNumberingSystemOption(content, macros);
+            return StemType.OTHER;
+        }
+
+        ///// Non-required options: /////
+
+        // Scientific options
+        switch (stem) {
+        case SCIENTIFIC_NOTATION:
+            if (parseExponentWidthOption(content, macros)) {
+                return StemType.SCIENTIFIC_NOTATION;
+            }
+            if (parseExponentSignOption(content, macros)) {
+                return StemType.SCIENTIFIC_NOTATION;
+            }
+        }
+
         // Frac-sig option
         switch (stem) {
         case FRACTION_ROUNDER:
@@ -249,51 +385,61 @@ class NumberSkeletonImpl {
             }
         }
 
-        // Increment option
-        switch (stem) {
-        case MAYBE_INCREMENT_ROUNDER:
-            // The increment option is required.
-            parseIncrementOption(content, macros);
-            return StemType.ROUNDER;
-        }
-
         // Rounding mode option
         switch (stem) {
         case ROUNDER:
         case FRACTION_ROUNDER:
         case CURRENCY_ROUNDER:
             if (parseRoundingModeOption(content, macros)) {
-                break;
+                return StemType.ROUNDER;
             }
         }
 
-        // Measure unit option
-        switch (stem) {
-        case MEASURE_UNIT:
-            // The measure unit option is required.
-            parseMeasureUnitOption(content, macros);
-            return StemType.OTHER;
-        }
-
         // Unknown option
         throw new SkeletonSyntaxException("Unknown option", content);
     }
 
-    /////
-
     private static void generateSkeleton(MacroProps macros, StringBuilder sb) {
-        if (macros.rounder != null) {
-            generateRoundingValue(macros, sb);
+        if (macros.notation != null) {
+            generateNotationValue(macros, sb);
             sb.append(' ');
         }
         if (macros.unit != null) {
             generateUnitValue(macros, sb);
             sb.append(' ');
         }
+        if (macros.perUnit != null) {
+            generatePerUnitValue(macros, sb);
+            sb.append(' ');
+        }
+        if (macros.rounder != null) {
+            generateRoundingValue(macros, sb);
+            sb.append(' ');
+        }
+        if (macros.grouping != null) {
+            generateGroupingValue(macros, sb);
+            sb.append(' ');
+        }
+        if (macros.integerWidth != null) {
+            generateIntegerWidthValue(macros, sb);
+            sb.append(' ');
+        }
+        if (macros.symbols != null) {
+            generateSymbolsValue(macros, sb);
+            sb.append(' ');
+        }
         if (macros.unitWidth != null) {
             generateUnitWidthValue(macros, sb);
             sb.append(' ');
         }
+        if (macros.sign != null) {
+            generateSignValue(macros, sb);
+            sb.append(' ');
+        }
+        if (macros.decimal != null) {
+            generateDecimalValue(macros, sb);
+            sb.append(' ');
+        }
 
         // Remove the trailing space
         if (sb.length() > 0) {
@@ -303,6 +449,90 @@ class NumberSkeletonImpl {
 
     /////
 
+    private static boolean parseExponentWidthOption(CharSequence content, MacroProps macros) {
+        if (content.charAt(0) != '+') {
+            return false;
+        }
+        int offset = 1;
+        int minExp = 0;
+        for (; offset < content.length(); offset++) {
+            if (content.charAt(offset) == 'e') {
+                minExp++;
+            } else {
+                break;
+            }
+        }
+        if (offset < content.length()) {
+            return false;
+        }
+        // Use the public APIs to enforce bounds checking
+        macros.notation = ((ScientificNotation) macros.notation).withMinExponentDigits(minExp);
+        return true;
+    }
+
+    private static void generateExponentWidthOption(int minInt, int maxInt, StringBuilder sb) {
+        sb.append('+');
+        appendMultiple(sb, 'e', minInt);
+    }
+
+    private static boolean parseExponentSignOption(CharSequence content, MacroProps macros) {
+        Object value = skeletonData.stemToValue(content);
+        if (value != null && value instanceof SignDisplay) {
+            macros.notation = ((ScientificNotation) macros.notation)
+                    .withExponentSignDisplay((SignDisplay) value);
+            return true;
+        }
+        return false;
+    }
+
+    private static void generateCurrencyOption(Currency currency, StringBuilder sb) {
+        sb.append(currency.getCurrencyCode());
+    }
+
+    private static void parseCurrencyOption(CharSequence content, MacroProps macros) {
+        String currencyCode = content.subSequence(0, content.length()).toString();
+        try {
+            macros.unit = Currency.getInstance(currencyCode);
+        } catch (IllegalArgumentException e) {
+            throw new SkeletonSyntaxException("Invalid currency", content, e);
+        }
+    }
+
+    private static void parseMeasureUnitOption(CharSequence content, MacroProps macros) {
+        // NOTE: The category (type) of the unit is guaranteed to be a valid subtag (alphanumeric)
+        // http://unicode.org/reports/tr35/#Validity_Data
+        int firstHyphen = 0;
+        while (firstHyphen < content.length() && content.charAt(firstHyphen) != '-') {
+            firstHyphen++;
+        }
+        if (firstHyphen == content.length()) {
+            throw new SkeletonSyntaxException("Invalid measure unit option", content);
+        }
+        String type = content.subSequence(0, firstHyphen).toString();
+        String subType = content.subSequence(firstHyphen + 1, content.length()).toString();
+        Set<MeasureUnit> units = MeasureUnit.getAvailable(type);
+        for (MeasureUnit unit : units) {
+            if (subType.equals(unit.getSubtype())) {
+                macros.unit = unit;
+                return;
+            }
+        }
+        throw new SkeletonSyntaxException("Unknown measure unit", content);
+    }
+
+    private static void generateMeasureUnitOption(MeasureUnit unit, StringBuilder sb) {
+        sb.append(unit.getType() + "-" + unit.getSubtype());
+    }
+
+    private static void parseMeasurePerUnitOption(CharSequence content, MacroProps macros) {
+        // A little bit of a hack: safe the current unit (numerator), call the main measure unit parsing
+        // code, put back the numerator unit, and put the new unit into per-unit.
+        MeasureUnit numerator = macros.unit;
+        parseMeasureUnitOption(content, macros);
+        macros.perUnit = macros.unit;
+        macros.unit = numerator;
+    }
+
     private static void parseFractionStem(CharSequence content, MacroProps macros) {
         assert content.charAt(0) == '.';
         int offset = 1;
@@ -412,7 +642,7 @@ class NumberSkeletonImpl {
         }
         FractionRounder oldRounder = (FractionRounder) macros.rounder;
         // A little bit of a hack: parse the option as a digits stem, and extract the min/max sig from
-        // the new Rounder saved into the macros
+        // the new Rounder saved into the macros.
         parseDigitsStem(content, macros);
         Rounder.SignificantRounderImpl intermediate = (Rounder.SignificantRounderImpl) macros.rounder;
         if (intermediate.maxSig == -1) {
@@ -455,31 +685,134 @@ class NumberSkeletonImpl {
         sb.append(mode.toString());
     }
 
-    private static void parseMeasureUnitOption(CharSequence content, MacroProps macros) {
-        // NOTE: The category (type) of the unit is guaranteed to be a valid subtag (alphanumeric)
-        // http://unicode.org/reports/tr35/#Validity_Data
-        int firstHyphen = 0;
-        while (firstHyphen < content.length() && content.charAt(firstHyphen) != '-') {
-            firstHyphen++;
+    private static void parseIntegerWidthOption(CharSequence content, MacroProps macros) {
+        int offset = 0;
+        int minInt = 0;
+        int maxInt;
+        if (content.charAt(0) == '+') {
+            maxInt = -1;
+            offset++;
+        } else {
+            maxInt = 0;
         }
-        String type = content.subSequence(0, firstHyphen).toString();
-        String subType = content.subSequence(firstHyphen + 1, content.length()).toString();
-        Set<MeasureUnit> units = MeasureUnit.getAvailable(type);
-        for (MeasureUnit unit : units) {
-            if (subType.equals(unit.getSubtype())) {
-                macros.unit = unit;
-                return;
+        for (; offset < content.length(); offset++) {
+            if (content.charAt(offset) == '#') {
+                maxInt++;
+            } else {
+                break;
+            }
+        }
+        if (offset < content.length()) {
+            for (; offset < content.length(); offset++) {
+                if (content.charAt(offset) == '0') {
+                    minInt++;
+                } else {
+                    break;
+                }
             }
         }
-        throw new SkeletonSyntaxException("Unknown unit", content);
+        if (maxInt != -1) {
+            maxInt += minInt;
+        }
+        if (offset < content.length()) {
+            throw new SkeletonSyntaxException("Invalid integer width stem", content);
+        }
+        // Use the public APIs to enforce bounds checking
+        if (maxInt == -1) {
+            macros.integerWidth = IntegerWidth.zeroFillTo(minInt);
+        } else {
+            macros.integerWidth = IntegerWidth.zeroFillTo(minInt).truncateAt(maxInt);
+        }
     }
 
-    private static void generateMeasureUnitOption(MeasureUnit unit, StringBuilder sb) {
-        sb.append(unit.getType() + "-" + unit.getSubtype());
+    private static void generateIntegerWidthOption(int minInt, int maxInt, StringBuilder sb) {
+        if (maxInt == -1) {
+            sb.append('+');
+        } else {
+            appendMultiple(sb, '#', maxInt - minInt);
+        }
+        appendMultiple(sb, '0', minInt);
+    }
+
+    private static void parseNumberingSystemOption(CharSequence content, MacroProps macros) {
+        String nsName = content.subSequence(0, content.length()).toString();
+        NumberingSystem ns = NumberingSystem.getInstanceByName(nsName);
+        if (ns == null) {
+            throw new SkeletonSyntaxException("Unknown numbering system", content);
+        }
+        macros.symbols = ns;
+    }
+
+    private static void generateNumberingSystemOption(NumberingSystem ns, StringBuilder sb) {
+        sb.append(ns.getName());
     }
 
     /////
 
+    private static void generateNotationValue(MacroProps macros, StringBuilder sb) {
+        // Check for literals
+        String literal = skeletonData.valueToStem(macros.notation);
+        if (literal != null) {
+            sb.append(literal);
+            return;
+        }
+
+        // Generate the stem
+        if (macros.notation instanceof CompactNotation) {
+            // Compact notation generated from custom data (not supported in skeleton)
+            // The other compact notations are literals
+        } else if (macros.notation instanceof ScientificNotation) {
+            ScientificNotation impl = (ScientificNotation) macros.notation;
+            if (impl.engineeringInterval == 3) {
+                sb.append("engineering");
+            } else {
+                sb.append("scientific");
+            }
+            if (impl.minExponentDigits > 1) {
+                sb.append('/');
+                generateExponentWidthOption(impl.minExponentDigits, -1, sb);
+            }
+            if (impl.exponentSignDisplay != SignDisplay.AUTO) {
+                sb.append('/');
+                sb.append(skeletonData.valueToStem(impl.exponentSignDisplay));
+            }
+        } else {
+            assert macros.notation instanceof SimpleNotation;
+            sb.append("notation-simple");
+        }
+    }
+
+    private static void generateUnitValue(MacroProps macros, StringBuilder sb) {
+        // Check for literals
+        String literal = skeletonData.valueToStem(macros.unit);
+        if (literal != null) {
+            sb.append(literal);
+            return;
+        }
+
+        // Generate the stem
+        if (macros.unit instanceof Currency) {
+            sb.append("currency/");
+            generateCurrencyOption((Currency) macros.unit, sb);
+        } else if (macros.unit instanceof NoUnit) {
+            // This should be taken care of by the literals.
+            assert false;
+        } else {
+            sb.append("measure-unit/");
+            generateMeasureUnitOption(macros.unit, sb);
+        }
+    }
+
+    private static void generatePerUnitValue(MacroProps macros, StringBuilder sb) {
+        // Per-units are currently expected to be only MeasureUnits.
+        if (macros.unit instanceof Currency || macros.unit instanceof NoUnit) {
+            assert false;
+        } else {
+            sb.append("per-measure-unit/");
+            generateMeasureUnitOption(macros.perUnit, sb);
+        }
+    }
+
     private static void generateRoundingValue(MacroProps macros, StringBuilder sb) {
         // Check for literals
         String literal = skeletonData.valueToStem(macros.rounder);
@@ -527,30 +860,39 @@ class NumberSkeletonImpl {
         }
     }
 
-    private static void generateUnitValue(MacroProps macros, StringBuilder sb) {
-        // Check for literals
-        String literal = skeletonData.valueToStem(macros.unit);
-        if (literal != null) {
-            sb.append(literal);
-            return;
-        }
+    private static void generateGroupingValue(MacroProps macros, StringBuilder sb) {
+        appendExpectedLiteral(macros.grouping, sb);
+    }
 
-        // Generate the stem
-        if (macros.unit instanceof Currency) {
-            // TODO
-        } else if (macros.unit instanceof NoUnit) {
-            // TODO
+    private static void generateIntegerWidthValue(MacroProps macros, StringBuilder sb) {
+        sb.append("integer-width/");
+        generateIntegerWidthOption(macros.integerWidth.minInt, macros.integerWidth.maxInt, sb);
+    }
+
+    private static void generateSymbolsValue(MacroProps macros, StringBuilder sb) {
+        if (macros.symbols instanceof NumberingSystem) {
+            NumberingSystem ns = (NumberingSystem) macros.symbols;
+            if (ns.getName().equals("latn")) {
+                sb.append("latin");
+            } else {
+                sb.append("numbering-system/");
+                generateNumberingSystemOption(ns, sb);
+            }
         } else {
-            sb.append("measure-unit/");
-            generateMeasureUnitOption(macros.unit, sb);
+            // DecimalFormatSymbols (not supported in skeleton)
         }
     }
 
     private static void generateUnitWidthValue(MacroProps macros, StringBuilder sb) {
-        // There should be a literal.
-        String literal = skeletonData.valueToStem(macros.unitWidth);
-        assert literal != null;
-        sb.append(literal);
+        appendExpectedLiteral(macros.unitWidth, sb);
+    }
+
+    private static void generateSignValue(MacroProps macros, StringBuilder sb) {
+        appendExpectedLiteral(macros.sign, sb);
+    }
+
+    private static void generateDecimalValue(MacroProps macros, StringBuilder sb) {
+        appendExpectedLiteral(macros.decimal, sb);
     }
 
     /////
@@ -566,4 +908,10 @@ class NumberSkeletonImpl {
             sb.appendCodePoint(cp);
         }
     }
+
+    private static void appendExpectedLiteral(Object value, StringBuilder sb) {
+        String literal = skeletonData.valueToStem(value);
+        assert literal != null;
+        sb.append(literal);
+    }
 }
index 49569d73d4b739ca874f7615f1fd5dc0a2507ad1..050e60dfd577c6fcb019c9d8992ada29188b8adc 100644 (file)
@@ -75,7 +75,7 @@ public class NumberFormatterApiTest {
 
         assertFormatDescendingBig(
                 "Big Simple",
-                "",
+                "simple-notation",
                 NumberFormatter.with().notation(Notation.simple()),
                 ULocale.ENGLISH,
                 "87,650,000",
@@ -101,7 +101,7 @@ public class NumberFormatterApiTest {
     public void notationScientific() {
         assertFormatDescending(
                 "Scientific",
-                "E",
+                "scientific",
                 NumberFormatter.with().notation(Notation.scientific()),
                 ULocale.ENGLISH,
                 "8.765E4",
@@ -116,7 +116,7 @@ public class NumberFormatterApiTest {
 
         assertFormatDescending(
                 "Engineering",
-                "E3",
+                "engineering",
                 NumberFormatter.with().notation(Notation.engineering()),
                 ULocale.ENGLISH,
                 "87.65E3",
@@ -131,7 +131,7 @@ public class NumberFormatterApiTest {
 
         assertFormatDescending(
                 "Scientific sign always shown",
-                "E+",
+                "scientific/sign-always",
                 NumberFormatter.with().notation(Notation.scientific().withExponentSignDisplay(SignDisplay.ALWAYS)),
                 ULocale.ENGLISH,
                 "8.765E+4",
@@ -146,7 +146,7 @@ public class NumberFormatterApiTest {
 
         assertFormatDescending(
                 "Scientific min exponent digits",
-                "E00",
+                "scientific/+ee",
                 NumberFormatter.with().notation(Notation.scientific().withMinExponentDigits(2)),
                 ULocale.ENGLISH,
                 "8.765E04",
@@ -161,7 +161,7 @@ public class NumberFormatterApiTest {
 
         assertFormatSingle(
                 "Scientific Negative",
-                "E",
+                "scientific",
                 NumberFormatter.with().notation(Notation.scientific()),
                 ULocale.ENGLISH,
                 -1000000,
@@ -172,7 +172,7 @@ public class NumberFormatterApiTest {
     public void notationCompact() {
         assertFormatDescendingBig(
                 "Compact Short",
-                "C",
+                "compact-short",
                 NumberFormatter.with().notation(Notation.compactShort()),
                 ULocale.ENGLISH,
                 "88M",
@@ -187,7 +187,7 @@ public class NumberFormatterApiTest {
 
         assertFormatDescendingBig(
                 "Compact Long",
-                "CC",
+                "compact-long",
                 NumberFormatter.with().notation(Notation.compactLong()),
                 ULocale.ENGLISH,
                 "88 million",
@@ -202,7 +202,7 @@ public class NumberFormatterApiTest {
 
         assertFormatDescending(
                 "Compact Short Currency",
-                "C $USD",
+                "compact-short currency/USD",
                 NumberFormatter.with().notation(Notation.compactShort()).unit(USD),
                 ULocale.ENGLISH,
                 "$88K",
@@ -217,7 +217,7 @@ public class NumberFormatterApiTest {
 
         assertFormatDescending(
                 "Compact Short with ISO Currency",
-                "C $USD unit-width=ISO_CODE",
+                "compact-short currency/USD unit-width-iso-code",
                 NumberFormatter.with().notation(Notation.compactShort()).unit(USD).unitWidth(UnitWidth.ISO_CODE),
                 ULocale.ENGLISH,
                 "USD 88K",
@@ -232,7 +232,7 @@ public class NumberFormatterApiTest {
 
         assertFormatDescending(
                 "Compact Short with Long Name Currency",
-                "C $USD unit-width=FULL_NAME",
+                "compact-short currency/USD unit-width-full-name",
                 NumberFormatter.with().notation(Notation.compactShort()).unit(USD).unitWidth(UnitWidth.FULL_NAME),
                 ULocale.ENGLISH,
                 "88K US dollars",
@@ -249,7 +249,7 @@ public class NumberFormatterApiTest {
         // This test case should be fixed when proper compact long currency patterns are added.
         assertFormatDescending(
                 "Compact Long Currency",
-                "CC $USD",
+                "compact-long currency/USD",
                 NumberFormatter.with().notation(Notation.compactLong()).unit(USD),
                 ULocale.ENGLISH,
                 "$88K", // should be something like "$88 thousand"
@@ -266,7 +266,7 @@ public class NumberFormatterApiTest {
         // This test case should be fixed when proper compact long currency patterns are added.
         assertFormatDescending(
                 "Compact Long with ISO Currency",
-                "CC $USD unit-width=ISO_CODE",
+                "compact-long currency/USD unit-width-iso-code",
                 NumberFormatter.with().notation(Notation.compactLong()).unit(USD).unitWidth(UnitWidth.ISO_CODE),
                 ULocale.ENGLISH,
                 "USD 88K", // should be something like "USD 88 thousand"
@@ -282,7 +282,7 @@ public class NumberFormatterApiTest {
         // TODO: This behavior could be improved and should be revisited.
         assertFormatDescending(
                 "Compact Long with Long Name Currency",
-                "CC $USD unit-width=FULL_NAME",
+                "compact-long currency/USD unit-width-full-name",
                 NumberFormatter.with().notation(Notation.compactLong()).unit(USD).unitWidth(UnitWidth.FULL_NAME),
                 ULocale.ENGLISH,
                 "88 thousand US dollars",
@@ -297,7 +297,7 @@ public class NumberFormatterApiTest {
 
         assertFormatSingle(
                 "Compact Plural One",
-                "CC",
+                "compact-long",
                 NumberFormatter.with().notation(Notation.compactLong()),
                 ULocale.forLanguageTag("es"),
                 1000000,
@@ -305,7 +305,7 @@ public class NumberFormatterApiTest {
 
         assertFormatSingle(
                 "Compact Plural Other",
-                "CC",
+                "compact-long",
                 NumberFormatter.with().notation(Notation.compactLong()),
                 ULocale.forLanguageTag("es"),
                 2000000,
@@ -313,7 +313,7 @@ public class NumberFormatterApiTest {
 
         assertFormatSingle(
                 "Compact with Negative Sign",
-                "C",
+                "compact-short",
                 NumberFormatter.with().notation(Notation.compactShort()),
                 ULocale.ENGLISH,
                 -9876543.21,
@@ -321,7 +321,7 @@ public class NumberFormatterApiTest {
 
         assertFormatSingle(
                 "Compact Rounding",
-                "C",
+                "compact-short",
                 NumberFormatter.with().notation(Notation.compactShort()),
                 ULocale.ENGLISH,
                 990000,
@@ -329,7 +329,7 @@ public class NumberFormatterApiTest {
 
         assertFormatSingle(
                 "Compact Rounding",
-                "C",
+                "compact-short",
                 NumberFormatter.with().notation(Notation.compactShort()),
                 ULocale.ENGLISH,
                 999000,
@@ -337,7 +337,7 @@ public class NumberFormatterApiTest {
 
         assertFormatSingle(
                 "Compact Rounding",
-                "C",
+                "compact-short",
                 NumberFormatter.with().notation(Notation.compactShort()),
                 ULocale.ENGLISH,
                 999900,
@@ -345,7 +345,7 @@ public class NumberFormatterApiTest {
 
         assertFormatSingle(
                 "Compact Rounding",
-                "C",
+                "compact-short",
                 NumberFormatter.with().notation(Notation.compactShort()),
                 ULocale.ENGLISH,
                 9900000,
@@ -353,7 +353,7 @@ public class NumberFormatterApiTest {
 
         assertFormatSingle(
                 "Compact Rounding",
-                "C",
+                "compact-short",
                 NumberFormatter.with().notation(Notation.compactShort()),
                 ULocale.ENGLISH,
                 9990000,
@@ -366,7 +366,7 @@ public class NumberFormatterApiTest {
         compactCustomData.put("1000", entry);
         assertFormatSingle(
                 "Compact Somali No Figure",
-                "",
+                null, // feature not supported in skeleton
                 NumberFormatter.with().notation(CompactNotation.forCustomData(compactCustomData)),
                 ULocale.ENGLISH,
                 1000,
@@ -423,7 +423,7 @@ public class NumberFormatterApiTest {
 
         assertFormatSingleMeasure(
                 "Meters with Measure Input",
-                "unit-width=FULL_NAME",
+                "unit-width-full-name",
                 NumberFormatter.with().unitWidth(UnitWidth.FULL_NAME),
                 ULocale.ENGLISH,
                 new Measure(5.43, MeasureUnit.METER),
@@ -431,7 +431,7 @@ public class NumberFormatterApiTest {
 
         assertFormatSingleMeasure(
                 "Measure format method takes precedence over fluent chain",
-                "U:length:meter",
+                "measure-unit/length-meter",
                 NumberFormatter.with().unit(MeasureUnit.METER),
                 ULocale.ENGLISH,
                 new Measure(5.43, USD),
@@ -439,7 +439,7 @@ public class NumberFormatterApiTest {
 
         assertFormatSingle(
                 "Meters with Negative Sign",
-                "U:length:meter",
+                "measure-unit/length-meter",
                 NumberFormatter.with().unit(MeasureUnit.METER),
                 ULocale.ENGLISH,
                 -9876543.21,
@@ -448,7 +448,7 @@ public class NumberFormatterApiTest {
         // The locale string "सान" appears only in brx.txt:
         assertFormatSingle(
                 "Interesting Data Fallback 1",
-                "U:duration:day unit-width=FULL_NAME",
+                "measure-unit/duration-day unit-width-full-name",
                 NumberFormatter.with().unit(MeasureUnit.DAY).unitWidth(UnitWidth.FULL_NAME),
                 ULocale.forLanguageTag("brx"),
                 5.43,
@@ -457,7 +457,7 @@ public class NumberFormatterApiTest {
         // Requires following the alias from unitsNarrow to unitsShort:
         assertFormatSingle(
                 "Interesting Data Fallback 2",
-                "U:duration:day unit-width=NARROW",
+                "measure-unit/duration-day unit-width-narrow",
                 NumberFormatter.with().unit(MeasureUnit.DAY).unitWidth(UnitWidth.NARROW),
                 ULocale.forLanguageTag("brx"),
                 5.43,
@@ -467,7 +467,7 @@ public class NumberFormatterApiTest {
         // requiring fallback to the root.
         assertFormatSingle(
                 "Interesting Data Fallback 3",
-                "U:area:square-meter unit-width=NARROW",
+                "measure-unit/area-square-meter unit-width-narrow",
                 NumberFormatter.with().unit(MeasureUnit.SQUARE_METER).unitWidth(UnitWidth.NARROW),
                 ULocale.forLanguageTag("en-GB"),
                 5.43,
@@ -477,7 +477,7 @@ public class NumberFormatterApiTest {
         // NOTE: This example is in the documentation.
         assertFormatSingle(
                 "MeasureUnit Difference between Narrow and Short (Narrow Version)",
-                "",
+                "measure-unit/temperature-fahrenheit unit-width-narrow",
                 NumberFormatter.with().unit(MeasureUnit.FAHRENHEIT).unitWidth(UnitWidth.NARROW),
                 ULocale.forLanguageTag("es-US"),
                 5.43,
@@ -485,7 +485,7 @@ public class NumberFormatterApiTest {
 
         assertFormatSingle(
                 "MeasureUnit Difference between Narrow and Short (Short Version)",
-                "",
+                "measure-unit/temperature-fahrenheit unit-width-short",
                 NumberFormatter.with().unit(MeasureUnit.FAHRENHEIT).unitWidth(UnitWidth.SHORT),
                 ULocale.forLanguageTag("es-US"),
                 5.43,
@@ -493,7 +493,7 @@ public class NumberFormatterApiTest {
 
         assertFormatSingle(
                 "MeasureUnit form without {0} in CLDR pattern",
-                "",
+                "measure-unit/temperature-kelvin unit-width-full-name",
                 NumberFormatter.with().unit(MeasureUnit.KELVIN).unitWidth(UnitWidth.FULL_NAME),
                 ULocale.forLanguageTag("es-MX"),
                 1,
@@ -501,9 +501,9 @@ public class NumberFormatterApiTest {
 
         assertFormatSingle(
                 "MeasureUnit form without {0} in CLDR pattern and wide base form",
-                "",
+                "measure-unit/temperature-kelvin .0000000000 unit-width-full-name",
                 NumberFormatter.with()
-                    .rounding(Rounder.fixedFraction(20))
+                    .rounding(Rounder.fixedFraction(10))
                     .unit(MeasureUnit.KELVIN)
                     .unitWidth(UnitWidth.FULL_NAME),
                 ULocale.forLanguageTag("es-MX"),
@@ -515,7 +515,7 @@ public class NumberFormatterApiTest {
     public void unitCompoundMeasure() {
         assertFormatDescending(
                 "Meters Per Second Short (unit that simplifies)",
-                "",
+                "measure-unit/length-meter per-measure-unit/duration-second",
                 NumberFormatter.with().unit(MeasureUnit.METER).perUnit(MeasureUnit.SECOND),
                 ULocale.ENGLISH,
                 "87,650 m/s",
@@ -530,7 +530,7 @@ public class NumberFormatterApiTest {
 
         assertFormatDescending(
                 "Pounds Per Square Mile Short (secondary unit has per-format)",
-                "",
+                "measure-unit/mass-pound per-measure-unit/area-square-mile",
                 NumberFormatter.with().unit(MeasureUnit.POUND).perUnit(MeasureUnit.SQUARE_MILE),
                 ULocale.ENGLISH,
                 "87,650 lb/mi²",
@@ -545,7 +545,7 @@ public class NumberFormatterApiTest {
 
         assertFormatDescending(
                 "Joules Per Furlong Short (unit with no simplifications or special patterns)",
-                "",
+                "measure-unit/energy-joule per-measure-unit/length-furlong",
                 NumberFormatter.with().unit(MeasureUnit.JOULE).perUnit(MeasureUnit.FURLONG),
                 ULocale.ENGLISH,
                 "87,650 J/fur",
@@ -563,7 +563,7 @@ public class NumberFormatterApiTest {
     public void unitCurrency() {
         assertFormatDescending(
                 "Currency",
-                "$GBP",
+                "currency/GBP",
                 NumberFormatter.with().unit(GBP),
                 ULocale.ENGLISH,
                 "£87,650.00",
@@ -578,7 +578,7 @@ public class NumberFormatterApiTest {
 
         assertFormatDescending(
                 "Currency ISO",
-                "$GBP unit-width=ISO_CODE",
+                "currency/GBP unit-width-iso-code",
                 NumberFormatter.with().unit(GBP).unitWidth(UnitWidth.ISO_CODE),
                 ULocale.ENGLISH,
                 "GBP 87,650.00",
@@ -593,7 +593,7 @@ public class NumberFormatterApiTest {
 
         assertFormatDescending(
                 "Currency Long Name",
-                "$GBP unit-width=FULL_NAME",
+                "currency/GBP unit-width-full-name",
                 NumberFormatter.with().unit(GBP).unitWidth(UnitWidth.FULL_NAME),
                 ULocale.ENGLISH,
                 "87,650.00 British pounds",
@@ -608,7 +608,7 @@ public class NumberFormatterApiTest {
 
         assertFormatDescending(
                 "Currency Hidden",
-                "$GBP unit-width=HIDDEN",
+                "currency/GBP unit-width-hidden",
                 NumberFormatter.with().unit(GBP).unitWidth(UnitWidth.HIDDEN),
                 ULocale.ENGLISH,
                 "87,650.00",
@@ -631,7 +631,7 @@ public class NumberFormatterApiTest {
 
         assertFormatSingle(
                 "Currency Long Name from Pattern Syntax",
-                "$GBP F0 grouping=none integer-width=1- symbols=loc:en sign=AUTO decimal=AUTO",
+                null,
                 NumberFormatter.fromDecimalFormat(
                         PatternStringParser.parseToProperties("0 ¤¤¤"),
                         DecimalFormatSymbols.getInstance(ULocale.ENGLISH),
@@ -642,7 +642,7 @@ public class NumberFormatterApiTest {
 
         assertFormatSingle(
                 "Currency with Negative Sign",
-                "$GBP",
+                "currency/GBP",
                 NumberFormatter.with().unit(GBP),
                 ULocale.ENGLISH,
                 -9876543.21,
@@ -652,7 +652,7 @@ public class NumberFormatterApiTest {
         // NOTE: This example is in the documentation.
         assertFormatSingle(
                 "Currency Difference between Narrow and Short (Narrow Version)",
-                "",
+                "currency/USD unit-width-narrow",
                 NumberFormatter.with().unit(USD).unitWidth(UnitWidth.NARROW),
                 ULocale.forLanguageTag("en-CA"),
                 5.43,
@@ -660,7 +660,7 @@ public class NumberFormatterApiTest {
 
         assertFormatSingle(
                 "Currency Difference between Narrow and Short (Short Version)",
-                "",
+                "currency/USD unit-width-short",
                 NumberFormatter.with().unit(USD).unitWidth(UnitWidth.SHORT),
                 ULocale.forLanguageTag("en-CA"),
                 5.43,
@@ -668,7 +668,7 @@ public class NumberFormatterApiTest {
 
         assertFormatSingle(
                 "Currency-dependent format (Control)",
-                "",
+                "currency/USD unit-width-short",
                 NumberFormatter.with().unit(USD).unitWidth(UnitWidth.SHORT),
                 ULocale.forLanguageTag("ca"),
                 444444.55,
@@ -676,7 +676,7 @@ public class NumberFormatterApiTest {
 
         assertFormatSingle(
                 "Currency-dependent format (Test)",
-                "",
+                "currency/ESP unit-width-short",
                 NumberFormatter.with().unit(ESP).unitWidth(UnitWidth.SHORT),
                 ULocale.forLanguageTag("ca"),
                 444444.55,
@@ -684,7 +684,7 @@ public class NumberFormatterApiTest {
 
         assertFormatSingle(
                 "Currency-dependent symbols (Control)",
-                "",
+                "currency/USD unit-width-short",
                 NumberFormatter.with().unit(USD).unitWidth(UnitWidth.SHORT),
                 ULocale.forLanguageTag("pt-PT"),
                 444444.55,
@@ -694,7 +694,7 @@ public class NumberFormatterApiTest {
         // width space), and they set the decimal separator to the $ symbol.
         assertFormatSingle(
                 "Currency-dependent symbols (Test)",
-                "",
+                "currency/PTE unit-width-short",
                 NumberFormatter.with().unit(PTE).unitWidth(UnitWidth.SHORT),
                 ULocale.forLanguageTag("pt-PT"),
                 444444.55,
@@ -702,7 +702,7 @@ public class NumberFormatterApiTest {
 
         assertFormatSingle(
                 "Currency-dependent symbols (Test)",
-                "",
+                "currency/PTE unit-width-narrow",
                 NumberFormatter.with().unit(PTE).unitWidth(UnitWidth.NARROW),
                 ULocale.forLanguageTag("pt-PT"),
                 444444.55,
@@ -710,7 +710,7 @@ public class NumberFormatterApiTest {
 
         assertFormatSingle(
                 "Currency-dependent symbols (Test)",
-                "",
+                "currency/PTE unit-width-iso-code",
                 NumberFormatter.with().unit(PTE).unitWidth(UnitWidth.ISO_CODE),
                 ULocale.forLanguageTag("pt-PT"),
                 444444.55,
@@ -721,7 +721,7 @@ public class NumberFormatterApiTest {
     public void unitPercent() {
         assertFormatDescending(
                 "Percent",
-                "%",
+                "percent",
                 NumberFormatter.with().unit(NoUnit.PERCENT),
                 ULocale.ENGLISH,
                 "87,650%",
@@ -736,7 +736,7 @@ public class NumberFormatterApiTest {
 
         assertFormatDescending(
                 "Permille",
-                "%%",
+                "permille",
                 NumberFormatter.with().unit(NoUnit.PERMILLE),
                 ULocale.ENGLISH,
                 "87,650‰",
@@ -751,7 +751,7 @@ public class NumberFormatterApiTest {
 
         assertFormatSingle(
                 "NoUnit Base",
-                "B",
+                "base-unit",
                 NumberFormatter.with().unit(NoUnit.BASE),
                 ULocale.ENGLISH,
                 51423,
@@ -759,7 +759,7 @@ public class NumberFormatterApiTest {
 
         assertFormatSingle(
                 "Percent with Negative Sign",
-                "%",
+                "percent",
                 NumberFormatter.with().unit(NoUnit.PERCENT),
                 ULocale.ENGLISH,
                 -98.7654321,
@@ -1023,7 +1023,7 @@ public class NumberFormatterApiTest {
 
         assertFormatDescending(
                 "Currency Standard",
-                "round-currency-standard",
+                "currency/CZK round-currency-standard",
                 NumberFormatter.with().rounding(Rounder.currency(CurrencyUsage.STANDARD)).unit(CZK),
                 ULocale.ENGLISH,
                 "CZK 87,650.00",
@@ -1038,7 +1038,7 @@ public class NumberFormatterApiTest {
 
         assertFormatDescending(
                 "Currency Cash",
-                "round-currency-cash",
+                "currency/CZK round-currency-cash",
                 NumberFormatter.with().rounding(Rounder.currency(CurrencyUsage.CASH)).unit(CZK),
                 ULocale.ENGLISH,
                 "CZK 87,650",
@@ -1053,7 +1053,7 @@ public class NumberFormatterApiTest {
 
         assertFormatDescending(
                 "Currency Cash with Nickel Rounding",
-                "round-currency-cash",
+                "currency/CAD round-currency-cash",
                 NumberFormatter.with().rounding(Rounder.currency(CurrencyUsage.CASH)).unit(CAD),
                 ULocale.ENGLISH,
                 "CA$87,650.00",
@@ -1068,7 +1068,7 @@ public class NumberFormatterApiTest {
 
         assertFormatDescending(
                 "Currency not in top-level fluent chain",
-                "round-currency-cash/CZK",
+                "round-integer", // calling .withCurrency() applies currency rounding rules immediately
                 NumberFormatter.with().rounding(Rounder.currency(CurrencyUsage.CASH).withCurrency(CZK)),
                 ULocale.ENGLISH,
                 "87,650",
@@ -1083,8 +1083,8 @@ public class NumberFormatterApiTest {
 
         // NOTE: Other tests cover the behavior of the other rounding modes.
         assertFormatDescending(
+                "Rounding Mode CEILING",
                 "round-integer/CEILING",
-                "",
                 NumberFormatter.with().rounding(Rounder.integer().withMode(RoundingMode.CEILING)),
                 ULocale.ENGLISH,
                 "87,650",
@@ -1102,7 +1102,7 @@ public class NumberFormatterApiTest {
     public void grouping() {
         assertFormatDescendingBig(
                 "Western Grouping",
-                "grouping=defaults",
+                "group-auto",
                 NumberFormatter.with().grouping(GroupingStrategy.AUTO),
                 ULocale.ENGLISH,
                 "87,650,000",
@@ -1117,7 +1117,7 @@ public class NumberFormatterApiTest {
 
         assertFormatDescendingBig(
                 "Indic Grouping",
-                "grouping=defaults",
+                "group-auto",
                 NumberFormatter.with().grouping(GroupingStrategy.AUTO),
                 new ULocale("en-IN"),
                 "8,76,50,000",
@@ -1132,7 +1132,7 @@ public class NumberFormatterApiTest {
 
         assertFormatDescendingBig(
                 "Western Grouping, Min 2",
-                "grouping=min2",
+                "group-min2",
                 NumberFormatter.with().grouping(GroupingStrategy.MIN2),
                 ULocale.ENGLISH,
                 "87,650,000",
@@ -1147,7 +1147,7 @@ public class NumberFormatterApiTest {
 
         assertFormatDescendingBig(
                 "Indic Grouping, Min 2",
-                "grouping=min2",
+                "group-min2",
                 NumberFormatter.with().grouping(GroupingStrategy.MIN2),
                 new ULocale("en-IN"),
                 "8,76,50,000",
@@ -1162,7 +1162,7 @@ public class NumberFormatterApiTest {
 
         assertFormatDescendingBig(
                 "No Grouping",
-                "grouping=none",
+                "group-off",
                 NumberFormatter.with().grouping(GroupingStrategy.OFF),
                 new ULocale("en-IN"),
                 "87650000",
@@ -1177,7 +1177,7 @@ public class NumberFormatterApiTest {
 
         assertFormatDescendingBig(
                 "Indic locale with THOUSANDS grouping",
-                "",
+                "group-thousands",
                 NumberFormatter.with().grouping(GroupingStrategy.THOUSANDS),
                 new ULocale("en-IN"),
                 "87,650,000",
@@ -1194,7 +1194,7 @@ public class NumberFormatterApiTest {
         // If this test breaks due to data changes, find another locale that has minimumGroupingDigits.
         assertFormatDescendingBig(
                 "Hungarian Grouping",
-                "",
+                "group-auto",
                 NumberFormatter.with().grouping(GroupingStrategy.AUTO),
                 new ULocale("hu"),
                 "87 650 000",
@@ -1209,7 +1209,7 @@ public class NumberFormatterApiTest {
 
         assertFormatDescendingBig(
                 "Hungarian Grouping, Min 2",
-                "",
+                "group-min2",
                 NumberFormatter.with().grouping(GroupingStrategy.MIN2),
                 new ULocale("hu"),
                 "87 650 000",
@@ -1224,7 +1224,7 @@ public class NumberFormatterApiTest {
 
         assertFormatDescendingBig(
                 "Hungarian Grouping, Always",
-                "",
+                "group-on-aligned",
                 NumberFormatter.with().grouping(GroupingStrategy.ON_ALIGNED),
                 new ULocale("hu"),
                 "87 650 000",
@@ -1241,7 +1241,7 @@ public class NumberFormatterApiTest {
         // If this test breaks due to data changes, find another locale that has no default grouping.
         assertFormatDescendingBig(
                 "Bulgarian Currency Grouping",
-                "",
+                "currency/USD group-auto",
                 NumberFormatter.with().grouping(GroupingStrategy.AUTO).unit(USD),
                 new ULocale("bg"),
                 "87650000,00 щ.д.",
@@ -1256,7 +1256,7 @@ public class NumberFormatterApiTest {
 
         assertFormatDescendingBig(
                 "Bulgarian Currency Grouping, Always",
-                "",
+                "currency/USD group-on-aligned",
                 NumberFormatter.with().grouping(GroupingStrategy.ON_ALIGNED).unit(USD),
                 new ULocale("bg"),
                 "87 650 000,00 щ.д.",
@@ -1273,7 +1273,7 @@ public class NumberFormatterApiTest {
         macros.grouping = Grouper.getInstance((short) 4, (short) 1, (short) 3);
         assertFormatDescendingBig(
                 "Custom Grouping via Internal API",
-                "",
+                null,
                 NumberFormatter.with().macros(macros),
                 ULocale.ENGLISH,
                 "8,7,6,5,0000",
@@ -1291,7 +1291,7 @@ public class NumberFormatterApiTest {
     public void padding() {
         assertFormatDescending(
                 "Padding",
-                "",
+                null,
                 NumberFormatter.with().padding(Padder.none()),
                 ULocale.ENGLISH,
                 "87,650",
@@ -1306,7 +1306,7 @@ public class NumberFormatterApiTest {
 
         assertFormatDescending(
                 "Padding",
-                "",
+                null,
                 NumberFormatter.with().padding(Padder.codePoints('*', 8, PadPosition.AFTER_PREFIX)),
                 ULocale.ENGLISH,
                 "**87,650",
@@ -1321,7 +1321,7 @@ public class NumberFormatterApiTest {
 
         assertFormatDescending(
                 "Padding with code points",
-                "",
+                null,
                 NumberFormatter.with().padding(Padder.codePoints(0x101E4, 8, PadPosition.AFTER_PREFIX)),
                 ULocale.ENGLISH,
                 "𐇤𐇤87,650",
@@ -1336,7 +1336,7 @@ public class NumberFormatterApiTest {
 
         assertFormatDescending(
                 "Padding with wide digits",
-                "symbols=ns:mathsanb",
+                null,
                 NumberFormatter.with().padding(Padder.codePoints('*', 8, PadPosition.AFTER_PREFIX))
                         .symbols(NumberingSystem.getInstanceByName("mathsanb")),
                 ULocale.ENGLISH,
@@ -1352,7 +1352,7 @@ public class NumberFormatterApiTest {
 
         assertFormatDescending(
                 "Padding with currency spacing",
-                "$GBP unit-width=ISO_CODE",
+                null,
                 NumberFormatter.with().padding(Padder.codePoints('*', 10, PadPosition.AFTER_PREFIX)).unit(GBP)
                         .unitWidth(UnitWidth.ISO_CODE),
                 ULocale.ENGLISH,
@@ -1368,7 +1368,7 @@ public class NumberFormatterApiTest {
 
         assertFormatSingle(
                 "Pad Before Prefix",
-                "",
+                null,
                 NumberFormatter.with().padding(Padder.codePoints('*', 8, PadPosition.BEFORE_PREFIX)),
                 ULocale.ENGLISH,
                 -88.88,
@@ -1376,7 +1376,7 @@ public class NumberFormatterApiTest {
 
         assertFormatSingle(
                 "Pad After Prefix",
-                "",
+                null,
                 NumberFormatter.with().padding(Padder.codePoints('*', 8, PadPosition.AFTER_PREFIX)),
                 ULocale.ENGLISH,
                 -88.88,
@@ -1384,7 +1384,7 @@ public class NumberFormatterApiTest {
 
         assertFormatSingle(
                 "Pad Before Suffix",
-                "%",
+                null,
                 NumberFormatter.with().padding(Padder.codePoints('*', 8, PadPosition.BEFORE_SUFFIX))
                         .unit(NoUnit.PERCENT),
                 ULocale.ENGLISH,
@@ -1393,7 +1393,7 @@ public class NumberFormatterApiTest {
 
         assertFormatSingle(
                 "Pad After Suffix",
-                "%",
+                null,
                 NumberFormatter.with().padding(Padder.codePoints('*', 8, PadPosition.AFTER_SUFFIX))
                         .unit(NoUnit.PERCENT),
                 ULocale.ENGLISH,
@@ -1402,7 +1402,7 @@ public class NumberFormatterApiTest {
 
         assertFormatSingle(
                 "Currency Spacing with Zero Digit Padding Broken",
-                "$GBP unit-width=ISO_CODE",
+                null,
                 NumberFormatter.with().padding(Padder.codePoints('0', 12, PadPosition.AFTER_PREFIX)).unit(GBP)
                         .unitWidth(UnitWidth.ISO_CODE),
                 ULocale.ENGLISH,
@@ -1414,7 +1414,7 @@ public class NumberFormatterApiTest {
     public void integerWidth() {
         assertFormatDescending(
                 "Integer Width Default",
-                "integer-width=1-",
+                "integer-width/+0",
                 NumberFormatter.with().integerWidth(IntegerWidth.zeroFillTo(1)),
                 ULocale.ENGLISH,
                 "87,650",
@@ -1429,7 +1429,7 @@ public class NumberFormatterApiTest {
 
         assertFormatDescending(
                 "Integer Width Zero Fill 0",
-                "integer-width=0-",
+                "integer-width/+",
                 NumberFormatter.with().integerWidth(IntegerWidth.zeroFillTo(0)),
                 ULocale.ENGLISH,
                 "87,650",
@@ -1444,7 +1444,7 @@ public class NumberFormatterApiTest {
 
         assertFormatDescending(
                 "Integer Width Zero Fill 3",
-                "integer-width=3-",
+                "integer-width/+000",
                 NumberFormatter.with().integerWidth(IntegerWidth.zeroFillTo(3)),
                 ULocale.ENGLISH,
                 "87,650",
@@ -1459,7 +1459,7 @@ public class NumberFormatterApiTest {
 
         assertFormatDescending(
                 "Integer Width Max 3",
-                "integer-width=1-3",
+                "integer-width/##0",
                 NumberFormatter.with().integerWidth(IntegerWidth.zeroFillTo(1).truncateAt(3)),
                 ULocale.ENGLISH,
                 "650",
@@ -1474,7 +1474,7 @@ public class NumberFormatterApiTest {
 
         assertFormatDescending(
                 "Integer Width Fixed 2",
-                "integer-width=2",
+                "integer-width/00",
                 NumberFormatter.with().integerWidth(IntegerWidth.zeroFillTo(2).truncateAt(2)),
                 ULocale.ENGLISH,
                 "50",
@@ -1492,7 +1492,7 @@ public class NumberFormatterApiTest {
     public void symbols() {
         assertFormatDescending(
                 "French Symbols with Japanese Data 1",
-                "symbols=loc:fr",
+                null,
                 NumberFormatter.with().symbols(DecimalFormatSymbols.getInstance(ULocale.FRENCH)),
                 ULocale.JAPAN,
                 "87 650",
@@ -1507,7 +1507,7 @@ public class NumberFormatterApiTest {
 
         assertFormatSingle(
                 "French Symbols with Japanese Data 2",
-                "C symbols=loc:fr",
+                null,
                 NumberFormatter.with().notation(Notation.compactShort())
                         .symbols(DecimalFormatSymbols.getInstance(ULocale.FRENCH)),
                 ULocale.JAPAN,
@@ -1516,7 +1516,7 @@ public class NumberFormatterApiTest {
 
         assertFormatDescending(
                 "Latin Numbering System with Arabic Data",
-                "$USD symbols=ns:latn",
+                "currency/USD latin",
                 NumberFormatter.with().symbols(NumberingSystem.LATIN).unit(USD),
                 new ULocale("ar"),
                 "US$ 87,650.00",
@@ -1531,7 +1531,7 @@ public class NumberFormatterApiTest {
 
         assertFormatDescending(
                 "Math Numbering System with French Data",
-                "symbols=ns:mathsanb",
+                "numbering-system/mathsanb",
                 NumberFormatter.with().symbols(NumberingSystem.getInstanceByName("mathsanb")),
                 ULocale.FRENCH,
                 "𝟴𝟳 𝟲𝟱𝟬",
@@ -1546,7 +1546,7 @@ public class NumberFormatterApiTest {
 
         assertFormatSingle(
                 "Swiss Symbols (used in documentation)",
-                "symbols=loc:de_CH",
+                null,
                 NumberFormatter.with().symbols(DecimalFormatSymbols.getInstance(new ULocale("de-CH"))),
                 ULocale.ENGLISH,
                 12345.67,
@@ -1554,7 +1554,7 @@ public class NumberFormatterApiTest {
 
         assertFormatSingle(
                 "Myanmar Symbols (used in documentation)",
-                "symbols=loc:my_MY",
+                null,
                 NumberFormatter.with().symbols(DecimalFormatSymbols.getInstance(new ULocale("my_MY"))),
                 ULocale.ENGLISH,
                 12345.67,
@@ -1564,7 +1564,7 @@ public class NumberFormatterApiTest {
 
         assertFormatSingle(
                 "Currency symbol should precede number in ar with NS latn",
-                "",
+                "currency/USD latin",
                 NumberFormatter.with().symbols(NumberingSystem.LATIN).unit(USD),
                 new ULocale("ar"),
                 12345.67,
@@ -1572,7 +1572,7 @@ public class NumberFormatterApiTest {
 
         assertFormatSingle(
                 "Currency symbol should precede number in ar@numbers=latn",
-                "",
+                "currency/USD",
                 NumberFormatter.with().unit(USD),
                 new ULocale("ar@numbers=latn"),
                 12345.67,
@@ -1580,7 +1580,7 @@ public class NumberFormatterApiTest {
 
         assertFormatSingle(
                 "Currency symbol should follow number in ar-EG with NS arab",
-                "",
+                "currency/USD",
                 NumberFormatter.with().unit(USD),
                 new ULocale("ar-EG"),
                 12345.67,
@@ -1588,7 +1588,7 @@ public class NumberFormatterApiTest {
 
         assertFormatSingle(
                 "Currency symbol should follow number in ar@numbers=arab",
-                "",
+                "currency/USD",
                 NumberFormatter.with().unit(USD),
                 new ULocale("ar@numbers=arab"),
                 12345.67,
@@ -1596,7 +1596,7 @@ public class NumberFormatterApiTest {
 
         assertFormatSingle(
                 "NumberingSystem in API should win over @numbers keyword",
-                "",
+                "currency/USD latin",
                 NumberFormatter.with().symbols(NumberingSystem.LATIN).unit(USD),
                 new ULocale("ar@numbers=arab"),
                 12345.67,
@@ -1615,7 +1615,7 @@ public class NumberFormatterApiTest {
         symbols.setGroupingSeparatorString("!");
         assertFormatSingle(
                 "Symbols object should be copied",
-                "symbols=loc:de_CH",
+                null,
                 f,
                 ULocale.ENGLISH,
                 12345.67,
@@ -1623,7 +1623,7 @@ public class NumberFormatterApiTest {
 
         assertFormatSingle(
                 "The last symbols setter wins",
-                "symbols=ns:latn",
+                "latin",
                 NumberFormatter.with().symbols(symbols).symbols(NumberingSystem.LATIN),
                 ULocale.ENGLISH,
                 12345.67,
@@ -1631,7 +1631,7 @@ public class NumberFormatterApiTest {
 
         assertFormatSingle(
                 "The last symbols setter wins",
-                "symbols=loc:de_CH",
+                null,
                 NumberFormatter.with().symbols(NumberingSystem.LATIN).symbols(symbols),
                 ULocale.ENGLISH,
                 12345.67,
@@ -1657,7 +1657,7 @@ public class NumberFormatterApiTest {
     public void sign() {
         assertFormatSingle(
                 "Sign Auto Positive",
-                "sign=AUTO",
+                "sign-auto",
                 NumberFormatter.with().sign(SignDisplay.AUTO),
                 ULocale.ENGLISH,
                 444444,
@@ -1665,7 +1665,7 @@ public class NumberFormatterApiTest {
 
         assertFormatSingle(
                 "Sign Auto Negative",
-                "sign=AUTO",
+                "sign-auto",
                 NumberFormatter.with().sign(SignDisplay.AUTO),
                 ULocale.ENGLISH,
                 -444444,
@@ -1673,7 +1673,7 @@ public class NumberFormatterApiTest {
 
         assertFormatSingle(
                 "Sign Auto Zero",
-                "",
+                "sign-auto",
                 NumberFormatter.with().sign(SignDisplay.AUTO),
                 ULocale.ENGLISH,
                 0,
@@ -1681,7 +1681,7 @@ public class NumberFormatterApiTest {
 
         assertFormatSingle(
                 "Sign Always Positive",
-                "sign=ALWAYS",
+                "sign-always",
                 NumberFormatter.with().sign(SignDisplay.ALWAYS),
                 ULocale.ENGLISH,
                 444444,
@@ -1689,7 +1689,7 @@ public class NumberFormatterApiTest {
 
         assertFormatSingle(
                 "Sign Always Negative",
-                "sign=ALWAYS",
+                "sign-always",
                 NumberFormatter.with().sign(SignDisplay.ALWAYS),
                 ULocale.ENGLISH,
                 -444444,
@@ -1697,7 +1697,7 @@ public class NumberFormatterApiTest {
 
         assertFormatSingle(
                 "Sign Always Zero",
-                "",
+                "sign-always",
                 NumberFormatter.with().sign(SignDisplay.ALWAYS),
                 ULocale.ENGLISH,
                 0,
@@ -1705,7 +1705,7 @@ public class NumberFormatterApiTest {
 
         assertFormatSingle(
                 "Sign Never Positive",
-                "sign=NEVER",
+                "sign-never",
                 NumberFormatter.with().sign(SignDisplay.NEVER),
                 ULocale.ENGLISH,
                 444444,
@@ -1713,7 +1713,7 @@ public class NumberFormatterApiTest {
 
         assertFormatSingle(
                 "Sign Never Negative",
-                "sign=NEVER",
+                "sign-never",
                 NumberFormatter.with().sign(SignDisplay.NEVER),
                 ULocale.ENGLISH,
                 -444444,
@@ -1721,7 +1721,7 @@ public class NumberFormatterApiTest {
 
         assertFormatSingle(
                 "Sign Never Zero",
-                "",
+                "sign-never",
                 NumberFormatter.with().sign(SignDisplay.NEVER),
                 ULocale.ENGLISH,
                 0,
@@ -1729,7 +1729,7 @@ public class NumberFormatterApiTest {
 
         assertFormatSingle(
                 "Sign Accounting Positive",
-                "$USD sign=ACCOUNTING",
+                "currency/USD sign-accounting",
                 NumberFormatter.with().sign(SignDisplay.ACCOUNTING).unit(USD),
                 ULocale.ENGLISH,
                 444444,
@@ -1737,7 +1737,7 @@ public class NumberFormatterApiTest {
 
         assertFormatSingle(
                 "Sign Accounting Negative",
-                "$USD sign=ACCOUNTING",
+                "currency/USD sign-accounting",
                 NumberFormatter.with().sign(SignDisplay.ACCOUNTING).unit(USD),
                 ULocale.ENGLISH,
                 -444444,
@@ -1745,7 +1745,7 @@ public class NumberFormatterApiTest {
 
         assertFormatSingle(
                 "Sign Accounting Zero",
-                "",
+                "currency/USD sign-accounting",
                 NumberFormatter.with().sign(SignDisplay.ACCOUNTING).unit(USD),
                 ULocale.ENGLISH,
                 0,
@@ -1753,7 +1753,7 @@ public class NumberFormatterApiTest {
 
         assertFormatSingle(
                 "Sign Accounting-Always Positive",
-                "$USD sign=ACCOUNTING_ALWAYS",
+                "currency/USD sign-accounting-always",
                 NumberFormatter.with().sign(SignDisplay.ACCOUNTING_ALWAYS).unit(USD),
                 ULocale.ENGLISH,
                 444444,
@@ -1761,7 +1761,7 @@ public class NumberFormatterApiTest {
 
         assertFormatSingle(
                 "Sign Accounting-Always Negative",
-                "$USD sign=ACCOUNTING_ALWAYS",
+                "currency/USD sign-accounting-always",
                 NumberFormatter.with().sign(SignDisplay.ACCOUNTING_ALWAYS).unit(USD),
                 ULocale.ENGLISH,
                 -444444,
@@ -1769,7 +1769,7 @@ public class NumberFormatterApiTest {
 
         assertFormatSingle(
                 "Sign Accounting-Always Zero",
-                "",
+                "currency/USD sign-accounting-always",
                 NumberFormatter.with().sign(SignDisplay.ACCOUNTING_ALWAYS).unit(USD),
                 ULocale.ENGLISH,
                 0,
@@ -1777,7 +1777,7 @@ public class NumberFormatterApiTest {
 
         assertFormatSingle(
                 "Sign Except-Zero Positive",
-                "",
+                "sign-except-zero",
                 NumberFormatter.with().sign(SignDisplay.EXCEPT_ZERO),
                 ULocale.ENGLISH,
                 444444,
@@ -1785,7 +1785,7 @@ public class NumberFormatterApiTest {
 
         assertFormatSingle(
                 "Sign Always Negative",
-                "",
+                "sign-except-zero",
                 NumberFormatter.with().sign(SignDisplay.EXCEPT_ZERO),
                 ULocale.ENGLISH,
                 -444444,
@@ -1793,7 +1793,7 @@ public class NumberFormatterApiTest {
 
         assertFormatSingle(
                 "Sign Except-Zero Zero",
-                "",
+                "sign-except-zero",
                 NumberFormatter.with().sign(SignDisplay.EXCEPT_ZERO),
                 ULocale.ENGLISH,
                 0,
@@ -1801,7 +1801,7 @@ public class NumberFormatterApiTest {
 
         assertFormatSingle(
                 "Sign Accounting-Except-Zero Positive",
-                "$USD sign=ACCOUNTING_ALWAYS",
+                "currency/USD sign-accounting-except-zero",
                 NumberFormatter.with().sign(SignDisplay.ACCOUNTING_EXCEPT_ZERO).unit(USD),
                 ULocale.ENGLISH,
                 444444,
@@ -1809,7 +1809,7 @@ public class NumberFormatterApiTest {
 
         assertFormatSingle(
                 "Sign Accounting-Except-Zero Negative",
-                "$USD sign=ACCOUNTING_ALWAYS",
+                "currency/USD sign-accounting-except-zero",
                 NumberFormatter.with().sign(SignDisplay.ACCOUNTING_EXCEPT_ZERO).unit(USD),
                 ULocale.ENGLISH,
                 -444444,
@@ -1817,7 +1817,7 @@ public class NumberFormatterApiTest {
 
         assertFormatSingle(
                 "Sign Accounting-Except-Zero Zero",
-                "",
+                "currency/USD sign-accounting-except-zero",
                 NumberFormatter.with().sign(SignDisplay.ACCOUNTING_EXCEPT_ZERO).unit(USD),
                 ULocale.ENGLISH,
                 0,
@@ -1825,7 +1825,7 @@ public class NumberFormatterApiTest {
 
         assertFormatSingle(
                 "Sign Accounting Negative Hidden",
-                "$USD unit-width=HIDDEN sign=ACCOUNTING",
+                "currency/USD unit-width-hidden sign-accounting",
                 NumberFormatter.with().sign(SignDisplay.ACCOUNTING).unit(USD).unitWidth(UnitWidth.HIDDEN),
                 ULocale.ENGLISH,
                 -444444,
@@ -1836,7 +1836,7 @@ public class NumberFormatterApiTest {
     public void decimal() {
         assertFormatDescending(
                 "Decimal Default",
-                "decimal=AUTO",
+                "decimal-auto",
                 NumberFormatter.with().decimal(DecimalSeparatorDisplay.AUTO),
                 ULocale.ENGLISH,
                 "87,650",
@@ -1851,7 +1851,7 @@ public class NumberFormatterApiTest {
 
         assertFormatDescending(
                 "Decimal Always Shown",
-                "decimal=ALWAYS",
+                "decimal-always",
                 NumberFormatter.with().decimal(DecimalSeparatorDisplay.ALWAYS),
                 ULocale.ENGLISH,
                 "87,650.",
@@ -1880,7 +1880,7 @@ public class NumberFormatterApiTest {
 
         assertFormatSingle(
                 "Plural 1",
-                "$USD F0 unit-width=FULL_NAME",
+                "currency/USD round-integer unit-width-full-name",
                 NumberFormatter.with().unit(USD).unitWidth(UnitWidth.FULL_NAME).rounding(Rounder.fixedFraction(0)),
                 ULocale.ENGLISH,
                 1,
@@ -1888,7 +1888,7 @@ public class NumberFormatterApiTest {
 
         assertFormatSingle(
                 "Plural 1.00",
-                "$USD F2 unit-width=FULL_NAME",
+                "currency/USD .00 unit-width-full-name",
                 NumberFormatter.with().unit(USD).unitWidth(UnitWidth.FULL_NAME).rounding(Rounder.fixedFraction(2)),
                 ULocale.ENGLISH,
                 1,
@@ -2036,18 +2036,23 @@ public class NumberFormatterApiTest {
             double[] inputs,
             String... expected) {
         assert expected.length == 9;
-        assertEquals(message + ": Skeleton:", skeleton, f.toSkeleton());
         LocalizedNumberFormatter l1 = f.threshold(0L).locale(locale); // no self-regulation
         LocalizedNumberFormatter l2 = f.threshold(1L).locale(locale); // all self-regulation
-        LocalizedNumberFormatter l3 = NumberFormatter.fromSkeleton(skeleton).locale(locale);
         for (int i = 0; i < 9; i++) {
             double d = inputs[i];
             String actual1 = l1.format(d).toString();
             assertEquals(message + ": Unsafe Path: " + d, expected[i], actual1);
             String actual2 = l2.format(d).toString();
             assertEquals(message + ": Safe Path: " + d, expected[i], actual2);
-            String actual3 = l3.format(d).toString();
-            assertEquals(message + ": Skeleton Path: " + d, expected[i], actual3);
+        }
+        if (skeleton != null) { // if null, skeleton is declared as undefined.
+            assertEquals(message + ": Skeleton:", skeleton, f.toSkeleton());
+            LocalizedNumberFormatter l3 = NumberFormatter.fromSkeleton(skeleton).locale(locale);
+            for (int i = 0; i < 9; i++) {
+                double d = inputs[i];
+                String actual3 = l3.format(d).toString();
+                assertEquals(message + ": Skeleton Path: " + d, expected[i], actual3);
+            }
         }
     }
 
@@ -2058,16 +2063,18 @@ public class NumberFormatterApiTest {
             ULocale locale,
             Number input,
             String expected) {
-        assertEquals(message + ": Skeleton:", skeleton, f.toSkeleton());
         LocalizedNumberFormatter l1 = f.threshold(0L).locale(locale); // no self-regulation
         LocalizedNumberFormatter l2 = f.threshold(1L).locale(locale); // all self-regulation
-        LocalizedNumberFormatter l3 = NumberFormatter.fromSkeleton(skeleton).locale(locale);
         String actual1 = l1.format(input).toString();
         assertEquals(message + ": Unsafe Path: " + input, expected, actual1);
         String actual2 = l2.format(input).toString();
         assertEquals(message + ": Safe Path: " + input, expected, actual2);
-        String actual3 = l3.format(input).toString();
-        assertEquals(message + ": Skeleton Path: " + input, expected, actual3);
+        if (skeleton != null) { // if null, skeleton is declared as undefined.
+            assertEquals(message + ": Skeleton:", skeleton, f.toSkeleton());
+            LocalizedNumberFormatter l3 = NumberFormatter.fromSkeleton(skeleton).locale(locale);
+            String actual3 = l3.format(input).toString();
+            assertEquals(message + ": Skeleton Path: " + input, expected, actual3);
+        }
     }
 
     private static void assertFormatSingleMeasure(
@@ -2077,15 +2084,17 @@ public class NumberFormatterApiTest {
             ULocale locale,
             Measure input,
             String expected) {
-        assertEquals(message + ": Skeleton:", skeleton, f.toSkeleton());
         LocalizedNumberFormatter l1 = f.threshold(0L).locale(locale); // no self-regulation
         LocalizedNumberFormatter l2 = f.threshold(1L).locale(locale); // all self-regulation
-        LocalizedNumberFormatter l3 = NumberFormatter.fromSkeleton(skeleton).locale(locale);
         String actual1 = l1.format(input).toString();
         assertEquals(message + ": Unsafe Path: " + input, expected, actual1);
         String actual2 = l2.format(input).toString();
         assertEquals(message + ": Safe Path: " + input, expected, actual2);
-        String actual3 = l3.format(input).toString();
-        assertEquals(message + ": Skeleton Path: " + input, expected, actual3);
+        if (skeleton != null) { // if null, skeleton is declared as undefined.
+            assertEquals(message + ": Skeleton:", skeleton, f.toSkeleton());
+            LocalizedNumberFormatter l3 = NumberFormatter.fromSkeleton(skeleton).locale(locale);
+            String actual3 = l3.format(input).toString();
+            assertEquals(message + ": Skeleton Path: " + input, expected, actual3);
+        }
     }
 }
index 6735271ab4c5fdb993e7a942142fddb122cceded..6bb084c48bdabb5fc6bb18b1c825e8ec166f6526 100644 (file)
@@ -38,7 +38,11 @@ public class NumberSkeletonTest {
                 "@#+",
                 "round-increment/xxx",
                 "round-increment/0.1.2",
-        };
+                "currency/dummy",
+                "measure-unit/foo",
+                "integer-width/xxx",
+                "integer-width/0+",
+                "integer-width/+0#", };
 
         for (String cas : cases) {
             try {
@@ -51,19 +55,33 @@ public class NumberSkeletonTest {
     }
 
     @Test
-    public void stemsRequiringOption() {
-        String[] cases = {
-                "round-increment",
-                "round-increment/",
-                "round-increment scientific",
-        };
+    public void unknownTokens() {
+        String[] cases = { "measure-unit/foo-bar", "numbering-system/dummy" };
 
         for (String cas : cases) {
             try {
                 NumberFormatter.fromSkeleton(cas);
                 fail();
             } catch (SkeletonSyntaxException expected) {
-                assertTrue(expected.getMessage(), expected.getMessage().contains("requires an option"));
+                assertTrue(expected.getMessage(), expected.getMessage().contains("Unknown"));
+            }
+        }
+    }
+
+    @Test
+    public void stemsRequiringOption() {
+        String[] stems = { "round-increment", "currency", "measure-unit", "integer-width", };
+        String[] suffixes = { "", "/", " scientific", "/ scientific" };
+
+        for (String stem : stems) {
+            for (String suffix : suffixes) {
+                try {
+                    NumberFormatter.fromSkeleton(stem + suffix);
+                    fail();
+                } catch (SkeletonSyntaxException expected) {
+                    assertTrue(expected.getMessage(),
+                            expected.getMessage().contains("requires an option"));
+                }
             }
         }
     }