]> granicus.if.org Git - icu/commitdiff
ICU-13177 Moving around a lot of code for better C++ portability. The only behavior...
authorShane Carr <shane@unicode.org>
Sat, 26 Aug 2017 06:56:41 +0000 (06:56 +0000)
committerShane Carr <shane@unicode.org>
Sat, 26 Aug 2017 06:56:41 +0000 (06:56 +0000)
X-SVN-Rev: 40357

46 files changed:
icu4j/main/classes/core/src/com/ibm/icu/impl/number/modifiers/CurrencySpacingEnabledModifier.java [moved from icu4j/main/classes/core/src/newapi/impl/CurrencySpacingEnabledModifier.java with 98% similarity]
icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java
icu4j/main/classes/core/src/newapi/CompactNotation.java [new file with mode: 0644]
icu4j/main/classes/core/src/newapi/CurrencyRounder.java [new file with mode: 0644]
icu4j/main/classes/core/src/newapi/FormattedNumber.java [new file with mode: 0644]
icu4j/main/classes/core/src/newapi/FractionRounder.java [new file with mode: 0644]
icu4j/main/classes/core/src/newapi/Grouper.java [new file with mode: 0644]
icu4j/main/classes/core/src/newapi/IntegerWidth.java [new file with mode: 0644]
icu4j/main/classes/core/src/newapi/LocalizedNumberFormatter.java [new file with mode: 0644]
icu4j/main/classes/core/src/newapi/MurkyLongNameHandler.java [new file with mode: 0644]
icu4j/main/classes/core/src/newapi/MurkyModifier.java [new file with mode: 0644]
icu4j/main/classes/core/src/newapi/Notation.java [new file with mode: 0644]
icu4j/main/classes/core/src/newapi/NumberFormatter.java
icu4j/main/classes/core/src/newapi/NumberFormatterSettings.java [new file with mode: 0644]
icu4j/main/classes/core/src/newapi/NumberPropertyMapper.java [new file with mode: 0644]
icu4j/main/classes/core/src/newapi/Rounder.java [new file with mode: 0644]
icu4j/main/classes/core/src/newapi/ScientificNotation.java [new file with mode: 0644]
icu4j/main/classes/core/src/newapi/SimpleNotation.java [new file with mode: 0644]
icu4j/main/classes/core/src/newapi/SkeletonBuilder.java [new file with mode: 0644]
icu4j/main/classes/core/src/newapi/UnlocalizedNumberFormatter.java [new file with mode: 0644]
icu4j/main/classes/core/src/newapi/Worker1.java [new file with mode: 0644]
icu4j/main/classes/core/src/newapi/impl/CompactData.java
icu4j/main/classes/core/src/newapi/impl/CompactImpl.java [deleted file]
icu4j/main/classes/core/src/newapi/impl/DataUtils.java [deleted file]
icu4j/main/classes/core/src/newapi/impl/GroupingImpl.java [deleted file]
icu4j/main/classes/core/src/newapi/impl/IntegerWidthImpl.java [deleted file]
icu4j/main/classes/core/src/newapi/impl/MacroProps.java
icu4j/main/classes/core/src/newapi/impl/MicroProps.java
icu4j/main/classes/core/src/newapi/impl/MultiplierProducer.java [new file with mode: 0644]
icu4j/main/classes/core/src/newapi/impl/MurkyModifier.java [deleted file]
icu4j/main/classes/core/src/newapi/impl/NotationImpl.java [deleted file]
icu4j/main/classes/core/src/newapi/impl/NumberFormatterImpl.java [deleted file]
icu4j/main/classes/core/src/newapi/impl/NumberPropertyMapper.java [deleted file]
icu4j/main/classes/core/src/newapi/impl/Padder.java [new file with mode: 0644]
icu4j/main/classes/core/src/newapi/impl/PaddingImpl.java [deleted file]
icu4j/main/classes/core/src/newapi/impl/PositiveDecimalImpl.java [deleted file]
icu4j/main/classes/core/src/newapi/impl/QuantityDependentModOuter.java [deleted file]
icu4j/main/classes/core/src/newapi/impl/RoundingImpl.java [deleted file]
icu4j/main/classes/core/src/newapi/impl/ScientificImpl.java [deleted file]
icu4j/main/classes/core/src/newapi/impl/SkeletonBuilder.java [deleted file]
icu4j/main/classes/core/src/newapi/impl/Worker1.java [deleted file]
icu4j/main/classes/core/src/newapi/impl/demo.java [moved from icu4j/main/classes/core/src/newapi/demo.java with 71% similarity]
icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatDataDrivenTest.java
icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/FormatQuantityTest.java
icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/MurkyModifierTest.java
icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterTest.java

similarity index 98%
rename from icu4j/main/classes/core/src/newapi/impl/CurrencySpacingEnabledModifier.java
rename to icu4j/main/classes/core/src/com/ibm/icu/impl/number/modifiers/CurrencySpacingEnabledModifier.java
index eed7ed13fa6733c1020e6a8c091cf4485b41d48f..66b7c2d6204aa8f44e80696fba513f4ab39d9bf8 100644 (file)
@@ -1,9 +1,8 @@
 // © 2017 and later: Unicode, Inc. and others.
 // License & terms of use: http://www.unicode.org/copyright.html#License
-package newapi.impl;
+package com.ibm.icu.impl.number.modifiers;
 
 import com.ibm.icu.impl.number.NumberStringBuilder;
-import com.ibm.icu.impl.number.modifiers.ConstantMultiFieldModifier;
 import com.ibm.icu.text.DecimalFormatSymbols;
 import com.ibm.icu.text.NumberFormat;
 import com.ibm.icu.text.UnicodeSet;
index 3efa8fa15b8c3106b06d11e295abe6bbb193563f..d56f5d7e2f6eb707a5561ddfc1d99a001c009cb3 100644 (file)
@@ -28,11 +28,11 @@ import com.ibm.icu.util.CurrencyAmount;
 import com.ibm.icu.util.ULocale;
 import com.ibm.icu.util.ULocale.Category;
 
-import newapi.NumberFormatter.LocalizedNumberFormatter;
-import newapi.NumberFormatter.NumberFormatterResult;
+import newapi.FormattedNumber;
+import newapi.LocalizedNumberFormatter;
+import newapi.NumberFormatter;
+import newapi.NumberPropertyMapper;
 import newapi.impl.MacroProps;
-import newapi.impl.NumberFormatterImpl;
-import newapi.impl.NumberPropertyMapper;
 
 /**
  * {@icuenhanced java.text.DecimalFormat}.{@icu _usage_} <code>DecimalFormat</code> is the primary
@@ -683,7 +683,7 @@ public class DecimalFormat extends NumberFormat {
    */
   @Override
   public StringBuffer format(double number, StringBuffer result, FieldPosition fieldPosition) {
-    NumberFormatterResult output = formatter.format(number);
+    FormattedNumber output = formatter.format(number);
     output.populateFieldPosition(fieldPosition, result.length());
     output.appendTo(result);
     return result;
@@ -696,7 +696,7 @@ public class DecimalFormat extends NumberFormat {
    */
   @Override
   public StringBuffer format(long number, StringBuffer result, FieldPosition fieldPosition) {
-    NumberFormatterResult output = formatter.format(number);
+    FormattedNumber output = formatter.format(number);
     output.populateFieldPosition(fieldPosition, result.length());
     output.appendTo(result);
     return result;
@@ -709,7 +709,7 @@ public class DecimalFormat extends NumberFormat {
    */
   @Override
   public StringBuffer format(BigInteger number, StringBuffer result, FieldPosition fieldPosition) {
-    NumberFormatterResult output = formatter.format(number);
+    FormattedNumber output = formatter.format(number);
     output.populateFieldPosition(fieldPosition, result.length());
     output.appendTo(result);
     return result;
@@ -723,7 +723,7 @@ public class DecimalFormat extends NumberFormat {
   @Override
   public StringBuffer format(
       java.math.BigDecimal number, StringBuffer result, FieldPosition fieldPosition) {
-    NumberFormatterResult output = formatter.format(number);
+    FormattedNumber output = formatter.format(number);
     output.populateFieldPosition(fieldPosition, result.length());
     output.appendTo(result);
     return result;
@@ -736,7 +736,7 @@ public class DecimalFormat extends NumberFormat {
    */
   @Override
   public StringBuffer format(BigDecimal number, StringBuffer result, FieldPosition fieldPosition) {
-    NumberFormatterResult output = formatter.format(number);
+    FormattedNumber output = formatter.format(number);
     output.populateFieldPosition(fieldPosition, result.length());
     output.appendTo(result);
     return result;
@@ -751,7 +751,7 @@ public class DecimalFormat extends NumberFormat {
   public AttributedCharacterIterator formatToCharacterIterator(Object obj) {
     if (!(obj instanceof Number)) throw new IllegalArgumentException();
     Number number = (Number) obj;
-    NumberFormatterResult output = formatter.format(number);
+    FormattedNumber output = formatter.format(number);
     return output.toAttributedCharacterIterator();
   }
 
@@ -762,7 +762,7 @@ public class DecimalFormat extends NumberFormat {
    */
   @Override
   public StringBuffer format(CurrencyAmount currAmt, StringBuffer toAppendTo, FieldPosition pos) {
-    NumberFormatterResult output = formatter.format(currAmt);
+    FormattedNumber output = formatter.format(currAmt);
     output.populateFieldPosition(pos, toAppendTo.length());
     output.appendTo(toAppendTo);
     return toAppendTo;
@@ -2434,7 +2434,7 @@ public class DecimalFormat extends NumberFormat {
       locale = symbols.getULocale();
     }
     assert locale != null;
-    formatter = NumberFormatterImpl.fromMacros(macros).locale(locale);
+    formatter = NumberFormatter.with().macros(macros).locale(locale);
   }
 
   /**
diff --git a/icu4j/main/classes/core/src/newapi/CompactNotation.java b/icu4j/main/classes/core/src/newapi/CompactNotation.java
new file mode 100644 (file)
index 0000000..c45bb37
--- /dev/null
@@ -0,0 +1,135 @@
+// © 2017 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html#License
+package newapi;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import com.ibm.icu.impl.StandardPlural;
+import com.ibm.icu.impl.number.FormatQuantity;
+import com.ibm.icu.impl.number.LdmlPatternInfo;
+import com.ibm.icu.impl.number.LdmlPatternInfo.PatternParseResult;
+import com.ibm.icu.text.CompactDecimalFormat.CompactStyle;
+import com.ibm.icu.text.CompactDecimalFormat.CompactType;
+import com.ibm.icu.text.PluralRules;
+import com.ibm.icu.util.ULocale;
+
+import newapi.MurkyModifier.ImmutableMurkyModifier;
+import newapi.impl.CompactData;
+import newapi.impl.MicroProps;
+import newapi.impl.QuantityChain;
+
+public class CompactNotation extends Notation {
+
+    final CompactStyle compactStyle;
+    final Map<String, Map<String, String>> compactCustomData;
+
+    public CompactNotation(CompactStyle compactStyle) {
+        compactCustomData = null;
+        this.compactStyle = compactStyle;
+    }
+
+    public CompactNotation(Map<String, Map<String, String>> compactCustomData) {
+        compactStyle = null;
+        this.compactCustomData = compactCustomData;
+    }
+
+    /* package-private */ QuantityChain withLocaleData(ULocale dataLocale, CompactType compactType, PluralRules rules,
+            MurkyModifier buildReference, QuantityChain parent) {
+        CompactData data;
+        if (compactStyle != null) {
+            data = CompactData.getInstance(dataLocale, compactType, compactStyle);
+        } else {
+            data = CompactData.getInstance(compactCustomData);
+        }
+        return new CompactImpl(data, rules, buildReference, parent);
+    }
+
+    private static class CompactImpl implements QuantityChain {
+
+        private static class CompactModInfo {
+            public ImmutableMurkyModifier mod;
+            public int numDigits;
+        }
+
+        final PluralRules rules;
+        final CompactData data;
+        final Map<String, CompactModInfo> precomputedMods;
+        final QuantityChain parent;
+
+        private CompactImpl(CompactData data, PluralRules rules, MurkyModifier buildReference, QuantityChain parent) {
+            this.data = data;
+            this.rules = rules;
+            if (buildReference != null) {
+                // Safe code path
+                precomputedMods = precomputeAllModifiers(data, buildReference);
+            } else {
+                // Unsafe code path
+                precomputedMods = null;
+            }
+            this.parent = parent;
+        }
+
+        /** Used by the safe code path */
+        private static Map<String, CompactModInfo> precomputeAllModifiers(CompactData data,
+                MurkyModifier buildReference) {
+            Map<String, CompactModInfo> precomputedMods = new HashMap<String, CompactModInfo>();
+            Set<String> allPatterns = data.getAllPatterns();
+            for (String patternString : allPatterns) {
+                CompactModInfo info = new CompactModInfo();
+                PatternParseResult patternInfo = LdmlPatternInfo.parse(patternString);
+                buildReference.setPatternInfo(patternInfo);
+                info.mod = buildReference.createImmutable();
+                info.numDigits = patternInfo.positive.totalIntegerDigits;
+                precomputedMods.put(patternString, info);
+            }
+            return precomputedMods;
+        }
+
+        @Override
+        public MicroProps withQuantity(FormatQuantity input) {
+            MicroProps micros = parent.withQuantity(input);
+            assert micros.rounding != null;
+
+            // Treat zero as if it had magnitude 0
+            int magnitude;
+            if (input.isZero()) {
+                magnitude = 0;
+                micros.rounding.apply(input);
+            } else {
+                // TODO: Revisit chooseMultiplierAndApply
+                int multiplier = micros.rounding.chooseMultiplierAndApply(input, data);
+                magnitude = input.isZero() ? 0 : input.getMagnitude();
+                magnitude -= multiplier;
+            }
+
+            StandardPlural plural = input.getStandardPlural(rules);
+            String patternString = data.getPattern(magnitude, plural);
+            int numDigits = -1;
+            if (patternString == null) {
+                // Use the default (non-compact) modifier.
+                // No need to take any action.
+            } else if (precomputedMods != null) {
+                // Build code path.
+                CompactModInfo info = precomputedMods.get(patternString);
+                info.mod.applyToMicros(micros, input);
+                numDigits = info.numDigits;
+            } else {
+                // Non-build code path.
+                // Overwrite the PatternInfo in the existing modMiddle
+                assert micros.modMiddle instanceof MurkyModifier;
+                PatternParseResult patternInfo = LdmlPatternInfo.parse(patternString);
+                ((MurkyModifier) micros.modMiddle).setPatternInfo(patternInfo);
+                numDigits = patternInfo.positive.totalIntegerDigits;
+            }
+
+            // FIXME: Deal with numDigits == 0 (Awaiting a test case)
+
+            // We already performed rounding. Do not perform it again.
+            micros.rounding = Rounder.constructPassThrough();
+
+            return micros;
+        }
+    }
+}
\ No newline at end of file
diff --git a/icu4j/main/classes/core/src/newapi/CurrencyRounder.java b/icu4j/main/classes/core/src/newapi/CurrencyRounder.java
new file mode 100644 (file)
index 0000000..631084e
--- /dev/null
@@ -0,0 +1,42 @@
+// © 2017 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html#License
+package newapi;
+
+import com.ibm.icu.util.Currency;
+import com.ibm.icu.util.Currency.CurrencyUsage;
+import com.ibm.icu.util.CurrencyAmount;
+import com.ibm.icu.util.Measure;
+import com.ibm.icu.util.MeasureUnit;
+
+/** A rounding strategy parameterized by a currency. */
+public abstract class CurrencyRounder extends Rounder {
+
+    /* package-private */ CurrencyRounder() {
+    }
+
+    /**
+     * Associates a {@link com.ibm.icu.util.Currency} with this rounding strategy. Only applies to rounding strategies
+     * returned from {@link #currency(CurrencyUsage)}.
+     *
+     * <p>
+     * <strong>Calling this method is <em>not required</em></strong>, because the currency specified in
+     * {@link NumberFormatter#unit(MeasureUnit)} or via a {@link CurrencyAmount} passed into
+     * {@link LocalizedNumberFormatter#format(Measure)} is automatically applied to currency rounding strategies.
+     * However, this method enables you to override that automatic association.
+     *
+     * <p>
+     * This method also enables numbers to be formatted using currency rounding rules without explicitly using a
+     * currency format.
+     *
+     * @param currency
+     *            The currency to associate with this rounding strategy.
+     * @return An immutable object for chaining.
+     */
+    public Rounder withCurrency(Currency currency) {
+        if (currency != null) {
+            return constructFromCurrency(this, currency);
+        } else {
+            throw new IllegalArgumentException("Currency must not be null");
+        }
+    };
+}
\ No newline at end of file
diff --git a/icu4j/main/classes/core/src/newapi/FormattedNumber.java b/icu4j/main/classes/core/src/newapi/FormattedNumber.java
new file mode 100644 (file)
index 0000000..3749eeb
--- /dev/null
@@ -0,0 +1,115 @@
+// © 2017 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html#License
+package newapi;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.text.AttributedCharacterIterator;
+import java.text.FieldPosition;
+import java.util.Arrays;
+
+import com.ibm.icu.impl.number.FormatQuantity;
+import com.ibm.icu.impl.number.NumberStringBuilder;
+import com.ibm.icu.text.PluralRules.IFixedDecimal;
+import com.ibm.icu.util.ICUUncheckedIOException;
+
+import newapi.impl.MicroProps;
+
+public class FormattedNumber {
+    NumberStringBuilder nsb;
+    FormatQuantity fq;
+    MicroProps micros;
+
+    FormattedNumber(NumberStringBuilder nsb, FormatQuantity fq, MicroProps micros) {
+        this.nsb = nsb;
+        this.fq = fq;
+        this.micros = micros;
+    }
+
+    @Override
+    public String toString() {
+        return nsb.toString();
+    }
+
+    public <A extends Appendable> A appendTo(A appendable) {
+        try {
+            appendable.append(nsb);
+        } catch (IOException e) {
+            // Throw as an unchecked exception to avoid users needing try/catch
+            throw new ICUUncheckedIOException(e);
+        }
+        return appendable;
+    }
+
+    public void populateFieldPosition(FieldPosition fieldPosition) {
+        populateFieldPosition(fieldPosition, 0);
+    }
+
+    /**
+     * @internal
+     * @deprecated This API is ICU internal only.
+     */
+    @Deprecated
+    public void populateFieldPosition(FieldPosition fieldPosition, int offset) {
+        nsb.populateFieldPosition(fieldPosition, offset);
+        fq.populateUFieldPosition(fieldPosition);
+    }
+
+    public AttributedCharacterIterator toAttributedCharacterIterator() {
+        return nsb.getIterator();
+    }
+
+    public BigDecimal toBigDecimal() {
+        return fq.toBigDecimal();
+    }
+
+    /**
+     * @internal
+     * @deprecated This API a technology preview. It is not stable and may change or go away in an upcoming release.
+     */
+    @Deprecated
+    public String getPrefix() {
+        return micros.modOuter.getPrefix() + micros.modMiddle.getPrefix() + micros.modInner.getPrefix();
+    }
+
+    /**
+     * @internal
+     * @deprecated This API a technology preview. It is not stable and may change or go away in an upcoming release.
+     */
+    @Deprecated
+    public String getSuffix() {
+        return micros.modInner.getSuffix() + micros.modMiddle.getSuffix() + micros.modOuter.getSuffix();
+    }
+
+    /**
+     * @internal
+     * @deprecated This API a technology preview. It is not stable and may change or go away in an upcoming release.
+     */
+    @Deprecated
+    public IFixedDecimal getFixedDecimal() {
+        return fq;
+    }
+
+    @Override
+    public int hashCode() {
+        // NumberStringBuilder and BigDecimal are mutable, so we can't call
+        // #equals() or #hashCode() on them directly.
+        return Arrays.hashCode(nsb.toCharArray()) ^ Arrays.hashCode(nsb.toFieldArray()) ^ fq.toBigDecimal().hashCode();
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (this == other)
+            return true;
+        if (other == null)
+            return false;
+        if (!(other instanceof FormattedNumber))
+            return false;
+        // NumberStringBuilder and BigDecimal are mutable, so we can't call
+        // #equals() or #hashCode() on them directly.
+        FormattedNumber _other = (FormattedNumber) other;
+        return Arrays.equals(nsb.toCharArray(), _other.nsb.toCharArray())
+                ^ Arrays.equals(nsb.toFieldArray(), _other.nsb.toFieldArray())
+                ^ fq.toBigDecimal().equals(_other.fq.toBigDecimal());
+    }
+}
\ No newline at end of file
diff --git a/icu4j/main/classes/core/src/newapi/FractionRounder.java b/icu4j/main/classes/core/src/newapi/FractionRounder.java
new file mode 100644 (file)
index 0000000..b6dd1f4
--- /dev/null
@@ -0,0 +1,60 @@
+// © 2017 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html#License
+package newapi;
+
+/**
+ * A rounding strategy based on a minimum and/or maximum number of fraction digits. Allows for a minimum or maximum
+ * number of significant digits to be specified.
+ */
+public abstract class FractionRounder extends Rounder {
+
+    /* package-private */ FractionRounder() {
+    }
+
+    /**
+     * Ensures that no less than this number of significant figures are retained when rounding according to fraction
+     * rules.
+     *
+     * <p>
+     * For example, with integer rounding, the number 3.141 becomes "3". However, with minimum figures set to 2, 3.141
+     * becomes "3.1" instead.
+     *
+     * <p>
+     * This setting does not affect the number of trailing zeros. For example, 3.01 would print as "3", not "3.0".
+     *
+     * @param minFigures
+     *            The number of significant figures to guarantee.
+     * @return An immutable object for chaining.
+     */
+    public Rounder withMinFigures(int minFigures) {
+        if (minFigures > 0 && minFigures <= MAX_VALUE) {
+            return constructFractionSignificant(this, minFigures, -1);
+        } else {
+            throw new IllegalArgumentException("Significant digits must be between 0 and " + MAX_VALUE);
+        }
+    }
+
+    /**
+     * Ensures that no more than this number of significant figures are retained when rounding according to fraction
+     * rules.
+     *
+     * <p>
+     * For example, with integer rounding, the number 123.4 becomes "123". However, with maximum figures set to 2, 123.4
+     * becomes "120" instead.
+     *
+     * <p>
+     * This setting does not affect the number of trailing zeros. For example, with fixed fraction of 2, 123.4 would
+     * become "120.00".
+     *
+     * @param maxFigures
+     *            Round the number to no more than this number of significant figures.
+     * @return An immutable object for chaining.
+     */
+    public Rounder withMaxFigures(int maxFigures) {
+        if (maxFigures > 0 && maxFigures <= MAX_VALUE) {
+            return constructFractionSignificant(this, -1, maxFigures);
+        } else {
+            throw new IllegalArgumentException("Significant digits must be between 0 and " + MAX_VALUE);
+        }
+    }
+}
\ No newline at end of file
diff --git a/icu4j/main/classes/core/src/newapi/Grouper.java b/icu4j/main/classes/core/src/newapi/Grouper.java
new file mode 100644 (file)
index 0000000..0db2586
--- /dev/null
@@ -0,0 +1,98 @@
+// © 2017 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html#License
+package newapi;
+
+import com.ibm.icu.impl.number.FormatQuantity;
+import com.ibm.icu.impl.number.LdmlPatternInfo.PatternParseResult;
+
+public class Grouper {
+
+    // Conveniences for Java handling of bytes
+    private static final byte N2 = -2;
+    private static final byte N1 = -1;
+    private static final byte B2 = 2;
+    private static final byte B3 = 3;
+
+    private static final Grouper DEFAULTS = new Grouper(N2, N2, false);
+    private static final Grouper MIN2 = new Grouper(N2, N2, true);
+    private static final Grouper NONE = new Grouper(N1, N1, false);
+
+    private final byte grouping1; // -2 means "needs locale data"; -1 means "no grouping"
+    private final byte grouping2;
+    private final boolean min2;
+
+    private Grouper(byte grouping1, byte grouping2, boolean min2) {
+        this.grouping1 = grouping1;
+        this.grouping2 = grouping2;
+        this.min2 = min2;
+    }
+
+    public static Grouper defaults() {
+        return DEFAULTS;
+    }
+
+    public static Grouper min2() {
+        return MIN2;
+    }
+
+    public static Grouper none() {
+        return NONE;
+    }
+
+    //////////////////////////
+    // PACKAGE-PRIVATE APIS //
+    //////////////////////////
+
+    private static final Grouper GROUPING_3 = new Grouper(B3, B3, false);
+    private static final Grouper GROUPING_3_2 = new Grouper(B3, B2, false);
+    private static final Grouper GROUPING_3_MIN2 = new Grouper(B3, B3, true);
+    private static final Grouper GROUPING_3_2_MIN2 = new Grouper(B3, B2, true);
+
+    static Grouper getInstance(byte grouping1, byte grouping2, boolean min2) {
+        if (grouping1 == -1) {
+            return NONE;
+        } else if (!min2 && grouping1 == 3 && grouping2 == 3) {
+            return GROUPING_3;
+        } else if (!min2 && grouping1 == 3 && grouping2 == 2) {
+            return GROUPING_3_2;
+        } else if (min2 && grouping1 == 3 && grouping2 == 3) {
+            return GROUPING_3_MIN2;
+        } else if (min2 && grouping1 == 3 && grouping2 == 2) {
+            return GROUPING_3_2_MIN2;
+        } else {
+            return new Grouper(grouping1, grouping2, min2);
+        }
+    }
+
+    static Grouper normalizeType(Grouper grouping, PatternParseResult patternInfo) {
+        return grouping.withLocaleData(patternInfo);
+    }
+
+    Grouper withLocaleData(PatternParseResult patternInfo) {
+        if (grouping1 != -2) {
+            return this;
+        }
+        // TODO: short or byte?
+        byte grouping1 = (byte) (patternInfo.positive.groupingSizes & 0xffff);
+        byte grouping2 = (byte) ((patternInfo.positive.groupingSizes >>> 16) & 0xffff);
+        byte grouping3 = (byte) ((patternInfo.positive.groupingSizes >>> 32) & 0xffff);
+        if (grouping2 == -1) {
+            grouping1 = -1;
+        }
+        if (grouping3 == -1) {
+            grouping2 = grouping1;
+        }
+        return getInstance(grouping1, grouping2, min2);
+    }
+
+    boolean groupAtPosition(int position, FormatQuantity value) {
+        assert grouping1 != -2;
+        if (grouping1 == -1 || grouping1 == 0) {
+            // Either -1 or 0 means "no grouping"
+            return false;
+        }
+        position -= grouping1;
+        return position >= 0 && (position % grouping2) == 0
+                && value.getUpperDisplayMagnitude() - grouping1 + 1 >= (min2 ? 2 : 1);
+    }
+  }
\ No newline at end of file
diff --git a/icu4j/main/classes/core/src/newapi/IntegerWidth.java b/icu4j/main/classes/core/src/newapi/IntegerWidth.java
new file mode 100644 (file)
index 0000000..e092ec0
--- /dev/null
@@ -0,0 +1,39 @@
+// © 2017 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html#License
+package newapi;
+
+@SuppressWarnings("unused")
+public class IntegerWidth {
+
+    private static final IntegerWidth DEFAULT = new IntegerWidth(1, -1);
+
+    final int minInt;
+    final int maxInt;
+
+    private IntegerWidth(int minInt, int maxInt) {
+        this.minInt = minInt;
+        this.maxInt = maxInt;
+    }
+
+    public static IntegerWidth zeroFillTo(int minInt) {
+        if (minInt == 1) {
+            return DEFAULT;
+        } else if (minInt >= 0 && minInt < Rounder.MAX_VALUE) {
+            return new IntegerWidth(minInt, -1);
+        } else {
+            throw new IllegalArgumentException("Integer digits must be between 0 and " + Rounder.MAX_VALUE);
+        }
+    }
+
+    public IntegerWidth truncateAt(int maxInt) {
+        if (maxInt == this.maxInt) {
+            return this;
+        } else if (maxInt >= 0 && maxInt < Rounder.MAX_VALUE) {
+            return new IntegerWidth(minInt, maxInt);
+        } else if (maxInt == -1) {
+            return new IntegerWidth(minInt, maxInt);
+        } else {
+            throw new IllegalArgumentException("Integer digits must be between 0 and " + Rounder.MAX_VALUE);
+        }
+    }
+}
\ No newline at end of file
diff --git a/icu4j/main/classes/core/src/newapi/LocalizedNumberFormatter.java b/icu4j/main/classes/core/src/newapi/LocalizedNumberFormatter.java
new file mode 100644 (file)
index 0000000..9152360
--- /dev/null
@@ -0,0 +1,108 @@
+// © 2017 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html#License
+package newapi;
+
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicLongFieldUpdater;
+
+import com.ibm.icu.impl.number.FormatQuantity;
+import com.ibm.icu.impl.number.FormatQuantity4;
+import com.ibm.icu.impl.number.NumberStringBuilder;
+import com.ibm.icu.util.Measure;
+import com.ibm.icu.util.MeasureUnit;
+
+import newapi.impl.MacroProps;
+import newapi.impl.MicroProps;
+
+public class LocalizedNumberFormatter extends NumberFormatterSettings<LocalizedNumberFormatter> {
+
+    static final AtomicLongFieldUpdater<LocalizedNumberFormatter> callCount = AtomicLongFieldUpdater
+            .newUpdater(LocalizedNumberFormatter.class, "callCountInternal");
+
+    volatile long callCountInternal; // do not access directly; use callCount instead
+    volatile LocalizedNumberFormatter savedWithUnit;
+    volatile Worker1 compiled;
+
+    LocalizedNumberFormatter(NumberFormatterSettings<?> parent, int key, Object value) {
+        super(parent, key, value);
+    }
+
+    public FormattedNumber format(long input) {
+        return format(new FormatQuantity4(input));
+    }
+
+    public FormattedNumber format(double input) {
+        return format(new FormatQuantity4(input));
+    }
+
+    public FormattedNumber format(Number input) {
+        return format(new FormatQuantity4(input));
+    }
+
+    public FormattedNumber format(Measure input) {
+        MeasureUnit unit = input.getUnit();
+        Number number = input.getNumber();
+        // Use this formatter if possible
+        if (Objects.equals(resolve().unit, unit)) {
+            return format(number);
+        }
+        // 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.
+        LocalizedNumberFormatter withUnit = savedWithUnit;
+        if (withUnit == null || !Objects.equals(withUnit.resolve().unit, unit)) {
+            withUnit = new LocalizedNumberFormatter(this, KEY_UNIT, unit);
+            savedWithUnit = withUnit;
+        }
+        return withUnit.format(number);
+    }
+
+    /**
+     * This is the core entrypoint to the number formatting pipeline. It performs self-regulation: a static code path
+     * for the first few calls, and compiling a more efficient data structure if called repeatedly.
+     *
+     * @param fq
+     *            The quantity to be formatted.
+     * @return The formatted number result.
+     *
+     * @internal
+     * @deprecated ICU 60 This API is ICU internal only.
+     */
+    @Deprecated
+    public FormattedNumber format(FormatQuantity fq) {
+        MacroProps macros = resolve();
+        NumberStringBuilder string = new NumberStringBuilder();
+        long currentCount = callCount.incrementAndGet(this);
+        MicroProps micros;
+        if (currentCount == macros.threshold.longValue()) {
+            compiled = Worker1.fromMacros(macros);
+            micros = compiled.apply(fq, string);
+        } else if (compiled != null) {
+            micros = compiled.apply(fq, string);
+        } else {
+            micros = Worker1.applyStatic(macros, fq, string);
+        }
+        return new FormattedNumber(string, fq, micros);
+    }
+
+    @Override
+    protected LocalizedNumberFormatter create(int key, Object value) {
+        return new LocalizedNumberFormatter(this, key, value);
+    }
+
+    /**
+     * @internal
+     * @deprecated ICU 60 This API is ICU internal only.
+     */
+    @Deprecated
+    public static class Internal extends LocalizedNumberFormatter {
+
+        /**
+         * @internal
+         * @deprecated ICU 60 This API is ICU internal only.
+         */
+        @Deprecated
+        public Internal(NumberFormatterSettings<?> parent, int key, Object value) {
+            super(parent, key, value);
+        }
+    }
+}
\ No newline at end of file
diff --git a/icu4j/main/classes/core/src/newapi/MurkyLongNameHandler.java b/icu4j/main/classes/core/src/newapi/MurkyLongNameHandler.java
new file mode 100644 (file)
index 0000000..8fee3a3
--- /dev/null
@@ -0,0 +1,111 @@
+// © 2017 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html#License
+package newapi;
+
+import java.util.EnumMap;
+import java.util.Map;
+
+import com.ibm.icu.impl.CurrencyData;
+import com.ibm.icu.impl.SimpleFormatterImpl;
+import com.ibm.icu.impl.StandardPlural;
+import com.ibm.icu.impl.number.FormatQuantity;
+import com.ibm.icu.impl.number.Modifier;
+import com.ibm.icu.impl.number.modifiers.SimpleModifier;
+import com.ibm.icu.text.MeasureFormat.FormatWidth;
+import com.ibm.icu.text.NumberFormat.Field;
+import com.ibm.icu.text.PluralRules;
+import com.ibm.icu.util.Currency;
+import com.ibm.icu.util.MeasureUnit;
+import com.ibm.icu.util.ULocale;
+
+import newapi.impl.MeasureData;
+import newapi.impl.MicroProps;
+import newapi.impl.QuantityChain;
+
+class MurkyLongNameHandler implements QuantityChain {
+
+    private final Map<StandardPlural, Modifier> data;
+    /* unsafe */ PluralRules rules;
+    /* unsafe */ QuantityChain parent;
+
+    private MurkyLongNameHandler(Map<StandardPlural, Modifier> data) {
+        this.data = data;
+    }
+
+    public static MurkyLongNameHandler getCurrencyLongNameModifiers(ULocale loc, Currency currency) {
+        Map<String, String> data = CurrencyData.provider.getInstance(loc, true).getUnitPatterns();
+        Map<StandardPlural, Modifier> result = new EnumMap<StandardPlural, Modifier>(StandardPlural.class);
+        StringBuilder sb = new StringBuilder();
+        for (Map.Entry<String, String> e : data.entrySet()) {
+            String pluralKeyword = e.getKey();
+            StandardPlural plural = StandardPlural.fromString(e.getKey());
+            String longName = currency.getName(loc, Currency.PLURAL_LONG_NAME, pluralKeyword, null);
+            String simpleFormat = e.getValue(); // e.g., "{0} {1}"
+            simpleFormat = simpleFormat.replace("{1}", longName);
+            String compiled = SimpleFormatterImpl.compileToStringMinMaxArguments(simpleFormat, sb, 1, 1);
+            Modifier mod = new SimpleModifier(compiled, Field.CURRENCY, false);
+            result.put(plural, mod);
+        }
+        return new MurkyLongNameHandler(result);
+    }
+
+    public static MurkyLongNameHandler getMeasureUnitModifiers(ULocale loc, MeasureUnit unit, FormatWidth width) {
+        Map<StandardPlural, String> simpleFormats = MeasureData.getMeasureData(loc, unit, width);
+        Map<StandardPlural, Modifier> result = new EnumMap<StandardPlural, Modifier>(StandardPlural.class);
+        StringBuilder sb = new StringBuilder();
+        for (StandardPlural plural : StandardPlural.VALUES) {
+            if (simpleFormats.get(plural) == null) {
+                plural = StandardPlural.OTHER;
+            }
+            String simpleFormat = simpleFormats.get(plural);
+            String compiled = SimpleFormatterImpl.compileToStringMinMaxArguments(simpleFormat, sb, 1, 1);
+            Modifier mod = new SimpleModifier(compiled, Field.CURRENCY, false);
+            result.put(plural, mod);
+        }
+        return new MurkyLongNameHandler(result);
+    }
+
+    public QuantityChain withLocaleData(PluralRules rules, boolean safe, QuantityChain parent) {
+        if (safe) {
+            // Safe code path: return a new object
+            return new ImmutableLongNameHandler(data, rules, parent);
+        } else {
+            // Unsafe code path: re-use this object!
+            this.rules = rules;
+            this.parent = parent;
+            return this;
+        }
+    }
+
+    @Override
+    public MicroProps withQuantity(FormatQuantity quantity) {
+        MicroProps micros = parent.withQuantity(quantity);
+        // TODO: Avoid the copy here?
+        FormatQuantity copy = quantity.createCopy();
+        micros.rounding.apply(copy);
+        micros.modOuter = data.get(copy.getStandardPlural(rules));
+        return micros;
+    }
+
+    public static class ImmutableLongNameHandler implements QuantityChain {
+        final Map<StandardPlural, Modifier> data;
+        final PluralRules rules;
+        final QuantityChain parent;
+
+        public ImmutableLongNameHandler(Map<StandardPlural, Modifier> data, PluralRules rules, QuantityChain parent) {
+            this.data = data;
+            this.rules = rules;
+            this.parent = parent;
+        }
+
+        @Override
+        public MicroProps withQuantity(FormatQuantity quantity) {
+            MicroProps micros = parent.withQuantity(quantity);
+            // TODO: Avoid the copy here?
+            FormatQuantity copy = quantity.createCopy();
+            micros.rounding.apply(copy);
+            micros.modOuter = data.get(copy.getStandardPlural(rules));
+            return micros;
+        }
+    }
+}
diff --git a/icu4j/main/classes/core/src/newapi/MurkyModifier.java b/icu4j/main/classes/core/src/newapi/MurkyModifier.java
new file mode 100644 (file)
index 0000000..2ef3c2f
--- /dev/null
@@ -0,0 +1,473 @@
+// © 2017 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html#License
+package newapi;
+
+import com.ibm.icu.impl.StandardPlural;
+import com.ibm.icu.impl.number.AffixPatternUtils;
+import com.ibm.icu.impl.number.AffixPatternUtils.SymbolProvider;
+import com.ibm.icu.impl.number.FormatQuantity;
+import com.ibm.icu.impl.number.LdmlPatternInfo;
+import com.ibm.icu.impl.number.Modifier;
+import com.ibm.icu.impl.number.NumberStringBuilder;
+import com.ibm.icu.impl.number.modifiers.ConstantMultiFieldModifier;
+import com.ibm.icu.impl.number.modifiers.CurrencySpacingEnabledModifier;
+import com.ibm.icu.text.DecimalFormatSymbols;
+import com.ibm.icu.text.MeasureFormat.FormatWidth;
+import com.ibm.icu.text.PluralRules;
+import com.ibm.icu.util.Currency;
+
+import newapi.NumberFormatter.SignDisplay;
+import newapi.impl.AffixPatternProvider;
+import newapi.impl.MicroProps;
+import newapi.impl.QuantityChain;
+
+/**
+ * This class is a {@link Modifier} that wraps a decimal format pattern. It applies the pattern's affixes in
+ * {@link Modifier#apply}.
+ *
+ * <p>
+ * In addition to being a Modifier, this class contains the business logic for substituting the correct locale symbols
+ * into the affixes of the decimal format pattern.
+ *
+ * <p>
+ * In order to use this class, create a new instance and call the following four setters: {@link #setPatternInfo},
+ * {@link #setPatternAttributes}, {@link #setSymbols}, and {@link #setNumberProperties}. After calling these four
+ * setters, the instance will be ready for use as a Modifier.
+ *
+ * <p>
+ * This is a MUTABLE, NON-THREAD-SAFE class designed for performance. Do NOT save references to this or attempt to use
+ * it from multiple threads! Instead, you can obtain a safe, immutable decimal format pattern modifier by calling
+ * {@link MurkyModifier#createImmutable}, in effect treating this instance as a builder for the immutable variant.
+ *
+ * FIXME: Make this package-private
+ */
+public class MurkyModifier implements Modifier, SymbolProvider, CharSequence, QuantityChain {
+
+    // Modifier details
+    final boolean isStrong;
+
+    // Pattern details
+    AffixPatternProvider patternInfo;
+    SignDisplay signDisplay;
+    boolean perMilleReplacesPercent;
+
+    // Symbol details
+    DecimalFormatSymbols symbols;
+    FormatWidth unitWidth;
+    String currency1;
+    String currency2;
+    String[] currency3;
+    PluralRules rules;
+
+    // Number details
+    boolean isNegative;
+    StandardPlural plural;
+
+    // QuantityChain details
+    QuantityChain parent;
+
+    // Transient CharSequence fields
+    boolean inCharSequenceMode;
+    int flags;
+    int length;
+    boolean prependSign;
+    boolean plusReplacesMinusSign;
+
+    /**
+     * @param isStrong
+     *            Whether the modifier should be considered strong. For more information, see
+     *            {@link Modifier#isStrong()}. Most of the time, decimal format pattern modifiers should be considered
+     *            as non-strong.
+     */
+    public MurkyModifier(boolean isStrong) {
+        this.isStrong = isStrong;
+    }
+
+    /**
+     * Sets a reference to the parsed decimal format pattern, usually obtained from
+     * {@link LdmlPatternInfo#parse(String)}, but any implementation of {@link AffixPatternProvider} is accepted.
+     */
+    public void setPatternInfo(AffixPatternProvider patternInfo) {
+        this.patternInfo = patternInfo;
+    }
+
+    /**
+     * Sets attributes that imply changes to the literal interpretation of the pattern string affixes.
+     *
+     * @param signDisplay
+     *            Whether to force a plus sign on positive numbers.
+     * @param perMille
+     *            Whether to substitute the percent sign in the pattern with a permille sign.
+     */
+    public void setPatternAttributes(SignDisplay signDisplay, boolean perMille) {
+        this.signDisplay = signDisplay;
+        this.perMilleReplacesPercent = perMille;
+    }
+
+    /**
+     * Sets locale-specific details that affect the symbols substituted into the pattern string affixes.
+     *
+     * @param symbols
+     *            The desired instance of DecimalFormatSymbols.
+     * @param currency
+     *            The currency to be used when substituting currency values into the affixes. Cannot be null, but a
+     *            bogus currency like "XXX" can be used.
+     * @param unitWidth
+     *            The width used to render currencies.
+     * @param rules
+     *            Required if the triple currency sign, "¤¤¤", appears in the pattern, which can be determined from the
+     *            convenience method {@link #needsPlurals()}.
+     */
+    public void setSymbols(DecimalFormatSymbols symbols, Currency currency, FormatWidth unitWidth, PluralRules rules) {
+        assert (rules != null) == needsPlurals();
+        this.symbols = symbols;
+        this.unitWidth = unitWidth;
+        this.rules = rules;
+
+        currency1 = currency.getName(symbols.getULocale(), Currency.SYMBOL_NAME, null);
+        currency2 = currency.getCurrencyCode();
+
+        if (rules != null) {
+            currency3 = new String[StandardPlural.COUNT];
+            for (StandardPlural plural : StandardPlural.VALUES) {
+                currency3[plural.ordinal()] = currency.getName(symbols.getULocale(), Currency.PLURAL_LONG_NAME,
+                        plural.getKeyword(), null);
+            }
+        }
+    }
+
+    /**
+     * Sets attributes of the current number being processed.
+     *
+     * @param isNegative
+     *            Whether the number is negative.
+     * @param plural
+     *            The plural form of the number, required only if the pattern contains the triple currency sign, "¤¤¤"
+     *            (and as indicated by {@link #needsPlurals()}).
+     */
+    public void setNumberProperties(boolean isNegative, StandardPlural plural) {
+        assert (plural != null) == needsPlurals();
+        this.isNegative = isNegative;
+        this.plural = plural;
+    }
+
+    /**
+     * Returns true if the pattern represented by this MurkyModifier requires a plural keyword in order to localize.
+     * This is currently true only if there is a currency long name placeholder in the pattern ("¤¤¤").
+     */
+    public boolean needsPlurals() {
+        return patternInfo.containsSymbolType(AffixPatternUtils.TYPE_CURRENCY_TRIPLE);
+    }
+
+    /**
+     * Creates a new quantity-dependent Modifier that behaves the same as the current instance, but which is immutable
+     * and can be saved for future use. The number properties in the current instance are mutated; all other properties
+     * are left untouched.
+     *
+     * <p>
+     * The resulting modifier cannot be used in a QuantityChain.
+     *
+     * @return An immutable that supports both positive and negative numbers.
+     */
+    public ImmutableMurkyModifier createImmutable() {
+        return createImmutableAndChain(null);
+    }
+
+    /**
+     * Creates a new quantity-dependent Modifier that behaves the same as the current instance, but which is immutable
+     * and can be saved for future use. The number properties in the current instance are mutated; all other properties
+     * are left untouched.
+     *
+     * @param parent
+     *            The QuantityChain to which to chain this immutable.
+     * @return An immutable that supports both positive and negative numbers.
+     */
+    public ImmutableMurkyModifier createImmutableAndChain(QuantityChain parent) {
+        NumberStringBuilder a = new NumberStringBuilder();
+        NumberStringBuilder b = new NumberStringBuilder();
+        if (needsPlurals()) {
+            // Slower path when we require the plural keyword.
+            Modifier[] mods = new Modifier[ImmutableMurkyModifierWithPlurals.getModsLength()];
+            for (StandardPlural plural : StandardPlural.VALUES) {
+                setNumberProperties(false, plural);
+                Modifier positive = createConstantModifier(a, b);
+                setNumberProperties(true, plural);
+                Modifier negative = createConstantModifier(a, b);
+                mods[ImmutableMurkyModifierWithPlurals.getModIndex(false, plural)] = positive;
+                mods[ImmutableMurkyModifierWithPlurals.getModIndex(true, plural)] = negative;
+            }
+            return new ImmutableMurkyModifierWithPlurals(mods, rules, parent);
+        } else {
+            // Faster path when plural keyword is not needed.
+            setNumberProperties(false, null);
+            Modifier positive = createConstantModifier(a, b);
+            setNumberProperties(true, null);
+            Modifier negative = createConstantModifier(a, b);
+            return new ImmutableMurkyModifierWithoutPlurals(positive, negative, parent);
+        }
+    }
+
+    private Modifier createConstantModifier(NumberStringBuilder a, NumberStringBuilder b) {
+        insertPrefix(a.clear(), 0);
+        insertSuffix(b.clear(), 0);
+        if (patternInfo.hasCurrencySign()) {
+            return new CurrencySpacingEnabledModifier(a, b, isStrong, symbols);
+        } else {
+            return new ConstantMultiFieldModifier(a, b, isStrong);
+        }
+    }
+
+    public static interface ImmutableMurkyModifier extends QuantityChain {
+        public void applyToMicros(MicroProps micros, FormatQuantity quantity);
+    }
+
+    public static class ImmutableMurkyModifierWithoutPlurals implements ImmutableMurkyModifier {
+        final Modifier positive;
+        final Modifier negative;
+        final QuantityChain parent;
+
+        public ImmutableMurkyModifierWithoutPlurals(Modifier positive, Modifier negative, QuantityChain parent) {
+            this.positive = positive;
+            this.negative = negative;
+            this.parent = parent;
+        }
+
+        @Override
+        public MicroProps withQuantity(FormatQuantity quantity) {
+            assert parent != null;
+            MicroProps micros = parent.withQuantity(quantity);
+            applyToMicros(micros, quantity);
+            return micros;
+        }
+
+        @Override
+        public void applyToMicros(MicroProps micros, FormatQuantity quantity) {
+            if (quantity.isNegative()) {
+                micros.modMiddle = negative;
+            } else {
+                micros.modMiddle = positive;
+            }
+        }
+    }
+
+    public static class ImmutableMurkyModifierWithPlurals implements ImmutableMurkyModifier {
+        final Modifier[] mods;
+        final PluralRules rules;
+        final QuantityChain parent;
+
+        public ImmutableMurkyModifierWithPlurals(Modifier[] mods, PluralRules rules, QuantityChain parent) {
+            assert mods.length == getModsLength();
+            assert rules != null;
+            this.mods = mods;
+            this.rules = rules;
+            this.parent = parent;
+        }
+
+        public static int getModsLength() {
+            return 2 * StandardPlural.COUNT;
+        }
+
+        public static int getModIndex(boolean isNegative, StandardPlural plural) {
+            return plural.ordinal() * 2 + (isNegative ? 1 : 0);
+        }
+
+        @Override
+        public MicroProps withQuantity(FormatQuantity quantity) {
+            assert parent != null;
+            MicroProps micros = parent.withQuantity(quantity);
+            applyToMicros(micros, quantity);
+            return micros;
+        }
+
+        @Override
+        public void applyToMicros(MicroProps micros, FormatQuantity quantity) {
+            // TODO: Fix this. Avoid the copy.
+            FormatQuantity copy = quantity.createCopy();
+            copy.roundToInfinity();
+            StandardPlural plural = copy.getStandardPlural(rules);
+            Modifier mod = mods[getModIndex(quantity.isNegative(), plural)];
+            micros.modMiddle = mod;
+        }
+    }
+
+    public QuantityChain addToChain(QuantityChain parent) {
+        this.parent = parent;
+        return this;
+    }
+
+    @Override
+    public MicroProps withQuantity(FormatQuantity fq) {
+        MicroProps micros = parent.withQuantity(fq);
+        if (needsPlurals()) {
+            // TODO: Fix this. Avoid the copy.
+            FormatQuantity copy = fq.createCopy();
+            micros.rounding.apply(copy);
+            setNumberProperties(fq.isNegative(), copy.getStandardPlural(rules));
+        } else {
+            setNumberProperties(fq.isNegative(), null);
+        }
+        micros.modMiddle = this;
+        return micros;
+    }
+
+    @Override
+    public int apply(NumberStringBuilder output, int leftIndex, int rightIndex) {
+        int prefixLen = insertPrefix(output, leftIndex);
+        int suffixLen = insertSuffix(output, rightIndex + prefixLen);
+        CurrencySpacingEnabledModifier.applyCurrencySpacing(output, leftIndex, prefixLen, rightIndex + prefixLen,
+                suffixLen, symbols);
+        return prefixLen + suffixLen;
+    }
+
+    @Override
+    public boolean isStrong() {
+        return isStrong;
+    }
+
+    @Override
+    public String getPrefix() {
+        NumberStringBuilder sb = new NumberStringBuilder(10);
+        insertPrefix(sb, 0);
+        return sb.toString();
+    }
+
+    @Override
+    public String getSuffix() {
+        NumberStringBuilder sb = new NumberStringBuilder(10);
+        insertSuffix(sb, 0);
+        return sb.toString();
+    }
+
+    private int insertPrefix(NumberStringBuilder sb, int position) {
+        enterCharSequenceMode(true);
+        int length = AffixPatternUtils.unescape(this, sb, position, this);
+        exitCharSequenceMode();
+        return length;
+    }
+
+    private int insertSuffix(NumberStringBuilder sb, int position) {
+        enterCharSequenceMode(false);
+        int length = AffixPatternUtils.unescape(this, sb, position, this);
+        exitCharSequenceMode();
+        return length;
+    }
+
+    @Override
+    public CharSequence getSymbol(int type) {
+        switch (type) {
+        case AffixPatternUtils.TYPE_MINUS_SIGN:
+            return symbols.getMinusSignString();
+        case AffixPatternUtils.TYPE_PLUS_SIGN:
+            return symbols.getPlusSignString();
+        case AffixPatternUtils.TYPE_PERCENT:
+            return symbols.getPercentString();
+        case AffixPatternUtils.TYPE_PERMILLE:
+            return symbols.getPerMillString();
+        case AffixPatternUtils.TYPE_CURRENCY_SINGLE:
+            // FormatWidth ISO overrides the singular currency symbol
+            if (unitWidth == FormatWidth.SHORT) {
+                return currency2;
+            } else {
+                return currency1;
+            }
+        case AffixPatternUtils.TYPE_CURRENCY_DOUBLE:
+            return currency2;
+        case AffixPatternUtils.TYPE_CURRENCY_TRIPLE:
+            // NOTE: This is the code path only for patterns containing "".
+            // Most plural currencies are formatted in DataUtils.
+            assert plural != null;
+            if (currency3 == null) {
+                return currency2;
+            } else {
+                return currency3[plural.ordinal()];
+            }
+        case AffixPatternUtils.TYPE_CURRENCY_QUAD:
+            return "\uFFFD";
+        case AffixPatternUtils.TYPE_CURRENCY_QUINT:
+            return "\uFFFD";
+        default:
+            throw new AssertionError();
+        }
+    }
+
+    /** This method contains the heart of the logic for rendering LDML affix strings. */
+    private void enterCharSequenceMode(boolean isPrefix) {
+        assert !inCharSequenceMode;
+        inCharSequenceMode = true;
+
+        // Should the output render '+' where '-' would normally appear in the pattern?
+        plusReplacesMinusSign = !isNegative && signDisplay == SignDisplay.ALWAYS
+                && patternInfo.positiveHasPlusSign() == false;
+
+        // Should we use the negative affix pattern? (If not, we will use the positive one)
+        boolean useNegativeAffixPattern = patternInfo.hasNegativeSubpattern()
+                && (isNegative || (patternInfo.negativeHasMinusSign() && plusReplacesMinusSign));
+
+        // Resolve the flags for the affix pattern.
+        flags = 0;
+        if (useNegativeAffixPattern) {
+            flags |= AffixPatternProvider.Flags.NEGATIVE_SUBPATTERN;
+        }
+        if (isPrefix) {
+            flags |= AffixPatternProvider.Flags.PREFIX;
+        }
+        if (plural != null) {
+            assert plural.ordinal() == (AffixPatternProvider.Flags.PLURAL_MASK & plural.ordinal());
+            flags |= plural.ordinal();
+        }
+
+        // Should we prepend a sign to the pattern?
+        if (!isPrefix || useNegativeAffixPattern) {
+            prependSign = false;
+        } else if (isNegative) {
+            prependSign = signDisplay != SignDisplay.NEVER;
+        } else {
+            prependSign = plusReplacesMinusSign;
+        }
+
+        // Finally, compute the length of the affix pattern.
+        length = patternInfo.length(flags) + (prependSign ? 1 : 0);
+    }
+
+    private void exitCharSequenceMode() {
+        assert inCharSequenceMode;
+        inCharSequenceMode = false;
+    }
+
+    @Override
+    public int length() {
+        if (inCharSequenceMode) {
+            return length;
+        } else {
+            NumberStringBuilder sb = new NumberStringBuilder(20);
+            apply(sb, 0, 0);
+            return sb.length();
+        }
+    }
+
+    @Override
+    public char charAt(int index) {
+        assert inCharSequenceMode;
+        char candidate;
+        if (prependSign && index == 0) {
+            candidate = '-';
+        } else if (prependSign) {
+            candidate = patternInfo.charAt(flags, index - 1);
+        } else {
+            candidate = patternInfo.charAt(flags, index);
+        }
+        if (plusReplacesMinusSign && candidate == '-') {
+            return '+';
+        }
+        if (perMilleReplacesPercent && candidate == '%') {
+            return '‰';
+        }
+        return candidate;
+    }
+
+    @Override
+    public CharSequence subSequence(int start, int end) {
+        // Should never be called in normal circumstances
+        throw new AssertionError();
+    }
+}
diff --git a/icu4j/main/classes/core/src/newapi/Notation.java b/icu4j/main/classes/core/src/newapi/Notation.java
new file mode 100644 (file)
index 0000000..0487baa
--- /dev/null
@@ -0,0 +1,40 @@
+// © 2017 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html#License
+package newapi;
+
+import com.ibm.icu.text.CompactDecimalFormat.CompactStyle;
+
+import newapi.NumberFormatter.SignDisplay;
+
+public class Notation {
+
+    // FIXME: Support engineering intervals other than 3?
+    private static final ScientificNotation SCIENTIFIC = new ScientificNotation(1, false, 1, SignDisplay.AUTO);
+    private static final ScientificNotation ENGINEERING = new ScientificNotation(3, false, 1, SignDisplay.AUTO);
+    private static final CompactNotation COMPACT_SHORT = new CompactNotation(CompactStyle.SHORT);
+    private static final CompactNotation COMPACT_LONG = new CompactNotation(CompactStyle.LONG);
+    private static final SimpleNotation SIMPLE = new SimpleNotation();
+
+    /* package-private */ Notation() {
+    }
+
+    public static ScientificNotation scientific() {
+        return SCIENTIFIC;
+    }
+
+    public static ScientificNotation engineering() {
+        return ENGINEERING;
+    }
+
+    public static CompactNotation compactShort() {
+        return COMPACT_SHORT;
+    }
+
+    public static CompactNotation compactLong() {
+        return COMPACT_LONG;
+    }
+
+    public static SimpleNotation simple() {
+        return SIMPLE;
+    }
+}
\ No newline at end of file
index 80f7138f54c66a5126a6a3bb5e02f28c42824eee..c3abf489e40481adbf2f7d99a9c7eb8e386bc3e7 100644 (file)
@@ -2,53 +2,14 @@
 // License & terms of use: http://www.unicode.org/copyright.html#License
 package newapi;
 
-import java.io.IOException;
-import java.math.BigDecimal;
-import java.math.MathContext;
-import java.math.RoundingMode;
-import java.text.AttributedCharacterIterator;
-import java.text.FieldPosition;
-import java.util.Arrays;
 import java.util.Locale;
 
-import com.ibm.icu.impl.number.FormatQuantity;
-import com.ibm.icu.impl.number.NumberStringBuilder;
-import com.ibm.icu.impl.number.ThingsNeedingNewHome.PadPosition;
-import com.ibm.icu.text.CompactDecimalFormat.CompactStyle;
-import com.ibm.icu.text.DecimalFormatSymbols;
-import com.ibm.icu.text.MeasureFormat.FormatWidth;
-import com.ibm.icu.text.NumberingSystem;
-import com.ibm.icu.text.PluralRules.IFixedDecimal;
-import com.ibm.icu.util.Currency;
-import com.ibm.icu.util.Currency.CurrencyUsage;
-import com.ibm.icu.util.CurrencyAmount;
-import com.ibm.icu.util.ICUUncheckedIOException;
-import com.ibm.icu.util.Measure;
 import com.ibm.icu.util.MeasureUnit;
 import com.ibm.icu.util.ULocale;
 
-import newapi.impl.GroupingImpl;
-import newapi.impl.IntegerWidthImpl;
-import newapi.impl.MicroProps;
-import newapi.impl.NotationImpl.NotationCompactImpl;
-import newapi.impl.NotationImpl.NotationScientificImpl;
-import newapi.impl.NumberFormatterImpl;
-import newapi.impl.PaddingImpl;
-import newapi.impl.RoundingImpl.RoundingImplCurrency;
-import newapi.impl.RoundingImpl.RoundingImplFraction;
-import newapi.impl.RoundingImpl.RoundingImplIncrement;
-import newapi.impl.RoundingImpl.RoundingImplInfinity;
-import newapi.impl.RoundingImpl.RoundingImplSignificant;
-
 public final class NumberFormatter {
 
-  public interface IRounding {
-    public BigDecimal round(BigDecimal input);
-  }
-
-  public interface IGrouping {
-    public boolean groupAtPosition(int position, BigDecimal input);
-  }
+  private static final UnlocalizedNumberFormatter BASE = new UnlocalizedNumberFormatter();
 
   // This could possibly be combined into MeasureFormat.FormatWidth
   public static enum CurrencyDisplay {
@@ -71,626 +32,20 @@ public final class NumberFormatter {
     NEVER,
   }
 
-  public static class UnlocalizedNumberFormatter {
-
-    public UnlocalizedNumberFormatter notation(Notation notation) {
-      throw new AssertionError("See NumberFormatterImpl");
-    }
-
-    public UnlocalizedNumberFormatter unit(MeasureUnit unit) {
-      throw new AssertionError("See NumberFormatterImpl");
-    }
-
-    public UnlocalizedNumberFormatter rounding(IRounding rounding) {
-      throw new AssertionError("See NumberFormatterImpl");
-    }
-
-    public UnlocalizedNumberFormatter grouping(IGrouping grouping) {
-      throw new AssertionError("See NumberFormatterImpl");
-    }
-
-    public UnlocalizedNumberFormatter padding(Padding padding) {
-      throw new AssertionError("See NumberFormatterImpl");
-    }
-
-    public UnlocalizedNumberFormatter integerWidth(IntegerWidth style) {
-      throw new AssertionError("See NumberFormatterImpl");
-    }
-
-    public UnlocalizedNumberFormatter symbols(DecimalFormatSymbols symbols) {
-      throw new AssertionError("See NumberFormatterImpl");
-    }
-
-    public UnlocalizedNumberFormatter symbols(NumberingSystem ns) {
-      throw new AssertionError("See NumberFormatterImpl");
-    }
-
-    public UnlocalizedNumberFormatter unitWidth(FormatWidth style) {
-      throw new AssertionError("See NumberFormatterImpl");
-    }
-
-    public UnlocalizedNumberFormatter sign(SignDisplay style) {
-      throw new AssertionError("See NumberFormatterImpl");
-    }
-
-    public UnlocalizedNumberFormatter decimal(DecimalMarkDisplay style) {
-      throw new AssertionError("See NumberFormatterImpl");
-    }
-
-    public LocalizedNumberFormatter locale(Locale locale) {
-      throw new AssertionError("See NumberFormatterImpl");
-    }
-
-    public LocalizedNumberFormatter locale(ULocale locale) {
-      throw new AssertionError("See NumberFormatterImpl");
-    }
-
-    public String toSkeleton() {
-      throw new AssertionError("See NumberFormatterImpl");
-    }
-
-    // Prevent external subclassing with private constructor
-    private UnlocalizedNumberFormatter() {}
-  }
-
-  public static class LocalizedNumberFormatter extends UnlocalizedNumberFormatter {
-
-    @Override
-    public UnlocalizedNumberFormatter notation(Notation notation) {
-      throw new AssertionError("See NumberFormatterImpl");
-    }
-
-    @Override
-    public LocalizedNumberFormatter unit(MeasureUnit unit) {
-      throw new AssertionError("See NumberFormatterImpl");
-    }
-
-    @Override
-    public LocalizedNumberFormatter rounding(IRounding rounding) {
-      throw new AssertionError("See NumberFormatterImpl");
-    }
-
-    @Override
-    public LocalizedNumberFormatter grouping(IGrouping grouping) {
-      throw new AssertionError("See NumberFormatterImpl");
-    }
-
-    @Override
-    public LocalizedNumberFormatter padding(Padding padding) {
-      throw new AssertionError("See NumberFormatterImpl");
-    }
-
-    @Override
-    public LocalizedNumberFormatter integerWidth(IntegerWidth style) {
-      throw new AssertionError("See NumberFormatterImpl");
-    }
-
-    @Override
-    public LocalizedNumberFormatter symbols(DecimalFormatSymbols symbols) {
-      throw new AssertionError("See NumberFormatterImpl");
-    }
-
-    @Override
-    public LocalizedNumberFormatter symbols(NumberingSystem ns) {
-      throw new AssertionError("See NumberFormatterImpl");
-    }
-
-    @Override
-    public LocalizedNumberFormatter unitWidth(FormatWidth style) {
-      throw new AssertionError("See NumberFormatterImpl");
-    }
-
-    @Override
-    public LocalizedNumberFormatter sign(SignDisplay style) {
-      throw new AssertionError("See NumberFormatterImpl");
-    }
-
-    @Override
-    public LocalizedNumberFormatter decimal(DecimalMarkDisplay style) {
-      throw new AssertionError("See NumberFormatterImpl");
-    }
-
-    public NumberFormatterResult format(long input) {
-      throw new AssertionError("See NumberFormatterImpl");
-    }
-
-    public NumberFormatterResult format(double input) {
-      throw new AssertionError("See NumberFormatterImpl");
-    }
-
-    public NumberFormatterResult format(Number input) {
-      throw new AssertionError("See NumberFormatterImpl");
-    }
-
-    public NumberFormatterResult format(Measure input) {
-      throw new AssertionError("See NumberFormatterImpl");
-    }
-
-    // Prevent external subclassing with private constructor
-    private LocalizedNumberFormatter() {}
-
-    /**
-     * @internal
-     * @deprecated This API is for ICU internal use only.
-     */
-    @Deprecated
-    public static class Internal extends LocalizedNumberFormatter {}
-  }
-
   public static UnlocalizedNumberFormatter fromSkeleton(String skeleton) {
     // FIXME
     throw new UnsupportedOperationException();
   }
 
   public static UnlocalizedNumberFormatter with() {
-    return NumberFormatterImpl.with();
+    return BASE;
   }
 
   public static LocalizedNumberFormatter withLocale(Locale locale) {
-    return NumberFormatterImpl.with().locale(locale);
+    return BASE.locale(locale);
   }
 
   public static LocalizedNumberFormatter withLocale(ULocale locale) {
-    return NumberFormatterImpl.with().locale(locale);
-  }
-
-  public static class NumberFormatterResult {
-    NumberStringBuilder nsb;
-    FormatQuantity fq;
-    MicroProps micros;
-
-    /**
-     * @internal
-     * @deprecated This API is ICU internal only.
-     */
-    @Deprecated
-    public NumberFormatterResult(NumberStringBuilder nsb, FormatQuantity fq, MicroProps micros) {
-      this.nsb = nsb;
-      this.fq = fq;
-      this.micros = micros;
-    }
-
-    @Override
-    public String toString() {
-      return nsb.toString();
-    }
-
-    public <A extends Appendable> A appendTo(A appendable) {
-      try {
-        appendable.append(nsb);
-      } catch (IOException e) {
-        // Throw as an unchecked exception to avoid users needing try/catch
-        throw new ICUUncheckedIOException(e);
-      }
-      return appendable;
-    }
-
-    public AttributedCharacterIterator toAttributedCharacterIterator() {
-      return nsb.getIterator();
-    }
-
-    /**
-     * @internal
-     * @deprecated This API a technology preview. It is not stable and may change or go away in an
-     *     upcoming release.
-     */
-    @Deprecated
-    public void populateFieldPosition(FieldPosition fieldPosition, int offset) {
-      nsb.populateFieldPosition(fieldPosition, offset);
-      fq.populateUFieldPosition(fieldPosition);
-    }
-
-    /**
-     * @internal
-     * @deprecated This API a technology preview. It is not stable and may change or go away in an
-     *     upcoming release.
-     */
-    @Deprecated
-    public String getPrefix() {
-      return micros.modOuter.getPrefix()
-          + micros.modMiddle.getPrefix()
-          + micros.modInner.getPrefix();
-    }
-
-    /**
-     * @internal
-     * @deprecated This API a technology preview. It is not stable and may change or go away in an
-     *     upcoming release.
-     */
-    @Deprecated
-    public String getSuffix() {
-      return micros.modInner.getSuffix()
-          + micros.modMiddle.getSuffix()
-          + micros.modOuter.getSuffix();
-    }
-
-    /**
-     * @internal
-     * @deprecated This API a technology preview. It is not stable and may change or go away in an
-     *     upcoming release.
-     */
-    @Deprecated
-    public IFixedDecimal getFixedDecimal() {
-      return fq;
-    }
-
-    public BigDecimal toBigDecimal() {
-      return fq.toBigDecimal();
-    }
-
-    @Override
-    public int hashCode() {
-      // NumberStringBuilder and BigDecimal are mutable, so we can't call
-      // #equals() or #hashCode() on them directly.
-      return Arrays.hashCode(nsb.toCharArray())
-          ^ Arrays.hashCode(nsb.toFieldArray())
-          ^ fq.toBigDecimal().hashCode();
-    }
-
-    @Override
-    public boolean equals(Object other) {
-      if (this == other) return true;
-      if (other == null) return false;
-      if (!(other instanceof NumberFormatterResult)) return false;
-      // NumberStringBuilder and BigDecimal are mutable, so we can't call
-      // #equals() or #hashCode() on them directly.
-      NumberFormatterResult _other = (NumberFormatterResult) other;
-      return Arrays.equals(nsb.toCharArray(), _other.nsb.toCharArray())
-          ^ Arrays.equals(nsb.toFieldArray(), _other.nsb.toFieldArray())
-          ^ fq.toBigDecimal().equals(_other.fq.toBigDecimal());
-    }
-  }
-
-  public static class Notation {
-
-    // FIXME: Support engineering intervals other than 3?
-    public static final NotationScientific SCIENTIFIC = new NotationScientificImpl(1);
-    public static final NotationScientific ENGINEERING = new NotationScientificImpl(3);
-    public static final NotationCompact COMPACT_SHORT = new NotationCompactImpl(CompactStyle.SHORT);
-    public static final NotationCompact COMPACT_LONG = new NotationCompactImpl(CompactStyle.LONG);
-    public static final NotationSimple SIMPLE = new NotationSimple();
-
-    // Prevent subclassing
-    private Notation() {}
-  }
-
-  @SuppressWarnings("unused")
-  public static class NotationScientific extends Notation {
-
-    public NotationScientific withMinExponentDigits(int minExponentDigits) {
-      // Overridden in NotationImpl
-      throw new AssertionError();
-    }
-
-    public NotationScientific withExponentSignDisplay(SignDisplay exponentSignDisplay) {
-      // Overridden in NotationImpl
-      throw new AssertionError();
-    }
-
-    // Prevent subclassing
-    private NotationScientific() {}
-
-    /**
-     * @internal
-     * @deprecated This API is for ICU internal use only.
-     */
-    @Deprecated
-    public static class Internal extends NotationScientific {}
-  }
-
-  public static class NotationCompact extends Notation {
-
-    // Prevent subclassing
-    private NotationCompact() {}
-
-    /**
-     * @internal
-     * @deprecated This API is for ICU internal use only.
-     */
-    @Deprecated
-    public static class Internal extends NotationCompact {}
-  }
-
-  public static class NotationSimple extends Notation {
-    // Prevent subclassing
-    private NotationSimple() {}
-  }
-
-  public static class Rounding implements IRounding {
-
-    // FIXME
-    /** @internal */
-    public static final int MAX_VALUE = 100;
-
-    public static final Rounding NONE = new RoundingImplInfinity();
-    public static final Rounding INTEGER = new RoundingImplFraction();
-
-    public static FractionRounding fixedFraction(int minMaxFrac) {
-      if (minMaxFrac >= 0 && minMaxFrac <= MAX_VALUE) {
-        return RoundingImplFraction.getInstance(minMaxFrac, minMaxFrac);
-      } else {
-        throw new IllegalArgumentException("Fraction length must be between 0 and " + MAX_VALUE);
-      }
-    }
-
-    public static FractionRounding minFraction(int minFrac) {
-      if (minFrac >= 0 && minFrac < MAX_VALUE) {
-        return RoundingImplFraction.getInstance(minFrac, Integer.MAX_VALUE);
-      } else {
-        throw new IllegalArgumentException("Fraction length must be between 0 and " + MAX_VALUE);
-      }
-    }
-
-    public static FractionRounding maxFraction(int maxFrac) {
-      if (maxFrac >= 0 && maxFrac < MAX_VALUE) {
-        return RoundingImplFraction.getInstance(0, maxFrac);
-      } else {
-        throw new IllegalArgumentException("Fraction length must be between 0 and " + MAX_VALUE);
-      }
-    }
-
-    public static FractionRounding minMaxFraction(int minFrac, int maxFrac) {
-      if (minFrac >= 0 && maxFrac <= MAX_VALUE && minFrac <= maxFrac) {
-        return RoundingImplFraction.getInstance(minFrac, maxFrac);
-      } else {
-        throw new IllegalArgumentException("Fraction length must be between 0 and " + MAX_VALUE);
-      }
-    }
-
-    public static Rounding fixedFigures(int minMaxSig) {
-      if (minMaxSig > 0 && minMaxSig <= MAX_VALUE) {
-        return RoundingImplSignificant.getInstance(minMaxSig, minMaxSig);
-      } else {
-        throw new IllegalArgumentException("Significant digits must be between 0 and " + MAX_VALUE);
-      }
-    }
-
-    public static Rounding minFigures(int minSig) {
-      if (minSig > 0 && minSig <= MAX_VALUE) {
-        return RoundingImplSignificant.getInstance(minSig, Integer.MAX_VALUE);
-      } else {
-        throw new IllegalArgumentException("Significant digits must be between 0 and " + MAX_VALUE);
-      }
-    }
-
-    public static Rounding maxFigures(int maxSig) {
-      if (maxSig > 0 && maxSig <= MAX_VALUE) {
-        return RoundingImplSignificant.getInstance(0, maxSig);
-      } else {
-        throw new IllegalArgumentException("Significant digits must be between 0 and " + MAX_VALUE);
-      }
-    }
-
-    public static Rounding minMaxFigures(int minSig, int maxSig) {
-      if (minSig > 0 && maxSig <= MAX_VALUE && minSig <= maxSig) {
-        return RoundingImplSignificant.getInstance(minSig, maxSig);
-      } else {
-        throw new IllegalArgumentException("Significant digits must be between 0 and " + MAX_VALUE);
-      }
-    }
-
-    public static Rounding increment(BigDecimal roundingIncrement) {
-      if (roundingIncrement == null) {
-        throw new IllegalArgumentException("Rounding increment must be non-null");
-      } else if (roundingIncrement.compareTo(BigDecimal.ZERO) <= 0) {
-        throw new IllegalArgumentException("Rounding increment must be positive");
-      } else {
-        return RoundingImplIncrement.getInstance(roundingIncrement);
-      }
-    }
-
-    public static CurrencyRounding currency(CurrencyUsage currencyUsage) {
-      if (currencyUsage != CurrencyUsage.STANDARD && currencyUsage != CurrencyUsage.CASH) {
-        throw new IllegalArgumentException("Unknown CurrencyUsage: " + currencyUsage);
-      } else {
-        return RoundingImplCurrency.getInstance(currencyUsage);
-      }
-    }
-
-    /**
-     * Sets the {@link java.math.RoundingMode} to use when picking the direction to round (up or
-     * down).
-     *
-     * <p>Common values include {@link RoundingMode#HALF_EVEN}, {@link RoundingMode#HALF_UP}, and
-     * {@link RoundingMode#CEILING}. The default is HALF_EVEN.
-     *
-     * @param roundingMode The RoundingMode to use.
-     * @return An immutable object for chaining.
-     */
-    public Rounding withMode(RoundingMode roundingMode) {
-      // Overridden in RoundingImpl
-      throw new AssertionError();
-    }
-
-    /**
-     * Sets a MathContext directly instead of RoundingMode.
-     *
-     * @internal
-     * @deprecated This API is ICU internal only.
-     */
-    @Deprecated
-    public Rounding withMode(MathContext mathContext) {
-      // Overridden in RoundingImpl
-      throw new AssertionError();
-    }
-
-    @Override
-    public BigDecimal round(BigDecimal input) {
-      // Overridden in RoundingImpl
-      throw new AssertionError();
-    }
-
-    // Prevent subclassing
-    private Rounding() {}
-
-    /**
-     * @internal
-     * @deprecated This API is for ICU internal use only.
-     */
-    @Deprecated
-    public static class Internal extends Rounding {}
-  }
-
-  /**
-   * A rounding strategy based on a minimum and/or maximum number of fraction digits. Allows for a
-   * minimum or maximum number of significant digits to be specified.
-   */
-  public static class FractionRounding extends Rounding {
-    /**
-     * Ensures that no less than this number of significant figures are retained when rounding
-     * according to fraction rules.
-     *
-     * <p>For example, with integer rounding, the number 3.141 becomes "3". However, with minimum
-     * figures set to 2, 3.141 becomes "3.1" instead.
-     *
-     * <p>This setting does not affect the number of trailing zeros. For example, 3.01 would print
-     * as "3", not "3.0".
-     *
-     * @param minFigures The number of significant figures to guarantee.
-     * @return An immutable object for chaining.
-     */
-    public Rounding withMinFigures(int minFigures) {
-      // Overridden in RoundingImpl
-      throw new AssertionError();
-    }
-
-    /**
-     * Ensures that no more than this number of significant figures are retained when rounding
-     * according to fraction rules.
-     *
-     * <p>For example, with integer rounding, the number 123.4 becomes "123". However, with maximum
-     * figures set to 2, 123.4 becomes "120" instead.
-     *
-     * <p>This setting does not affect the number of trailing zeros. For example, with fixed
-     * fraction of 2, 123.4 would become "120.00".
-     *
-     * @param maxFigures
-     * @return An immutable object for chaining.
-     */
-    public Rounding withMaxFigures(int maxFigures) {
-      // Overridden in RoundingImpl
-      throw new AssertionError();
-    }
-
-    // Prevent subclassing
-    private FractionRounding() {}
-
-    /**
-     * @internal
-     * @deprecated This API is for ICU internal use only.
-     */
-    @Deprecated
-    public static class Internal extends FractionRounding {}
-  }
-
-  /** A rounding strategy parameterized by a currency. */
-  public static class CurrencyRounding extends Rounding {
-    /**
-     * Associates a {@link com.ibm.icu.util.Currency} with this rounding strategy. Only applies to
-     * rounding strategies returned from {@link #currency(CurrencyUsage)}.
-     *
-     * <p><strong>Calling this method is <em>not required</em></strong>, because the currency
-     * specified in {@link NumberFormatter#unit(MeasureUnit)} or via a {@link CurrencyAmount} passed
-     * into {@link LocalizedNumberFormatter#format(Measure)} is automatically applied to currency
-     * rounding strategies. However, this method enables you to override that automatic association.
-     *
-     * <p>This method also enables numbers to be formatted using currency rounding rules without
-     * explicitly using a currency format.
-     *
-     * @param currency The currency to associate with this rounding strategy.
-     * @return An immutable object for chaining.
-     */
-    public Rounding withCurrency(Currency currency) {
-      // Overridden in RoundingImpl
-      throw new AssertionError();
-    }
-
-    // Prevent subclassing
-    private CurrencyRounding() {}
-
-    /**
-     * @internal
-     * @deprecated This API is for ICU internal use only.
-     */
-    @Deprecated
-    public static class Internal extends CurrencyRounding {}
-  }
-
-  public static class Grouping implements IGrouping {
-
-    public static final Grouping DEFAULT = new GroupingImpl(GroupingImpl.TYPE_PLACEHOLDER);
-    public static final Grouping MIN_2_DIGITS = new GroupingImpl(GroupingImpl.TYPE_MIN2);
-    public static final Grouping NONE = new GroupingImpl(GroupingImpl.TYPE_NONE);
-
-    @Override
-    public boolean groupAtPosition(int position, BigDecimal input) {
-      throw new UnsupportedOperationException(
-          "This grouping strategy cannot be used outside of number formatting.");
-    }
-
-    // Prevent subclassing
-    private Grouping() {}
-
-    /**
-     * @internal
-     * @deprecated This API is for ICU internal use only.
-     */
-    @Deprecated
-    public static class Internal extends Grouping {}
-  }
-
-  public static class Padding {
-
-    public static final Padding NONE = new PaddingImpl();
-
-    public static Padding codePoints(int cp, int targetWidth, PadPosition position) {
-        // TODO: Validate the code point
-      if (targetWidth >= 0) {
-        String paddingString = String.valueOf(Character.toChars(cp));
-        return PaddingImpl.getInstance(paddingString, targetWidth, position);
-      } else {
-        throw new IllegalArgumentException("Padding width must not be negative");
-      }
-    }
-
-    // Prevent subclassing
-    private Padding() {}
-
-    /**
-     * @internal
-     * @deprecated This API is for ICU internal use only.
-     */
-    @Deprecated
-    public static class Internal extends Padding {}
-  }
-
-  @SuppressWarnings("unused")
-  public static class IntegerWidth {
-
-    public static final IntegerWidth DEFAULT = new IntegerWidthImpl();
-
-    public static IntegerWidth zeroFillTo(int minInt) {
-      if (minInt >= 0 && minInt < Rounding.MAX_VALUE) {
-        return new IntegerWidthImpl(minInt, Integer.MAX_VALUE);
-      } else {
-        throw new IllegalArgumentException(
-            "Integer digits must be between 0 and " + Rounding.MAX_VALUE);
-      }
-    }
-
-    public IntegerWidth truncateAt(int maxInt) {
-      // Implemented in IntegerWidthImpl
-      throw new AssertionError();
-    }
-
-    // Prevent subclassing
-    private IntegerWidth() {}
-
-    /**
-     * @internal
-     * @deprecated This API is for ICU internal use only.
-     */
-    @Deprecated
-    public static class Internal extends IntegerWidth {}
+    return BASE.locale(locale);
   }
 }
diff --git a/icu4j/main/classes/core/src/newapi/NumberFormatterSettings.java b/icu4j/main/classes/core/src/newapi/NumberFormatterSettings.java
new file mode 100644 (file)
index 0000000..112a309
--- /dev/null
@@ -0,0 +1,209 @@
+// © 2017 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html#License
+package newapi;
+
+import com.ibm.icu.text.DecimalFormatSymbols;
+import com.ibm.icu.text.MeasureFormat.FormatWidth;
+import com.ibm.icu.text.NumberingSystem;
+import com.ibm.icu.util.MeasureUnit;
+import com.ibm.icu.util.ULocale;
+
+import newapi.NumberFormatter.DecimalMarkDisplay;
+import newapi.NumberFormatter.SignDisplay;
+import newapi.impl.MacroProps;
+import newapi.impl.Padder;
+
+public abstract class NumberFormatterSettings<T extends NumberFormatterSettings<?>> {
+
+    static final int KEY_MACROS = 0;
+    static final int KEY_LOCALE = 1;
+    static final int KEY_NOTATION = 2;
+    static final int KEY_UNIT = 3;
+    static final int KEY_ROUNDER = 4;
+    static final int KEY_GROUPER = 5;
+    static final int KEY_PADDER = 6;
+    static final int KEY_INTEGER = 7;
+    static final int KEY_SYMBOLS = 8;
+    static final int KEY_UNIT_WIDTH = 9;
+    static final int KEY_SIGN = 10;
+    static final int KEY_DECIMAL = 11;
+    static final int KEY_THRESHOLD = 12;
+    static final int KEY_MAX = 13;
+
+    final NumberFormatterSettings<?> parent;
+    final int key;
+    final Object value;
+    volatile MacroProps resolvedMacros;
+
+    NumberFormatterSettings(NumberFormatterSettings<?> parent, int key, Object value) {
+        this.parent = parent;
+        this.key = key;
+        this.value = value;
+    }
+
+    public T notation(Notation notation) {
+        return create(KEY_NOTATION, notation);
+    }
+
+    public T unit(MeasureUnit unit) {
+        return create(KEY_UNIT, unit);
+    }
+
+    public T rounding(Rounder rounder) {
+        return create(KEY_ROUNDER, rounder);
+    }
+
+    public T grouping(Grouper grouper) {
+        return create(KEY_GROUPER, grouper);
+    }
+
+    public T integerWidth(IntegerWidth style) {
+        return create(KEY_INTEGER, style);
+    }
+
+    public T symbols(DecimalFormatSymbols symbols) {
+        return create(KEY_SYMBOLS, symbols);
+    }
+
+    public T symbols(NumberingSystem ns) {
+        return create(KEY_SYMBOLS, ns);
+    }
+
+    public T unitWidth(FormatWidth style) {
+        return create(KEY_UNIT_WIDTH, style);
+    }
+
+    public T sign(SignDisplay style) {
+        return create(KEY_SIGN, style);
+    }
+
+    public T decimal(DecimalMarkDisplay style) {
+        return create(KEY_DECIMAL, style);
+    }
+
+    /** Internal method to set a starting macros. */
+    public T macros(MacroProps macros) {
+        return create(KEY_MACROS, macros);
+    }
+
+    /** Non-public method */
+    public T padding(Padder padder) {
+        return create(KEY_PADDER, padder);
+    }
+
+    /**
+     * Internal fluent setter to support a custom regulation threshold. A threshold of 1 causes the data structures to
+     * be built right away. A threshold of 0 prevents the data structures from being built.
+     */
+    public T threshold(Long threshold) {
+        return create(KEY_THRESHOLD, threshold);
+    }
+
+    public String toSkeleton() {
+        return SkeletonBuilder.macrosToSkeleton(resolve());
+    }
+
+    abstract T create(int key, Object value);
+
+    MacroProps resolve() {
+        if (resolvedMacros != null) {
+            return resolvedMacros;
+        }
+        // Although the linked-list fluent storage approach requires this method,
+        // my benchmarks show that linked-list is still faster than a full clone
+        // of a MacroProps object at each step.
+        MacroProps macros = new MacroProps();
+        NumberFormatterSettings<?> current = this;
+        while (current != null) {
+            switch (current.key) {
+            case KEY_MACROS:
+                macros.fallback((MacroProps) current.value);
+                break;
+            case KEY_LOCALE:
+                if (macros.loc == null) {
+                    macros.loc = (ULocale) current.value;
+                }
+                break;
+            case KEY_NOTATION:
+                if (macros.notation == null) {
+                    macros.notation = (Notation) current.value;
+                }
+                break;
+            case KEY_UNIT:
+                if (macros.unit == null) {
+                    macros.unit = (MeasureUnit) current.value;
+                }
+                break;
+            case KEY_ROUNDER:
+                if (macros.rounder == null) {
+                    macros.rounder = (Rounder) current.value;
+                }
+                break;
+            case KEY_GROUPER:
+                if (macros.grouper == null) {
+                    macros.grouper = (Grouper) current.value;
+                }
+                break;
+            case KEY_PADDER:
+                if (macros.padder == null) {
+                    macros.padder = (Padder) current.value;
+                }
+                break;
+            case KEY_INTEGER:
+                if (macros.integerWidth == null) {
+                    macros.integerWidth = (IntegerWidth) current.value;
+                }
+                break;
+            case KEY_SYMBOLS:
+                if (macros.symbols == null) {
+                    macros.symbols = /* (Object) */ current.value;
+                }
+                break;
+            case KEY_UNIT_WIDTH:
+                if (macros.unitWidth == null) {
+                    macros.unitWidth = (FormatWidth) current.value;
+                }
+                break;
+            case KEY_SIGN:
+                if (macros.sign == null) {
+                    macros.sign = (SignDisplay) current.value;
+                }
+                break;
+            case KEY_DECIMAL:
+                if (macros.decimal == null) {
+                    macros.decimal = (DecimalMarkDisplay) current.value;
+                }
+                break;
+            case KEY_THRESHOLD:
+                if (macros.threshold == null) {
+                    macros.threshold = (Long) current.value;
+                }
+                break;
+            default:
+                throw new AssertionError("Unknown key: " + current.key);
+            }
+            current = current.parent;
+        }
+        resolvedMacros = macros;
+        return macros;
+    }
+
+    @Override
+    public int hashCode() {
+        return resolve().hashCode();
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (this == other) {
+            return true;
+        }
+        if (other == null) {
+            return false;
+        }
+        if (!(other instanceof NumberFormatterSettings)) {
+            return false;
+        }
+        return resolve().equals(((NumberFormatterSettings<?>) other).resolve());
+    }
+}
diff --git a/icu4j/main/classes/core/src/newapi/NumberPropertyMapper.java b/icu4j/main/classes/core/src/newapi/NumberPropertyMapper.java
new file mode 100644 (file)
index 0000000..274667b
--- /dev/null
@@ -0,0 +1,454 @@
+// © 2017 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html#License
+package newapi;
+
+import java.math.BigDecimal;
+import java.math.MathContext;
+
+import com.ibm.icu.impl.StandardPlural;
+import com.ibm.icu.impl.number.AffixPatternUtils;
+import com.ibm.icu.impl.number.LdmlPatternInfo;
+import com.ibm.icu.impl.number.LdmlPatternInfo.PatternParseResult;
+import com.ibm.icu.impl.number.PatternString;
+import com.ibm.icu.impl.number.Properties;
+import com.ibm.icu.impl.number.RoundingUtils;
+import com.ibm.icu.text.CompactDecimalFormat.CompactStyle;
+import com.ibm.icu.text.CurrencyPluralInfo;
+import com.ibm.icu.text.DecimalFormatSymbols;
+import com.ibm.icu.util.Currency;
+import com.ibm.icu.util.Currency.CurrencyUsage;
+import com.ibm.icu.util.ULocale;
+
+import newapi.NumberFormatter.DecimalMarkDisplay;
+import newapi.NumberFormatter.SignDisplay;
+import newapi.Rounder.FractionRounderImpl;
+import newapi.Rounder.IncrementRounderImpl;
+import newapi.Rounder.SignificantRounderImpl;
+import newapi.impl.AffixPatternProvider;
+import newapi.impl.CustomSymbolCurrency;
+import newapi.impl.MacroProps;
+import newapi.impl.MultiplierImpl;
+import newapi.impl.Padder;
+
+/** @author sffc */
+public final class NumberPropertyMapper {
+
+    /** Convenience method to create a NumberFormatter directly from Properties. */
+    public static UnlocalizedNumberFormatter create(Properties properties, DecimalFormatSymbols symbols) {
+        MacroProps macros = oldToNew(properties, symbols, null);
+        return NumberFormatter.with().macros(macros);
+    }
+
+    /**
+     * Convenience method to create a NumberFormatter directly from a pattern string. Something like this could become
+     * public API if there is demand.
+     */
+    public static UnlocalizedNumberFormatter create(String pattern, DecimalFormatSymbols symbols) {
+        Properties properties = PatternString.parseToProperties(pattern);
+        return create(properties, symbols);
+    }
+
+    /**
+     * Creates a new {@link MacroProps} object based on the content of a {@link Properties} object. In other words, maps
+     * Properties to MacroProps. This function is used by the JDK-compatibility API to call into the ICU 60 fluent
+     * number formatting pipeline.
+     *
+     * @param properties
+     *            The property bag to be mapped.
+     * @param symbols
+     *            The symbols associated with the property bag.
+     * @param exportedProperties
+     *            A property bag in which to store validated properties.
+     * @return A new MacroProps containing all of the information in the Properties.
+     */
+    public static MacroProps oldToNew(Properties properties, DecimalFormatSymbols symbols,
+            Properties exportedProperties) {
+        MacroProps macros = new MacroProps();
+        ULocale locale = symbols.getULocale();
+
+        /////////////
+        // SYMBOLS //
+        /////////////
+
+        macros.symbols = symbols;
+
+        //////////////////
+        // PLURAL RULES //
+        //////////////////
+
+        macros.rules = properties.getPluralRules();
+
+        /////////////
+        // AFFIXES //
+        /////////////
+
+        AffixPatternProvider affixProvider;
+        if (properties.getCurrencyPluralInfo() == null) {
+            affixProvider = new PropertiesAffixPatternProvider(
+                    properties.getPositivePrefix() != null ? AffixPatternUtils.escape(properties.getPositivePrefix())
+                            : properties.getPositivePrefixPattern(),
+                    properties.getPositiveSuffix() != null ? AffixPatternUtils.escape(properties.getPositiveSuffix())
+                            : properties.getPositiveSuffixPattern(),
+                    properties.getNegativePrefix() != null ? AffixPatternUtils.escape(properties.getNegativePrefix())
+                            : properties.getNegativePrefixPattern(),
+                    properties.getNegativeSuffix() != null ? AffixPatternUtils.escape(properties.getNegativeSuffix())
+                            : properties.getNegativeSuffixPattern());
+        } else {
+            affixProvider = new CurrencyPluralInfoAffixProvider(properties.getCurrencyPluralInfo());
+        }
+        macros.affixProvider = affixProvider;
+
+        ///////////
+        // UNITS //
+        ///////////
+
+        boolean useCurrency = ((properties.getCurrency() != null) || properties.getCurrencyPluralInfo() != null
+                || properties.getCurrencyUsage() != null || affixProvider.hasCurrencySign());
+        Currency currency = CustomSymbolCurrency.resolve(properties.getCurrency(), locale, symbols);
+        CurrencyUsage currencyUsage = properties.getCurrencyUsage();
+        boolean explicitCurrencyUsage = currencyUsage != null;
+        if (!explicitCurrencyUsage) {
+            currencyUsage = CurrencyUsage.STANDARD;
+        }
+        if (useCurrency) {
+            macros.unit = currency;
+        }
+
+        ///////////////////////
+        // ROUNDING STRATEGY //
+        ///////////////////////
+
+        int maxInt = properties.getMaximumIntegerDigits();
+        int minInt = properties.getMinimumIntegerDigits();
+        int maxFrac = properties.getMaximumFractionDigits();
+        int minFrac = properties.getMinimumFractionDigits();
+        int minSig = properties.getMinimumSignificantDigits();
+        int maxSig = properties.getMaximumSignificantDigits();
+        BigDecimal roundingIncrement = properties.getRoundingIncrement();
+        MathContext mathContext = RoundingUtils.getMathContextOrUnlimited(properties);
+        boolean explicitMinMaxFrac = minFrac != -1 || maxFrac != -1;
+        boolean explicitMinMaxSig = minSig != -1 || maxSig != -1;
+        // Validate min/max int/frac.
+        // For backwards compatibility, minimum overrides maximum if the two conflict.
+        // The following logic ensures that there is always a minimum of at least one digit.
+        if (minInt == 0 && maxFrac != 0) {
+            // Force a digit after the decimal point.
+            minFrac = minFrac <= 0 ? 1 : minFrac;
+            maxFrac = maxFrac < 0 ? Integer.MAX_VALUE : maxFrac < minFrac ? minFrac : maxFrac;
+            minInt = 0;
+            maxInt = maxInt < 0 ? -1 : maxInt;
+        } else {
+            // Force a digit before the decimal point.
+            minFrac = minFrac < 0 ? 0 : minFrac;
+            maxFrac = maxFrac < 0 ? Integer.MAX_VALUE : maxFrac < minFrac ? minFrac : maxFrac;
+            minInt = minInt <= 0 ? 1 : minInt;
+            maxInt = maxInt < 0 ? -1 : maxInt < minInt ? minInt : maxInt;
+        }
+        Rounder rounding = null;
+        if (explicitCurrencyUsage) {
+            rounding = Rounder.constructCurrency(currencyUsage).withCurrency(currency);
+        } else if (roundingIncrement != null) {
+            rounding = Rounder.constructIncrement(roundingIncrement);
+        } else if (explicitMinMaxSig) {
+            minSig = minSig < 1 ? 1 : minSig > 1000 ? 1000 : minSig;
+            maxSig = maxSig < 0 ? 1000 : maxSig < minSig ? minSig : maxSig > 1000 ? 1000 : maxSig;
+            rounding = Rounder.constructSignificant(minSig, maxSig);
+        } else if (explicitMinMaxFrac) {
+            rounding = Rounder.constructFraction(minFrac, maxFrac);
+        } else if (useCurrency) {
+            rounding = Rounder.constructCurrency(currencyUsage);
+        }
+        if (rounding != null) {
+            rounding = rounding.withMode(mathContext);
+            macros.rounder = rounding;
+        }
+
+        ///////////////////
+        // INTEGER WIDTH //
+        ///////////////////
+
+        macros.integerWidth = IntegerWidth.zeroFillTo(minInt).truncateAt(maxInt);
+
+        ///////////////////////
+        // GROUPING STRATEGY //
+        ///////////////////////
+
+        int grouping1 = properties.getGroupingSize();
+        int grouping2 = properties.getSecondaryGroupingSize();
+        int minGrouping = properties.getMinimumGroupingDigits();
+        assert grouping1 >= -2; // value of -2 means to forward no grouping information
+        grouping1 = grouping1 > 0 ? grouping1 : grouping2 > 0 ? grouping2 : grouping1;
+        grouping2 = grouping2 > 0 ? grouping2 : grouping1;
+        // TODO: Is it important to handle minGrouping > 2?
+        macros.grouper = Grouper.getInstance((byte) grouping1, (byte) grouping2, minGrouping == 2);
+
+        /////////////
+        // PADDING //
+        /////////////
+
+        if (properties.getFormatWidth() != -1) {
+            macros.padder = new Padder(properties.getPadString(), properties.getFormatWidth(),
+                    properties.getPadPosition());
+        }
+
+        ///////////////////////////////
+        // DECIMAL MARK ALWAYS SHOWN //
+        ///////////////////////////////
+
+        macros.decimal = properties.getDecimalSeparatorAlwaysShown() ? DecimalMarkDisplay.ALWAYS
+                : DecimalMarkDisplay.AUTO;
+
+        ///////////////////////
+        // SIGN ALWAYS SHOWN //
+        ///////////////////////
+
+        macros.sign = properties.getSignAlwaysShown() ? SignDisplay.ALWAYS : SignDisplay.AUTO;
+
+        /////////////////////////
+        // SCIENTIFIC NOTATION //
+        /////////////////////////
+
+        if (properties.getMinimumExponentDigits() != -1) {
+            // Scientific notation is required.
+            // The mapping from property bag to scientific notation is nontrivial due to LDML rules.
+            // The maximum of 8 engineering digits has unknown origins and is not in the spec.
+            int engineering = (maxInt != -1) ? maxInt : properties.getMaximumIntegerDigits();
+            engineering = (engineering < 0) ? 0 : (engineering > 8) ? minInt : engineering;
+            // Bug #13289: if maxInt > minInt > 1, then minInt should be 1.
+            // Clear out IntegerWidth to prevent padding extra zeros.
+            if (maxInt > minInt && minInt > 1) {
+                macros.integerWidth = null;
+            }
+            macros.notation = new ScientificNotation(
+                    // Engineering interval:
+                    engineering,
+                    // Enforce minimum integer digits (for patterns like "000.00E0"):
+                    (engineering == minInt),
+                    // Minimum exponent digits:
+                    properties.getMinimumExponentDigits(),
+                    // Exponent sign always shown:
+                    properties.getExponentSignAlwaysShown() ? SignDisplay.ALWAYS : SignDisplay.AUTO);
+            // Scientific notation also involves overriding the rounding mode.
+            // TODO: Overriding here is a bit of a hack. Should this logic go earlier?
+            if (macros.rounder instanceof FractionRounder) {
+                int minInt_ = properties.getMinimumIntegerDigits();
+                int minFrac_ = properties.getMinimumFractionDigits();
+                int maxFrac_ = properties.getMaximumFractionDigits();
+                if (minInt_ == 0 && maxFrac_ == 0) {
+                    // Patterns like "#E0" and "##E0", which mean no rounding!
+                    macros.rounder = Rounder.constructInfinite().withMode(mathContext);
+                } else if (minInt_ == 0 && minFrac_ == 0) {
+                    // Patterns like "#.##E0" (no zeros in the mantissa), which mean round to maxFrac+1
+                    macros.rounder = Rounder.constructSignificant(1, maxFrac_ + 1).withMode(mathContext);
+                } else {
+                    // All other scientific patterns, which mean round to minInt+maxFrac
+                    macros.rounder = Rounder.constructSignificant(minInt + minFrac, minInt + maxFrac)
+                            .withMode(mathContext);
+                }
+            }
+        }
+
+        //////////////////////
+        // COMPACT NOTATION //
+        //////////////////////
+
+        if (properties.getCompactStyle() != null) {
+            if (properties.getCompactCustomData() != null) {
+                macros.notation = new CompactNotation(properties.getCompactCustomData());
+            } else if (properties.getCompactStyle() == CompactStyle.LONG) {
+                macros.notation = Notation.compactLong();
+            } else {
+                macros.notation = Notation.compactShort();
+            }
+            // Do not forward the affix provider.
+            macros.affixProvider = null;
+        }
+
+        /////////////////
+        // MULTIPLIERS //
+        /////////////////
+
+        if (properties.getMagnitudeMultiplier() != 0) {
+            macros.multiplier = new MultiplierImpl(properties.getMagnitudeMultiplier());
+        } else if (properties.getMultiplier() != null) {
+            macros.multiplier = new MultiplierImpl(properties.getMultiplier());
+        }
+
+        //////////////////////
+        // PROPERTY EXPORTS //
+        //////////////////////
+
+        if (exportedProperties != null) {
+
+            exportedProperties.setMathContext(mathContext);
+            exportedProperties.setRoundingMode(mathContext.getRoundingMode());
+            exportedProperties.setMinimumIntegerDigits(minInt);
+            exportedProperties.setMaximumIntegerDigits(maxInt == -1 ? Integer.MAX_VALUE : maxInt);
+
+            Rounder rounding_;
+            if (rounding instanceof CurrencyRounder) {
+                rounding_ = ((CurrencyRounder) rounding).withCurrency(currency);
+            } else {
+                rounding_ = rounding;
+            }
+            int minFrac_ = minFrac;
+            int maxFrac_ = maxFrac;
+            int minSig_ = minSig;
+            int maxSig_ = maxSig;
+            BigDecimal increment_ = null;
+            if (rounding_ instanceof FractionRounderImpl) {
+                minFrac_ = ((FractionRounderImpl) rounding_).minFrac;
+                maxFrac_ = ((FractionRounderImpl) rounding_).maxFrac;
+            } else if (rounding_ instanceof IncrementRounderImpl) {
+                increment_ = ((IncrementRounderImpl) rounding_).increment;
+                minFrac_ = increment_.scale();
+                maxFrac_ = increment_.scale();
+            } else if (rounding_ instanceof SignificantRounderImpl) {
+                minSig_ = ((SignificantRounderImpl) rounding_).minSig;
+                maxSig_ = ((SignificantRounderImpl) rounding_).maxSig;
+            }
+
+            exportedProperties.setMinimumFractionDigits(minFrac_);
+            exportedProperties.setMaximumFractionDigits(maxFrac_);
+            exportedProperties.setMinimumSignificantDigits(minSig_);
+            exportedProperties.setMaximumSignificantDigits(maxSig_);
+            exportedProperties.setRoundingIncrement(increment_);
+        }
+
+        return macros;
+    }
+
+    private static class PropertiesAffixPatternProvider implements AffixPatternProvider {
+        private final String posPrefixPattern;
+        private final String posSuffixPattern;
+        private final String negPrefixPattern;
+        private final String negSuffixPattern;
+
+        public PropertiesAffixPatternProvider(String ppp, String psp, String npp, String nsp) {
+            if (ppp == null)
+                ppp = "";
+            if (psp == null)
+                psp = "";
+            if (npp == null && nsp != null)
+                npp = "-"; // TODO: This is a hack.
+            if (nsp == null && npp != null)
+                nsp = "";
+            posPrefixPattern = ppp;
+            posSuffixPattern = psp;
+            negPrefixPattern = npp;
+            negSuffixPattern = nsp;
+        }
+
+        @Override
+        public char charAt(int flags, int i) {
+            boolean prefix = (flags & Flags.PREFIX) != 0;
+            boolean negative = (flags & Flags.NEGATIVE_SUBPATTERN) != 0;
+            if (prefix && negative) {
+                return negPrefixPattern.charAt(i);
+            } else if (prefix) {
+                return posPrefixPattern.charAt(i);
+            } else if (negative) {
+                return negSuffixPattern.charAt(i);
+            } else {
+                return posSuffixPattern.charAt(i);
+            }
+        }
+
+        @Override
+        public int length(int flags) {
+            boolean prefix = (flags & Flags.PREFIX) != 0;
+            boolean negative = (flags & Flags.NEGATIVE_SUBPATTERN) != 0;
+            if (prefix && negative) {
+                return negPrefixPattern.length();
+            } else if (prefix) {
+                return posPrefixPattern.length();
+            } else if (negative) {
+                return negSuffixPattern.length();
+            } else {
+                return posSuffixPattern.length();
+            }
+        }
+
+        @Override
+        public boolean positiveHasPlusSign() {
+            return AffixPatternUtils.containsType(posPrefixPattern, AffixPatternUtils.TYPE_PLUS_SIGN)
+                    || AffixPatternUtils.containsType(posSuffixPattern, AffixPatternUtils.TYPE_PLUS_SIGN);
+        }
+
+        @Override
+        public boolean hasNegativeSubpattern() {
+            return negPrefixPattern != null;
+        }
+
+        @Override
+        public boolean negativeHasMinusSign() {
+            return AffixPatternUtils.containsType(negPrefixPattern, AffixPatternUtils.TYPE_MINUS_SIGN)
+                    || AffixPatternUtils.containsType(negSuffixPattern, AffixPatternUtils.TYPE_MINUS_SIGN);
+        }
+
+        @Override
+        public boolean hasCurrencySign() {
+            return AffixPatternUtils.hasCurrencySymbols(posPrefixPattern)
+                    || AffixPatternUtils.hasCurrencySymbols(posSuffixPattern)
+                    || AffixPatternUtils.hasCurrencySymbols(negPrefixPattern)
+                    || AffixPatternUtils.hasCurrencySymbols(negSuffixPattern);
+        }
+
+        @Override
+        public boolean containsSymbolType(int type) {
+            return AffixPatternUtils.containsType(posPrefixPattern, type)
+                    || AffixPatternUtils.containsType(posSuffixPattern, type)
+                    || AffixPatternUtils.containsType(negPrefixPattern, type)
+                    || AffixPatternUtils.containsType(negSuffixPattern, type);
+        }
+    }
+
+    private static class CurrencyPluralInfoAffixProvider implements AffixPatternProvider {
+        private final AffixPatternProvider[] affixesByPlural;
+
+        public CurrencyPluralInfoAffixProvider(CurrencyPluralInfo cpi) {
+            affixesByPlural = new PatternParseResult[StandardPlural.COUNT];
+            for (StandardPlural plural : StandardPlural.VALUES) {
+                affixesByPlural[plural.ordinal()] = LdmlPatternInfo
+                        .parse(cpi.getCurrencyPluralPattern(plural.getKeyword()));
+            }
+        }
+
+        @Override
+        public char charAt(int flags, int i) {
+            int pluralOrdinal = (flags & Flags.PLURAL_MASK);
+            return affixesByPlural[pluralOrdinal].charAt(flags, i);
+        }
+
+        @Override
+        public int length(int flags) {
+            int pluralOrdinal = (flags & Flags.PLURAL_MASK);
+            return affixesByPlural[pluralOrdinal].length(flags);
+        }
+
+        @Override
+        public boolean positiveHasPlusSign() {
+            return affixesByPlural[StandardPlural.OTHER.ordinal()].positiveHasPlusSign();
+        }
+
+        @Override
+        public boolean hasNegativeSubpattern() {
+            return affixesByPlural[StandardPlural.OTHER.ordinal()].hasNegativeSubpattern();
+        }
+
+        @Override
+        public boolean negativeHasMinusSign() {
+            return affixesByPlural[StandardPlural.OTHER.ordinal()].negativeHasMinusSign();
+        }
+
+        @Override
+        public boolean hasCurrencySign() {
+            return affixesByPlural[StandardPlural.OTHER.ordinal()].hasCurrencySign();
+        }
+
+        @Override
+        public boolean containsSymbolType(int type) {
+            return affixesByPlural[StandardPlural.OTHER.ordinal()].containsSymbolType(type);
+        }
+    }
+}
diff --git a/icu4j/main/classes/core/src/newapi/Rounder.java b/icu4j/main/classes/core/src/newapi/Rounder.java
new file mode 100644 (file)
index 0000000..5a6e74d
--- /dev/null
@@ -0,0 +1,446 @@
+// © 2017 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html#License
+package newapi;
+
+import java.math.BigDecimal;
+import java.math.MathContext;
+import java.math.RoundingMode;
+
+import com.ibm.icu.impl.number.FormatQuantity;
+import com.ibm.icu.impl.number.RoundingUtils;
+import com.ibm.icu.util.Currency;
+import com.ibm.icu.util.Currency.CurrencyUsage;
+
+import newapi.impl.MultiplierProducer;
+
+public abstract class Rounder implements Cloneable {
+
+    // FIXME
+    /** @internal */
+    public static final int MAX_VALUE = 100;
+
+    /* package-private final */ MathContext mathContext;
+
+    /* package-private */ Rounder() {
+        mathContext = RoundingUtils.mathContextUnlimited(RoundingMode.HALF_EVEN);
+    }
+
+    public static Rounder none() {
+        return constructInfinite();
+    }
+
+    public static FractionRounder integer() {
+        return constructFraction(0, 0);
+    }
+
+    public static FractionRounder fixedFraction(int minMaxFrac) {
+        if (minMaxFrac >= 0 && minMaxFrac <= MAX_VALUE) {
+            return constructFraction(minMaxFrac, minMaxFrac);
+        } else {
+            throw new IllegalArgumentException("Fraction length must be between 0 and " + MAX_VALUE);
+        }
+    }
+
+    public static FractionRounder minFraction(int minFrac) {
+        if (minFrac >= 0 && minFrac < MAX_VALUE) {
+            return constructFraction(minFrac, -1);
+        } else {
+            throw new IllegalArgumentException("Fraction length must be between 0 and " + MAX_VALUE);
+        }
+    }
+
+    public static FractionRounder maxFraction(int maxFrac) {
+        if (maxFrac >= 0 && maxFrac < MAX_VALUE) {
+            return constructFraction(0, maxFrac);
+        } else {
+            throw new IllegalArgumentException("Fraction length must be between 0 and " + MAX_VALUE);
+        }
+    }
+
+    public static FractionRounder minMaxFraction(int minFrac, int maxFrac) {
+        if (minFrac >= 0 && maxFrac <= MAX_VALUE && minFrac <= maxFrac) {
+            return constructFraction(minFrac, maxFrac);
+        } else {
+            throw new IllegalArgumentException("Fraction length must be between 0 and " + MAX_VALUE);
+        }
+    }
+
+    public static Rounder fixedFigures(int minMaxSig) {
+        if (minMaxSig > 0 && minMaxSig <= MAX_VALUE) {
+            return constructSignificant(minMaxSig, minMaxSig);
+        } else {
+            throw new IllegalArgumentException("Significant digits must be between 0 and " + MAX_VALUE);
+        }
+    }
+
+    public static Rounder minFigures(int minSig) {
+        if (minSig > 0 && minSig <= MAX_VALUE) {
+            return constructSignificant(minSig, -1);
+        } else {
+            throw new IllegalArgumentException("Significant digits must be between 0 and " + MAX_VALUE);
+        }
+    }
+
+    public static Rounder maxFigures(int maxSig) {
+        if (maxSig > 0 && maxSig <= MAX_VALUE) {
+            return constructSignificant(0, maxSig);
+        } else {
+            throw new IllegalArgumentException("Significant digits must be between 0 and " + MAX_VALUE);
+        }
+    }
+
+    public static Rounder minMaxFigures(int minSig, int maxSig) {
+        if (minSig > 0 && maxSig <= MAX_VALUE && minSig <= maxSig) {
+            return constructSignificant(minSig, maxSig);
+        } else {
+            throw new IllegalArgumentException("Significant digits must be between 0 and " + MAX_VALUE);
+        }
+    }
+
+    public static Rounder increment(BigDecimal roundingIncrement) {
+        if (roundingIncrement != null && roundingIncrement.compareTo(BigDecimal.ZERO) > 0) {
+            return constructIncrement(roundingIncrement);
+        } else {
+            throw new IllegalArgumentException("Rounding increment must be positive and non-null");
+        }
+    }
+
+    public static CurrencyRounder currency(CurrencyUsage currencyUsage) {
+        if (currencyUsage != null) {
+            return constructCurrency(currencyUsage);
+        } else {
+            throw new IllegalArgumentException("CurrencyUsage must be non-null");
+        }
+    }
+
+    /**
+     * Sets the {@link java.math.RoundingMode} to use when picking the direction to round (up or down).
+     *
+     * <p>
+     * Common values include {@link RoundingMode#HALF_EVEN}, {@link RoundingMode#HALF_UP}, and
+     * {@link RoundingMode#CEILING}. The default is HALF_EVEN.
+     *
+     * @param roundingMode
+     *            The RoundingMode to use.
+     * @return An immutable object for chaining.
+     */
+    public Rounder withMode(RoundingMode roundingMode) {
+        return withMode(RoundingUtils.mathContextUnlimited(roundingMode));
+    }
+
+    /**
+     * Sets a MathContext directly instead of RoundingMode.
+     *
+     * @internal
+     * @deprecated This API is ICU internal only.
+     */
+    @Deprecated
+    public Rounder withMode(MathContext mathContext) {
+        if (this.mathContext.equals(mathContext)) {
+            return this;
+        }
+        Rounder other = (Rounder) this.clone();
+        other.mathContext = mathContext;
+        return other;
+    }
+
+    @Override
+    public Object clone() {
+        try {
+            return super.clone();
+        } catch (CloneNotSupportedException e) {
+            // Should not happen since parent is Object
+            throw new AssertionError(e);
+        }
+    }
+
+    //////////////////////////
+    // PACKAGE-PRIVATE APIS //
+    //////////////////////////
+
+    private static final InfiniteRounderImpl NONE = new InfiniteRounderImpl();
+
+    private static final FractionRounderImpl FIXED_FRAC_0 = new FractionRounderImpl(0, 0);
+    private static final FractionRounderImpl FIXED_FRAC_2 = new FractionRounderImpl(2, 2);
+
+    private static final SignificantRounderImpl FIXED_SIG_2 = new SignificantRounderImpl(2, 2);
+    private static final SignificantRounderImpl FIXED_SIG_3 = new SignificantRounderImpl(3, 3);
+    private static final SignificantRounderImpl RANGE_SIG_2_3 = new SignificantRounderImpl(2, 3);
+
+    /* package-private */ static final FracSigRounderImpl COMPACT_STRATEGY = new FracSigRounderImpl(0, 0, 2, -1);
+
+    private static final IncrementRounderImpl NICKEL = new IncrementRounderImpl(BigDecimal.valueOf(0.5));
+
+    private static final CurrencyRounderImpl MONETARY_STANDARD = new CurrencyRounderImpl(CurrencyUsage.STANDARD);
+    private static final CurrencyRounderImpl MONETARY_CASH = new CurrencyRounderImpl(CurrencyUsage.CASH);
+
+    private static final PassThroughRounderImpl PASS_THROUGH = new PassThroughRounderImpl();
+
+    static Rounder constructInfinite() {
+        return NONE;
+    }
+
+    static FractionRounder constructFraction(int minFrac, int maxFrac) {
+        if (minFrac == 0 && maxFrac == 0) {
+            return FIXED_FRAC_0;
+        } else if (minFrac == 2 && maxFrac == 2) {
+            return FIXED_FRAC_2;
+        } else {
+            return new FractionRounderImpl(minFrac, maxFrac);
+        }
+    }
+
+    /** Assumes that minSig <= maxSig. */
+    static Rounder constructSignificant(int minSig, int maxSig) {
+        if (minSig == 2 && maxSig == 2) {
+            return FIXED_SIG_2;
+        } else if (minSig == 3 && maxSig == 3) {
+            return FIXED_SIG_3;
+        } else if (minSig == 2 && maxSig == 3) {
+            return RANGE_SIG_2_3;
+        } else {
+            return new SignificantRounderImpl(minSig, maxSig);
+        }
+    }
+
+    static Rounder constructFractionSignificant(FractionRounder base_, int minSig, int maxSig) {
+        assert base_ instanceof FractionRounderImpl;
+        FractionRounderImpl base = (FractionRounderImpl) base_;
+        if (base.minFrac == 0 && base.maxFrac == 0 && minSig == 2 /* && maxSig == -1 */) {
+            return COMPACT_STRATEGY;
+        } else {
+            return new FracSigRounderImpl(base.minFrac, base.maxFrac, minSig, maxSig);
+        }
+    }
+
+    static Rounder constructIncrement(BigDecimal increment) {
+        if (increment.compareTo(NICKEL.increment) == 0) {
+            return NICKEL;
+        } else {
+            return new IncrementRounderImpl(increment);
+        }
+    }
+
+    static CurrencyRounder constructCurrency(CurrencyUsage usage) {
+        if (usage == CurrencyUsage.STANDARD) {
+            return MONETARY_STANDARD;
+        } else if (usage == CurrencyUsage.CASH) {
+            return MONETARY_CASH;
+        } else {
+            throw new AssertionError();
+        }
+    }
+
+    static Rounder constructFromCurrency(CurrencyRounder base_, Currency currency) {
+        assert base_ instanceof CurrencyRounderImpl;
+        CurrencyRounderImpl base = (CurrencyRounderImpl) base_;
+        double incrementDouble = currency.getRoundingIncrement(base.usage);
+        if (incrementDouble != 0.0) {
+            BigDecimal increment = BigDecimal.valueOf(incrementDouble);
+            return constructIncrement(increment);
+        } else {
+            int minMaxFrac = currency.getDefaultFractionDigits(base.usage);
+            return constructFraction(minMaxFrac, minMaxFrac);
+        }
+    }
+
+    static Rounder constructPassThrough() {
+        return PASS_THROUGH;
+    }
+
+    /**
+     * Returns a valid working Rounder. If the Rounder is a CurrencyRounder, applies the given currency. Otherwise,
+     * simply passes through the argument.
+     *
+     * @param rounder
+     *            The input object.
+     * @param currency
+     *            A currency object to use in case the input object needs it.
+     * @return A Rounder object ready for use.
+     */
+    static Rounder normalizeType(Rounder rounder, Currency currency) {
+        if (rounder instanceof CurrencyRounder) {
+            return ((CurrencyRounder) rounder).withCurrency(currency);
+        } else {
+            return rounder;
+        }
+    }
+
+    abstract void apply(FormatQuantity value);
+
+    int chooseMultiplierAndApply(FormatQuantity input, MultiplierProducer producer) {
+        // TODO: Make a better and more efficient implementation.
+        // TODO: Avoid the object creation here.
+        FormatQuantity copy = input.createCopy();
+
+        assert !input.isZero();
+        int magnitude = input.getMagnitude();
+        int multiplier = producer.getMultiplier(magnitude);
+        input.adjustMagnitude(multiplier);
+        apply(input);
+
+        // If the number turned to zero when rounding, do not re-attempt the rounding.
+        if (!input.isZero() && input.getMagnitude() == magnitude + multiplier + 1) {
+            magnitude += 1;
+            input.copyFrom(copy);
+            multiplier = producer.getMultiplier(magnitude);
+            input.adjustMagnitude(multiplier);
+            assert input.getMagnitude() == magnitude + multiplier - 1;
+            apply(input);
+            assert input.getMagnitude() == magnitude + multiplier;
+        }
+
+        return multiplier;
+    }
+
+    ///////////////
+    // INTERNALS //
+    ///////////////
+
+    static class InfiniteRounderImpl extends Rounder {
+
+        private InfiniteRounderImpl() {
+        }
+
+        @Override
+        void apply(FormatQuantity value) {
+            value.roundToInfinity();
+            value.setFractionLength(0, Integer.MAX_VALUE);
+        }
+    }
+
+    static class FractionRounderImpl extends FractionRounder {
+        final int minFrac;
+        final int maxFrac;
+
+        private FractionRounderImpl(int minFrac, int maxFrac) {
+            this.minFrac = minFrac;
+            this.maxFrac = maxFrac;
+        }
+
+        @Override
+        void apply(FormatQuantity value) {
+            value.roundToMagnitude(getRoundingMagnitudeFraction(maxFrac), mathContext);
+            value.setFractionLength(Math.max(0, -getDisplayMagnitudeFraction(minFrac)), Integer.MAX_VALUE);
+        }
+    }
+
+    static class SignificantRounderImpl extends Rounder {
+        final int minSig;
+        final int maxSig;
+
+        private SignificantRounderImpl(int minSig, int maxSig) {
+            this.minSig = minSig;
+            this.maxSig = maxSig;
+        }
+
+        @Override
+        void apply(FormatQuantity value) {
+            value.roundToMagnitude(getRoundingMagnitudeSignificant(value, maxSig), mathContext);
+            value.setFractionLength(Math.max(0, -getDisplayMagnitudeSignificant(value, minSig)), Integer.MAX_VALUE);
+        }
+
+        /** Version of {@link #apply} that obeys minInt constraints. Used for scientific notation compatibility mode. */
+        public void apply(FormatQuantity quantity, int minInt) {
+            assert quantity.isZero();
+            quantity.setFractionLength(minSig - minInt, Integer.MAX_VALUE);
+        }
+    }
+
+    static class FracSigRounderImpl extends Rounder {
+        final int minFrac;
+        final int maxFrac;
+        final int minSig;
+        final int maxSig;
+
+        private FracSigRounderImpl(int minFrac, int maxFrac, int minSig, int maxSig) {
+            this.minFrac = minFrac;
+            this.maxFrac = maxFrac;
+            this.minSig = minSig;
+            this.maxSig = maxSig;
+        }
+
+        @Override
+        void apply(FormatQuantity value) {
+            int displayMag = getDisplayMagnitudeFraction(minFrac);
+            int roundingMag = getRoundingMagnitudeFraction(maxFrac);
+            if (minSig == -1) {
+                // Max Sig override
+                int candidate = getRoundingMagnitudeSignificant(value, maxSig);
+                roundingMag = Math.max(roundingMag, candidate);
+            } else {
+                // Min Sig override
+                int candidate = getDisplayMagnitudeSignificant(value, minSig);
+                roundingMag = Math.min(roundingMag, candidate);
+            }
+            value.roundToMagnitude(roundingMag, mathContext);
+            value.setFractionLength(Math.max(0, -displayMag), Integer.MAX_VALUE);
+        }
+    }
+
+    static class IncrementRounderImpl extends Rounder {
+        final BigDecimal increment;
+
+        private IncrementRounderImpl(BigDecimal increment) {
+            this.increment = increment;
+        }
+
+        @Override
+        void apply(FormatQuantity value) {
+            value.roundToIncrement(increment, mathContext);
+            value.setFractionLength(increment.scale(), increment.scale());
+        }
+    }
+
+    static class CurrencyRounderImpl extends CurrencyRounder {
+        final CurrencyUsage usage;
+
+        private CurrencyRounderImpl(CurrencyUsage usage) {
+            this.usage = usage;
+        }
+
+        @Override
+        void apply(FormatQuantity value) {
+            // Call .withCurrency() before .apply()!
+            throw new AssertionError();
+        }
+    }
+
+    static class PassThroughRounderImpl extends Rounder {
+
+        private PassThroughRounderImpl() {
+        }
+
+        @Override
+        void apply(FormatQuantity value) {
+            // TODO: Assert that value has already been rounded
+        }
+    }
+
+    private static int getRoundingMagnitudeFraction(int maxFrac) {
+        if (maxFrac == -1) {
+            return Integer.MIN_VALUE;
+        }
+        return -maxFrac;
+    }
+
+    private static int getRoundingMagnitudeSignificant(FormatQuantity value, int maxSig) {
+        if (maxSig == -1) {
+            return Integer.MIN_VALUE;
+        }
+        int magnitude = value.isZero() ? 0 : value.getMagnitude();
+        return magnitude - maxSig + 1;
+    }
+
+    private static int getDisplayMagnitudeFraction(int minFrac) {
+        if (minFrac == 0) {
+            return Integer.MAX_VALUE;
+        }
+        return -minFrac;
+    }
+
+    private static int getDisplayMagnitudeSignificant(FormatQuantity value, int minSig) {
+        int magnitude = value.isZero() ? 0 : value.getMagnitude();
+        return magnitude - minSig + 1;
+    }
+}
diff --git a/icu4j/main/classes/core/src/newapi/ScientificNotation.java b/icu4j/main/classes/core/src/newapi/ScientificNotation.java
new file mode 100644 (file)
index 0000000..d3a6ea1
--- /dev/null
@@ -0,0 +1,218 @@
+// © 2017 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html#License
+package newapi;
+
+import com.ibm.icu.impl.number.FormatQuantity;
+import com.ibm.icu.impl.number.Modifier;
+import com.ibm.icu.impl.number.NumberStringBuilder;
+import com.ibm.icu.text.DecimalFormatSymbols;
+import com.ibm.icu.text.NumberFormat;
+
+import newapi.NumberFormatter.SignDisplay;
+import newapi.Rounder.SignificantRounderImpl;
+import newapi.impl.MicroProps;
+import newapi.impl.MultiplierProducer;
+import newapi.impl.QuantityChain;
+
+@SuppressWarnings("unused")
+public class ScientificNotation extends Notation implements Cloneable {
+
+    int engineeringInterval;
+    boolean requireMinInt;
+    int minExponentDigits;
+    SignDisplay exponentSignDisplay;
+
+    /* package-private */ ScientificNotation(int engineeringInterval, boolean requireMinInt, int minExponentDigits,
+            SignDisplay exponentSignDisplay) {
+        this.engineeringInterval = engineeringInterval;
+        this.requireMinInt = requireMinInt;
+        this.minExponentDigits = minExponentDigits;
+        this.exponentSignDisplay = exponentSignDisplay;
+    }
+
+    public ScientificNotation withMinExponentDigits(int minExponentDigits) {
+        if (minExponentDigits >= 0 && minExponentDigits < Rounder.MAX_VALUE) {
+            ScientificNotation other = (ScientificNotation) this.clone();
+            other.minExponentDigits = minExponentDigits;
+            return other;
+        } else {
+            throw new IllegalArgumentException("Integer digits must be between 0 and " + Rounder.MAX_VALUE);
+        }
+    }
+
+    public ScientificNotation withExponentSignDisplay(SignDisplay exponentSignDisplay) {
+        ScientificNotation other = (ScientificNotation) this.clone();
+        other.exponentSignDisplay = exponentSignDisplay;
+        return other;
+    }
+
+    @Override
+    public Object clone() {
+        try {
+            return super.clone();
+        } catch (CloneNotSupportedException e) {
+            // Should not happen since parent is Object
+            throw new AssertionError(e);
+        }
+    }
+
+    /* package-private */ QuantityChain withLocaleData(DecimalFormatSymbols symbols, boolean build,
+            QuantityChain parent) {
+        return new MurkyScientificHandler(symbols, build, parent);
+    }
+
+    private class MurkyScientificHandler implements QuantityChain, MultiplierProducer, Modifier {
+
+        final DecimalFormatSymbols symbols;
+        final ImmutableScientificModifier[] precomputedMods;
+        final QuantityChain parent;
+        /* unsafe */ int exponent;
+
+        private MurkyScientificHandler(DecimalFormatSymbols symbols, boolean safe, QuantityChain parent) {
+            this.symbols = symbols;
+            this.parent = parent;
+
+            if (safe) {
+                // Pre-build the modifiers for exponents -12 through 12
+                precomputedMods = new ImmutableScientificModifier[25];
+                for (int i = -12; i <= 12; i++) {
+                    precomputedMods[i + 12] = new ImmutableScientificModifier(i);
+                }
+            } else {
+                precomputedMods = null;
+            }
+        }
+
+        @Override
+        public MicroProps withQuantity(FormatQuantity quantity) {
+            MicroProps micros = parent.withQuantity(quantity);
+            assert micros.rounding != null;
+
+            // Treat zero as if it had magnitude 0
+            int exponent;
+            if (quantity.isZero()) {
+                if (requireMinInt && micros.rounding instanceof SignificantRounderImpl) {
+                    // Show "00.000E0" on pattern "00.000E0"
+                    ((SignificantRounderImpl) micros.rounding).apply(quantity, engineeringInterval);
+                    exponent = 0;
+                } else {
+                    micros.rounding.apply(quantity);
+                    exponent = 0;
+                }
+            } else {
+                exponent = -micros.rounding.chooseMultiplierAndApply(quantity, this);
+            }
+
+            // Add the Modifier for the scientific format.
+            if (precomputedMods != null && exponent >= -12 && exponent <= 12) {
+                // Safe code path A
+                micros.modInner = precomputedMods[exponent + 12];
+            } else if (precomputedMods != null) {
+                // Safe code path B
+                micros.modInner = new ImmutableScientificModifier(exponent);
+            } else {
+                // Unsafe code path: mutates the object and re-uses it as a Modifier!
+                this.exponent = exponent;
+                micros.modInner = this;
+            }
+
+            // We already performed rounding. Do not perform it again.
+            micros.rounding = Rounder.constructPassThrough();
+
+            return micros;
+        }
+
+        @Override
+        public int getMultiplier(int magnitude) {
+            int interval = engineeringInterval;
+            int digitsShown;
+            if (requireMinInt) {
+                // For patterns like "000.00E0" and ".00E0"
+                digitsShown = interval;
+            } else if (interval <= 1) {
+                // For patterns like "0.00E0" and "@@@E0"
+                digitsShown = 1;
+            } else {
+                // For patterns like "##0.00"
+                digitsShown = ((magnitude % interval + interval) % interval) + 1;
+            }
+            return digitsShown - magnitude - 1;
+        }
+
+        @Override
+        public boolean isStrong() {
+            return true;
+        }
+
+        @Override
+        public String getPrefix() {
+            // FIXME: Localized exponent separator location.
+            return "";
+        }
+
+        @Override
+        public String getSuffix() {
+            // FIXME: Localized exponent separator location.
+            NumberStringBuilder temp = new NumberStringBuilder();
+            doApply(exponent, temp, 0);
+            return temp.toString();
+        }
+
+        @Override
+        public int apply(NumberStringBuilder output, int leftIndex, int rightIndex) {
+            return doApply(exponent, output, rightIndex);
+        }
+
+        private int doApply(int exponent, NumberStringBuilder output, int rightIndex) {
+            // FIXME: Localized exponent separator location.
+            int i = rightIndex;
+            // Append the exponent separator and sign
+            i += output.insert(i, symbols.getExponentSeparator(), NumberFormat.Field.EXPONENT_SYMBOL);
+            if (exponent < 0 && exponentSignDisplay != SignDisplay.NEVER) {
+                i += output.insert(i, symbols.getMinusSignString(), NumberFormat.Field.EXPONENT_SIGN);
+            } else if (exponentSignDisplay == SignDisplay.ALWAYS) {
+                i += output.insert(i, symbols.getPlusSignString(), NumberFormat.Field.EXPONENT_SIGN);
+            }
+            // Append the exponent digits (using a simple inline algorithm)
+            int disp = Math.abs(exponent);
+            for (int j = 0; j < minExponentDigits || disp > 0; j++, disp /= 10) {
+                int d = disp % 10;
+                String digitString = symbols.getDigitStringsLocal()[d];
+                i += output.insert(i - j, digitString, NumberFormat.Field.EXPONENT);
+            }
+            return i - rightIndex;
+        }
+
+        private class ImmutableScientificModifier implements Modifier {
+            final int exponent;
+
+            ImmutableScientificModifier(int exponent) {
+                this.exponent = exponent;
+            }
+
+            @Override
+            public boolean isStrong() {
+                return true;
+            }
+
+            @Override
+            public String getPrefix() {
+                // FIXME: Localized exponent separator location.
+                return "";
+            }
+
+            @Override
+            public String getSuffix() {
+                // FIXME: Localized exponent separator location.
+                NumberStringBuilder temp = new NumberStringBuilder();
+                doApply(exponent, temp, 0);
+                return temp.toString();
+            }
+
+            @Override
+            public int apply(NumberStringBuilder output, int leftIndex, int rightIndex) {
+                return doApply(exponent, output, rightIndex);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/icu4j/main/classes/core/src/newapi/SimpleNotation.java b/icu4j/main/classes/core/src/newapi/SimpleNotation.java
new file mode 100644 (file)
index 0000000..8c72cb4
--- /dev/null
@@ -0,0 +1,8 @@
+// © 2017 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html#License
+package newapi;
+
+public class SimpleNotation extends Notation {
+    /* package-private */ SimpleNotation() {
+    }
+}
\ No newline at end of file
diff --git a/icu4j/main/classes/core/src/newapi/SkeletonBuilder.java b/icu4j/main/classes/core/src/newapi/SkeletonBuilder.java
new file mode 100644 (file)
index 0000000..bc77e21
--- /dev/null
@@ -0,0 +1,586 @@
+// © 2017 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html#License
+package newapi;
+
+import java.math.BigDecimal;
+import java.math.MathContext;
+import java.math.RoundingMode;
+
+import com.ibm.icu.text.CompactDecimalFormat.CompactStyle;
+import com.ibm.icu.text.DecimalFormatSymbols;
+import com.ibm.icu.text.MeasureFormat.FormatWidth;
+import com.ibm.icu.text.NumberingSystem;
+import com.ibm.icu.util.Currency;
+import com.ibm.icu.util.Currency.CurrencyUsage;
+import com.ibm.icu.util.Dimensionless;
+import com.ibm.icu.util.MeasureUnit;
+
+import newapi.NumberFormatter.DecimalMarkDisplay;
+import newapi.NumberFormatter.SignDisplay;
+import newapi.Rounder.CurrencyRounderImpl;
+import newapi.Rounder.FracSigRounderImpl;
+import newapi.Rounder.FractionRounderImpl;
+import newapi.Rounder.IncrementRounderImpl;
+import newapi.Rounder.InfiniteRounderImpl;
+import newapi.Rounder.SignificantRounderImpl;
+import newapi.impl.MacroProps;
+
+final class SkeletonBuilder {
+
+    public static String macrosToSkeleton(MacroProps macros) {
+        // Print out the values in their canonical order.
+        StringBuilder sb = new StringBuilder();
+        if (macros.notation != null) {
+            // sb.append("notation=");
+            notationToSkeleton(macros.notation, sb);
+            sb.append(' ');
+        }
+        if (macros.unit != null) {
+            // sb.append("unit=");
+            unitToSkeleton(macros.unit, sb);
+            sb.append(' ');
+        }
+        if (macros.rounder != null) {
+            // sb.append("rounding=");
+            rounderToSkeleton(macros.rounder, sb);
+            sb.append(' ');
+        }
+        if (macros.grouper != null) {
+            sb.append("grouping=");
+            grouperToSkeleton(macros.grouper, sb);
+            sb.append(' ');
+        }
+//        if (macros.padder != null) {
+//            sb.append("padding=");
+//            paddingToSkeleton(macros.padder, sb);
+//            sb.append(' ');
+//        }
+        if (macros.integerWidth != null) {
+            sb.append("integer-width=");
+            integerWidthToSkeleton(macros.integerWidth, sb);
+            sb.append(' ');
+        }
+        if (macros.symbols != null) {
+            sb.append("symbols=");
+            symbolsToSkeleton(macros.symbols, sb);
+            sb.append(' ');
+        }
+        if (macros.unitWidth != null) {
+            sb.append("unit-width=");
+            unitWidthToSkeleton(macros.unitWidth, sb);
+            sb.append(' ');
+        }
+        if (macros.sign != null) {
+            sb.append("sign=");
+            signToSkeleton(macros.sign, sb);
+            sb.append(' ');
+        }
+        if (macros.decimal != null) {
+            sb.append("decimal=");
+            decimalToSkeleton(macros.decimal, sb);
+            sb.append(' ');
+        }
+        if (sb.length() > 0) {
+            // Remove the trailing space
+            sb.setLength(sb.length() - 1);
+        }
+        return sb.toString();
+    }
+
+    public static MacroProps skeletonToMacros(String skeleton) {
+        MacroProps macros = new MacroProps();
+        for (int offset = 0; offset < skeleton.length();) {
+            char c = skeleton.charAt(offset);
+            switch (c) {
+            case ' ':
+                offset++;
+                break;
+            case 'E':
+            case 'C':
+            case 'I':
+                offset += skeletonToNotation(skeleton, offset, macros);
+                break;
+            case '%':
+            case 'B':
+            case '$':
+            case 'U':
+                offset += skeletonToUnit(skeleton, offset, macros);
+                break;
+            case 'F':
+            case 'S':
+            case 'M':
+            case 'G':
+            case 'Y':
+                offset += skeletonToRounding(skeleton, offset, macros);
+                break;
+            default:
+                if (skeleton.regionMatches(offset, "notation=", 0, 9)) {
+                    offset += 9;
+                    offset += skeletonToNotation(skeleton, offset, macros);
+                } else if (skeleton.regionMatches(offset, "unit=", 0, 5)) {
+                    offset += 5;
+                    offset += skeletonToUnit(skeleton, offset, macros);
+                } else if (skeleton.regionMatches(offset, "rounding=", 0, 9)) {
+                    offset += 9;
+                    offset += skeletonToRounding(skeleton, offset, macros);
+                } else if (skeleton.regionMatches(offset, "grouping=", 0, 9)) {
+                    offset += 9;
+                    offset += skeletonToGrouping(skeleton, offset, macros);
+//                } else if (skeleton.regionMatches(offset, "padding=", 0, 9)) {
+//                    offset += 8;
+//                    offset += skeletonToPadding(skeleton, offset, macros);
+                } else if (skeleton.regionMatches(offset, "integer-width=", 0, 9)) {
+                    offset += 14;
+                    offset += skeletonToIntegerWidth(skeleton, offset, macros);
+                } else if (skeleton.regionMatches(offset, "symbols=", 0, 9)) {
+                    offset += 8;
+                    offset += skeletonToSymbols(skeleton, offset, macros);
+                } else if (skeleton.regionMatches(offset, "unit-width=", 0, 9)) {
+                    offset += 11;
+                    offset += skeletonToUnitWidth(skeleton, offset, macros);
+                } else if (skeleton.regionMatches(offset, "sign=", 0, 9)) {
+                    offset += 5;
+                    offset += skeletonToSign(skeleton, offset, macros);
+                } else if (skeleton.regionMatches(offset, "decimal=", 0, 9)) {
+                    offset += 8;
+                    offset += skeletonToDecimal(skeleton, offset, macros);
+                } else {
+                    throw new IllegalArgumentException(
+                            "Unexpected token at offset " + offset + " in skeleton string: " + c);
+                }
+            }
+        }
+        return macros;
+    }
+
+    private static void notationToSkeleton(Notation value, StringBuilder sb) {
+        if (value instanceof ScientificNotation) {
+            ScientificNotation notation = (ScientificNotation) value;
+            sb.append('E');
+            if (notation.engineeringInterval != 1) {
+                sb.append(notation.engineeringInterval);
+            }
+            if (notation.exponentSignDisplay == SignDisplay.ALWAYS) {
+                sb.append('+');
+            } else if (notation.exponentSignDisplay == SignDisplay.NEVER) {
+                sb.append('!');
+            } else {
+                assert notation.exponentSignDisplay == SignDisplay.AUTO;
+            }
+            if (notation.minExponentDigits != 1) {
+                for (int i = 0; i < notation.minExponentDigits; i++) {
+                    sb.append('0');
+                }
+            }
+        } else if (value instanceof CompactNotation) {
+            CompactNotation notation = (CompactNotation) value;
+            if (notation.compactStyle == CompactStyle.SHORT) {
+                sb.append('C');
+            } else {
+                // FIXME: CCC or CCCC instead?
+                sb.append("CC");
+            }
+        } else {
+            assert value instanceof SimpleNotation;
+            sb.append('I');
+        }
+    }
+
+    private static int skeletonToNotation(String skeleton, int offset, MacroProps output) {
+        int originalOffset = offset;
+        char c0 = skeleton.charAt(offset++);
+        Notation result = null;
+        if (c0 == 'E') {
+            int engineering = 1;
+            SignDisplay sign = SignDisplay.AUTO;
+            int minExponentDigits = 0;
+            char c = safeCharAt(skeleton, offset++);
+            if (c >= '1' && c <= '9') {
+                engineering = c - '0';
+                c = safeCharAt(skeleton, offset++);
+            }
+            if (c == '+') {
+                sign = SignDisplay.ALWAYS;
+                c = safeCharAt(skeleton, offset++);
+            }
+            if (c == '!') {
+                sign = SignDisplay.NEVER;
+                c = safeCharAt(skeleton, offset++);
+            }
+            while (c == '0') {
+                minExponentDigits++;
+                c = safeCharAt(skeleton, offset++);
+            }
+            minExponentDigits = Math.max(1, minExponentDigits);
+            result = new ScientificNotation(engineering, false, minExponentDigits, sign);
+        } else if (c0 == 'C') {
+            char c = safeCharAt(skeleton, offset++);
+            if (c == 'C') {
+                result = Notation.compactLong();
+            } else {
+                result = Notation.compactShort();
+            }
+        } else if (c0 == 'I') {
+            result = Notation.simple();
+        }
+        output.notation = result;
+        return offset - originalOffset;
+    }
+
+    private static void unitToSkeleton(MeasureUnit value, StringBuilder sb) {
+        if (value.getType().equals("dimensionless")) {
+            if (value.getSubtype().equals("percent")) {
+                sb.append('%');
+            } else if (value.getSubtype().equals("permille")) {
+                sb.append("%%");
+            } else {
+                assert value.getSubtype().equals("base");
+                sb.append('B');
+            }
+        } else if (value.getType().equals("currency")) {
+            sb.append('$');
+            sb.append(value.getSubtype());
+        } else {
+            sb.append("U:");
+            sb.append(value.getType());
+            sb.append(':');
+            sb.append(value.getSubtype());
+        }
+    }
+
+    private static int skeletonToUnit(String skeleton, int offset, MacroProps output) {
+        int originalOffset = offset;
+        char c0 = skeleton.charAt(offset++);
+        MeasureUnit result = null;
+        if (c0 == '%') {
+            char c = safeCharAt(skeleton, offset++);
+            if (c == '%') {
+                result = Dimensionless.PERCENT;
+            } else {
+                result = Dimensionless.PERMILLE;
+            }
+        } else if (c0 == 'B') {
+            result = Dimensionless.BASE;
+        } else if (c0 == '$') {
+            String currencyCode = skeleton.substring(offset, offset + 3);
+            offset += 3;
+            result = Currency.getInstance(currencyCode);
+        } else if (c0 == 'U') {
+            StringBuilder sb = new StringBuilder();
+            offset += consumeUntil(skeleton, offset, ':', sb);
+            String type = sb.toString();
+            sb.setLength(0);
+            offset += consumeUntil(skeleton, offset, ' ', sb);
+            String subtype = sb.toString();
+            for (MeasureUnit candidate : MeasureUnit.getAvailable(type)) {
+                if (candidate.getSubtype().equals(subtype)) {
+                    result = candidate;
+                    break;
+                }
+            }
+        }
+        output.unit = result;
+        return offset - originalOffset;
+    }
+
+    private static void rounderToSkeleton(Rounder value, StringBuilder sb) {
+        if (!(value instanceof Rounder)) {
+            // FIXME: Throw an exception here instead?
+            return;
+        }
+        MathContext mathContext;
+        if (value instanceof FractionRounderImpl) {
+            FractionRounderImpl rounder = (FractionRounderImpl) value;
+            sb.append('F');
+            minMaxToSkeletonHelper(rounder.minFrac, rounder.maxFrac, sb);
+            mathContext = rounder.mathContext;
+        } else if (value instanceof SignificantRounderImpl) {
+            SignificantRounderImpl rounder = (SignificantRounderImpl) value;
+            sb.append('S');
+            minMaxToSkeletonHelper(rounder.minSig, rounder.maxSig, sb);
+            mathContext = rounder.mathContext;
+        } else if (value instanceof FracSigRounderImpl) {
+            FracSigRounderImpl rounder = (FracSigRounderImpl) value;
+            sb.append('F');
+            minMaxToSkeletonHelper(rounder.minFrac, rounder.maxFrac, sb);
+            if (rounder.minSig != -1) {
+                sb.append('>');
+                sb.append(rounder.minSig);
+            } else {
+                sb.append('<');
+                sb.append(rounder.maxSig);
+            }
+            mathContext = rounder.mathContext;
+        } else if (value instanceof IncrementRounderImpl) {
+            IncrementRounderImpl rounder = (IncrementRounderImpl) value;
+            sb.append('M');
+            sb.append(rounder.increment.toString());
+            mathContext = rounder.mathContext;
+        } else if (value instanceof CurrencyRounderImpl) {
+            CurrencyRounderImpl rounder = (CurrencyRounderImpl) value;
+            sb.append('G');
+            sb.append(rounder.usage.name());
+            mathContext = rounder.mathContext;
+        } else {
+            InfiniteRounderImpl rounder = (InfiniteRounderImpl) value;
+            sb.append('Y');
+            mathContext = rounder.mathContext;
+        }
+        // RoundingMode
+        RoundingMode roundingMode = mathContext.getRoundingMode();
+        if (roundingMode != RoundingMode.HALF_EVEN) {
+            sb.append(';');
+            sb.append(roundingMode.name());
+        }
+    }
+
+    private static void minMaxToSkeletonHelper(int minFrac, int maxFrac, StringBuilder sb) {
+        if (minFrac == maxFrac) {
+            sb.append(minFrac);
+        } else {
+            boolean showMaxFrac = (maxFrac >= 0 && maxFrac < Integer.MAX_VALUE);
+            if (minFrac > 0 || !showMaxFrac) {
+                sb.append(minFrac);
+            }
+            sb.append('-');
+            if (showMaxFrac) {
+                sb.append(maxFrac);
+            }
+        }
+    }
+
+    private static int skeletonToRounding(String skeleton, int offset, MacroProps output) {
+        int originalOffset = offset;
+        char c0 = skeleton.charAt(offset++);
+        Rounder result = null;
+        if (c0 == 'F') {
+            int[] minMax = new int[2];
+            offset += skeletonToMinMaxHelper(skeleton, offset, minMax);
+            FractionRounder temp = Rounder.constructFraction(minMax[0], minMax[1]);
+            char c1 = skeleton.charAt(offset++);
+            if (c1 == '<') {
+                char c2 = skeleton.charAt(offset++);
+                result = temp.withMaxFigures(c2 - '0');
+            } else if (c1 == '>') {
+                char c2 = skeleton.charAt(offset++);
+                result = temp.withMinFigures(c2 - '0');
+            } else {
+                result = temp;
+            }
+        } else if (c0 == 'S') {
+            int[] minMax = new int[2];
+            offset += skeletonToMinMaxHelper(skeleton, offset, minMax);
+            result = Rounder.constructSignificant(minMax[0], minMax[1]);
+        } else if (c0 == 'M') {
+            StringBuilder sb = new StringBuilder();
+            offset += consumeUntil(skeleton, offset, ' ', sb);
+            BigDecimal increment = new BigDecimal(sb.toString());
+            result = Rounder.constructIncrement(increment);
+        } else if (c0 == 'G') {
+            StringBuilder sb = new StringBuilder();
+            offset += consumeUntil(skeleton, offset, ' ', sb);
+            CurrencyUsage usage = Enum.valueOf(CurrencyUsage.class, sb.toString());
+            result = Rounder.constructCurrency(usage);
+        } else if (c0 == 'Y') {
+            result = Rounder.constructInfinite();
+        }
+        output.rounder = result;
+        return offset - originalOffset;
+    }
+
+    private static int skeletonToMinMaxHelper(String skeleton, int offset, int[] output) {
+        int originalOffset = offset;
+        char c0 = safeCharAt(skeleton, offset++);
+        char c1 = safeCharAt(skeleton, offset++);
+        // TODO: This algorithm breaks if the number is more than 1 char wide.
+        if (c1 == '-') {
+            output[0] = c0 - '0';
+            char c2 = safeCharAt(skeleton, offset++);
+            if (c2 == ' ') {
+                output[1] = Integer.MAX_VALUE;
+            } else {
+                output[1] = c2 - '0';
+            }
+        } else if ('0' <= c1 && c1 <= '9') {
+            output[0] = 0;
+            output[1] = c1 - '0';
+        } else {
+            offset--;
+            output[0] = c0 - '0';
+            output[1] = c0 - '0';
+        }
+        return offset - originalOffset;
+    }
+
+    private static void grouperToSkeleton(Grouper value, StringBuilder sb) {
+        if (value.equals(Grouper.defaults())) {
+            sb.append("defaults");
+        } else if (value.equals(Grouper.min2())) {
+            sb.append("min2");
+        } else if (value.equals(Grouper.none())) {
+            sb.append("none");
+        } else {
+            // Not supported in skeleton string
+            sb.append("defaults");
+        }
+    }
+
+    private static int skeletonToGrouping(String skeleton, int offset, MacroProps output) {
+        int originalOffset = offset;
+        char c0 = skeleton.charAt(offset++);
+        Grouper result = null;
+        StringBuilder sb = new StringBuilder();
+        offset += consumeUntil(skeleton, --offset, ' ', sb);
+        String name = sb.toString();
+        if (name.equals("defaults")) {
+            result = Grouper.defaults();
+        } else if (name.equals("min2")) {
+            result = Grouper.min2();
+        } else if (name.equals("none")) {
+            result = Grouper.none();
+        }
+        output.grouper = result;
+        return offset - originalOffset;
+    }
+
+//    private static void paddingToSkeleton(Padder value, StringBuilder sb) {
+//        PaddingImpl padding = (PaddingImpl) value;
+//        if (padding == Padder.NONE) {
+//            sb.append("NONE");
+//            return;
+//        }
+//        sb.append(padding.targetWidth);
+//        sb.append(':');
+//        sb.append(padding.position.name());
+//        sb.append(':');
+//        if (!padding.paddingString.equals(" ")) {
+//            sb.append(padding.paddingString);
+//        }
+//    }
+//
+//    private static int skeletonToPadding(String skeleton, int offset, MacroProps output) {
+//        int originalOffset = offset;
+//        char c0 = skeleton.charAt(offset++);
+//        if (c0 == 'N') {
+//            offset += consumeUntil(skeleton, --offset, ' ', null);
+//        } else if ('0' <= c0 && c0 <= '9') {
+//            long intResult = consumeInt(skeleton, --offset);
+//            offset += intResult & 0xffffffff;
+//            int width = (int) (intResult >>> 32);
+//            char c1 = safeCharAt(skeleton, offset++);
+//            if (c1 != ':') {
+//                return offset - originalOffset - 1;
+//            }
+//            StringBuilder sb = new StringBuilder();
+//            offset += consumeUntil(skeleton, offset, ':', sb);
+//            String padPositionString = sb.toString();
+//            sb.setLength(0);
+//            offset += consumeUntil(skeleton, offset, ' ', sb);
+//            String string = (sb.length() == 0) ? " " : sb.toString();
+//            PadPosition position = Enum.valueOf(PadPosition.class, padPositionString);
+//            output.padder = PaddingImpl.getInstance(string, width, position);
+//        }
+//        return offset - originalOffset;
+//    }
+
+    private static void integerWidthToSkeleton(IntegerWidth value, StringBuilder sb) {
+        sb.append(value.minInt);
+        if (value.maxInt != value.minInt) {
+            sb.append('-');
+            if (value.maxInt != -1) {
+                sb.append(value.maxInt);
+            }
+        }
+    }
+
+    private static int skeletonToIntegerWidth(String skeleton, int offset, MacroProps output) {
+        int originalOffset = offset;
+        long intResult = consumeInt(skeleton, offset);
+        offset += intResult & 0xffffffff;
+        int minInt = (int) (intResult >>> 32);
+        char c1 = safeCharAt(skeleton, --offset);
+        int maxInt;
+        if (c1 == '-') {
+            intResult = consumeInt(skeleton, offset);
+            offset += intResult & 0xffffffff;
+            maxInt = (int) (intResult >>> 32);
+        }
+    }
+
+    private static void symbolsToSkeleton(Object value, StringBuilder sb) {
+        if (value instanceof DecimalFormatSymbols) {
+            // TODO: Check to see if any of the symbols are not default?
+            sb.append("loc:");
+            sb.append(((DecimalFormatSymbols) value).getULocale());
+        } else {
+            sb.append("ns:");
+            sb.append(((NumberingSystem) value).getName());
+        }
+    }
+
+    private static void unitWidthToSkeleton(FormatWidth value, StringBuilder sb) {
+        sb.append(value.name());
+    }
+
+    private static int skeletonToUnitWidth(String skeleton, int offset, MacroProps output) {
+        int originalOffset = offset;
+        StringBuilder sb = new StringBuilder();
+        offset += consumeUntil(skeleton, offset, ' ', sb);
+        output.unitWidth = Enum.valueOf(FormatWidth.class, sb.toString());
+        return offset - originalOffset;
+    }
+
+    private static void signToSkeleton(SignDisplay value, StringBuilder sb) {
+        sb.append(value.name());
+    }
+
+    private static int skeletonToSign(String skeleton, int offset, MacroProps output) {
+        int originalOffset = offset;
+        StringBuilder sb = new StringBuilder();
+        offset += consumeUntil(skeleton, offset, ' ', sb);
+        output.sign = Enum.valueOf(SignDisplay.class, sb.toString());
+        return offset - originalOffset;
+    }
+
+    private static void decimalToSkeleton(DecimalMarkDisplay value, StringBuilder sb) {
+        sb.append(value.name());
+    }
+
+    private static int skeletonToDecimal(String skeleton, int offset, MacroProps output) {
+        int originalOffset = offset;
+        StringBuilder sb = new StringBuilder();
+        offset += consumeUntil(skeleton, offset, ' ', sb);
+        output.decimal = Enum.valueOf(DecimalMarkDisplay.class, sb.toString());
+        return offset - originalOffset;
+    }
+
+    private static char safeCharAt(String str, int offset) {
+        if (offset < str.length()) {
+            return str.charAt(offset);
+        } else {
+            return ' ';
+        }
+    }
+
+    private static int consumeUntil(String skeleton, int offset, char brk, StringBuilder sb) {
+        int originalOffset = offset;
+        char c = safeCharAt(skeleton, offset++);
+        while (c != brk) {
+            if (sb != null)
+                sb.append(c);
+            c = safeCharAt(skeleton, offset++);
+        }
+        return offset - originalOffset;
+    }
+
+    private static long consumeInt(String skeleton, int offset) {
+        int originalOffset = offset;
+        char c = safeCharAt(skeleton, offset++);
+        int result = 0;
+        while ('0' <= c && c <= '9') {
+            result = (result * 10) + (c - '0');
+            c = safeCharAt(skeleton, offset++);
+        }
+        return (offset - originalOffset) | (((long) result) << 32);
+    }
+}
diff --git a/icu4j/main/classes/core/src/newapi/UnlocalizedNumberFormatter.java b/icu4j/main/classes/core/src/newapi/UnlocalizedNumberFormatter.java
new file mode 100644 (file)
index 0000000..fcfa7f9
--- /dev/null
@@ -0,0 +1,32 @@
+// © 2017 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html#License
+package newapi;
+
+import java.util.Locale;
+
+import com.ibm.icu.util.ULocale;
+
+public class UnlocalizedNumberFormatter extends NumberFormatterSettings<UnlocalizedNumberFormatter> {
+
+    /** Base constructor; called during startup only. Sets the threshold to the default value of 3. */
+    UnlocalizedNumberFormatter() {
+        super(null, KEY_THRESHOLD, new Long(3));
+    }
+
+    UnlocalizedNumberFormatter(NumberFormatterSettings<?> parent, int key, Object value) {
+        super(parent, key, value);
+    }
+
+    public LocalizedNumberFormatter locale(Locale locale) {
+        return new LocalizedNumberFormatter(this, KEY_LOCALE, ULocale.forLocale(locale));
+    }
+
+    public LocalizedNumberFormatter locale(ULocale locale) {
+        return new LocalizedNumberFormatter(this, KEY_LOCALE, locale);
+    }
+
+    @Override
+    protected UnlocalizedNumberFormatter create(int key, Object value) {
+        return new UnlocalizedNumberFormatter(this, key, value);
+    }
+}
\ No newline at end of file
diff --git a/icu4j/main/classes/core/src/newapi/Worker1.java b/icu4j/main/classes/core/src/newapi/Worker1.java
new file mode 100644 (file)
index 0000000..1453d7f
--- /dev/null
@@ -0,0 +1,299 @@
+// © 2017 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html#License
+package newapi;
+
+import com.ibm.icu.impl.number.FormatQuantity;
+import com.ibm.icu.impl.number.LdmlPatternInfo;
+import com.ibm.icu.impl.number.LdmlPatternInfo.PatternParseResult;
+import com.ibm.icu.impl.number.NumberStringBuilder;
+import com.ibm.icu.impl.number.modifiers.ConstantAffixModifier;
+import com.ibm.icu.text.CompactDecimalFormat.CompactType;
+import com.ibm.icu.text.DecimalFormatSymbols;
+import com.ibm.icu.text.MeasureFormat.FormatWidth;
+import com.ibm.icu.text.NumberFormat;
+import com.ibm.icu.text.NumberingSystem;
+import com.ibm.icu.text.PluralRules;
+import com.ibm.icu.util.Currency;
+import com.ibm.icu.util.Currency.CurrencyUsage;
+import com.ibm.icu.util.Dimensionless;
+import com.ibm.icu.util.ULocale;
+
+import newapi.NumberFormatter.DecimalMarkDisplay;
+import newapi.NumberFormatter.SignDisplay;
+import newapi.impl.MacroProps;
+import newapi.impl.MicroProps;
+import newapi.impl.Padder;
+import newapi.impl.QuantityChain;
+
+public class Worker1 {
+
+    public static Worker1 fromMacros(MacroProps macros) {
+        return new Worker1(make(macros, true));
+    }
+
+    public static MicroProps applyStatic(MacroProps macros, FormatQuantity inValue, NumberStringBuilder outString) {
+        MicroProps micros = make(macros, false).withQuantity(inValue);
+        applyStatic(micros, inValue, outString);
+        return micros;
+    }
+
+    private static final Currency DEFAULT_CURRENCY = Currency.getInstance("XXX");
+
+    final QuantityChain microsGenerator;
+
+    private Worker1(QuantityChain microsGenerator) {
+        this.microsGenerator = microsGenerator;
+    }
+
+    public MicroProps apply(FormatQuantity inValue, NumberStringBuilder outString) {
+        MicroProps micros = microsGenerator.withQuantity(inValue);
+        applyStatic(micros, inValue, outString);
+        return micros;
+    }
+
+    //////////
+
+    private static QuantityChain make(MacroProps input, boolean build) {
+
+        String innerPattern = null;
+        MurkyLongNameHandler longNames = null;
+        Rounder defaultRounding = Rounder.none();
+        Currency currency = DEFAULT_CURRENCY;
+        FormatWidth unitWidth = null;
+        boolean perMille = false;
+        PluralRules rules = input.rules;
+
+        MicroProps micros = new MicroProps(build);
+        QuantityChain chain = micros;
+
+        // Copy over the simple settings
+        micros.sign = input.sign == null ? SignDisplay.AUTO : input.sign;
+        micros.decimal = input.decimal == null ? DecimalMarkDisplay.AUTO : input.decimal;
+        micros.multiplier = 0;
+        micros.integerWidth = input.integerWidth == null ? IntegerWidth.zeroFillTo(1) : input.integerWidth;
+
+        if (input.unit == null || input.unit == Dimensionless.BASE) {
+            // No units; default format
+            innerPattern = NumberFormat.getPatternForStyle(input.loc, NumberFormat.NUMBERSTYLE);
+        } else if (input.unit == Dimensionless.PERCENT) {
+            // Percent
+            innerPattern = NumberFormat.getPatternForStyle(input.loc, NumberFormat.PERCENTSTYLE);
+            micros.multiplier += 2;
+        } else if (input.unit == Dimensionless.PERMILLE) {
+            // Permille
+            innerPattern = NumberFormat.getPatternForStyle(input.loc, NumberFormat.PERCENTSTYLE);
+            micros.multiplier += 3;
+            perMille = true;
+        } else if (input.unit instanceof Currency && input.unitWidth != FormatWidth.WIDE) {
+            // Narrow, short, or ISO currency.
+            // TODO: Accounting style?
+            innerPattern = NumberFormat.getPatternForStyle(input.loc, NumberFormat.CURRENCYSTYLE);
+            defaultRounding = Rounder.currency(CurrencyUsage.STANDARD);
+            currency = (Currency) input.unit;
+            micros.useCurrency = true;
+            unitWidth = (input.unitWidth == null) ? FormatWidth.NARROW : input.unitWidth;
+        } else if (input.unit instanceof Currency) {
+            // Currency long name
+            innerPattern = NumberFormat.getPatternForStyle(input.loc, NumberFormat.NUMBERSTYLE);
+            longNames = MurkyLongNameHandler.getCurrencyLongNameModifiers(input.loc, (Currency) input.unit);
+            defaultRounding = Rounder.currency(CurrencyUsage.STANDARD);
+            currency = (Currency) input.unit;
+            micros.useCurrency = true;
+            unitWidth = input.unitWidth = FormatWidth.WIDE;
+        } else {
+            // MeasureUnit
+            innerPattern = NumberFormat.getPatternForStyle(input.loc, NumberFormat.NUMBERSTYLE);
+            unitWidth = (input.unitWidth == null) ? FormatWidth.SHORT : input.unitWidth;
+            longNames = MurkyLongNameHandler.getMeasureUnitModifiers(input.loc, input.unit, unitWidth);
+        }
+
+        // Parse the pattern, which is used for grouping and affixes only.
+        PatternParseResult patternInfo = LdmlPatternInfo.parse(innerPattern);
+
+        // Symbols
+        if (input.symbols == null) {
+            micros.symbols = DecimalFormatSymbols.getInstance(input.loc);
+        } else if (input.symbols instanceof DecimalFormatSymbols) {
+            micros.symbols = (DecimalFormatSymbols) input.symbols;
+        } else if (input.symbols instanceof NumberingSystem) {
+            // TODO: Do this more efficiently. Will require modifying DecimalFormatSymbols.
+            NumberingSystem ns = (NumberingSystem) input.symbols;
+            ULocale temp = input.loc.setKeywordValue("numbers", ns.getName());
+            micros.symbols = DecimalFormatSymbols.getInstance(temp);
+        } else {
+            throw new AssertionError();
+        }
+
+        // TODO: Normalize the currency (accept symbols from DecimalFormatSymbols)?
+        // currency = CustomSymbolCurrency.resolve(currency, input.loc, micros.symbols);
+
+        // Multiplier (compatibility mode value).
+        // An int magnitude multiplier is used when not in compatibility mode to
+        // reduce object creations.
+        if (input.multiplier != null) {
+            chain = input.multiplier.copyAndChain(chain);
+        }
+
+        // Rounding strategy
+        if (input.rounder != null) {
+            micros.rounding = Rounder.normalizeType(input.rounder, currency);
+        } else if (input.notation instanceof CompactNotation) {
+            micros.rounding = Rounder.COMPACT_STRATEGY;
+        } else {
+            micros.rounding = Rounder.normalizeType(defaultRounding, currency);
+        }
+
+        // Grouping strategy
+        if (input.grouper != null) {
+            micros.grouping = Grouper.normalizeType(input.grouper, patternInfo);
+        } else if (input.notation instanceof CompactNotation) {
+            // Compact notation uses minGrouping by default since ICU 59
+            micros.grouping = Grouper.normalizeType(Grouper.min2(), patternInfo);
+        } else {
+            micros.grouping = Grouper.normalizeType(Grouper.defaults(), patternInfo);
+        }
+
+        // Inner modifier (scientific notation)
+        if (input.notation instanceof ScientificNotation) {
+            chain = ((ScientificNotation) input.notation).withLocaleData(micros.symbols, build, chain);
+        } else {
+            // No inner modifier required
+            micros.modInner = ConstantAffixModifier.EMPTY;
+        }
+
+        // Middle modifier (patterns, positive/negative, currency symbols, percent)
+        // The default middle modifier is weak (thus the false argument).
+        MurkyModifier murkyMod = new MurkyModifier(false);
+        murkyMod.setPatternInfo((input.affixProvider != null) ? input.affixProvider : patternInfo);
+        murkyMod.setPatternAttributes(micros.sign, perMille);
+        if (murkyMod.needsPlurals()) {
+            if (rules == null) {
+                // Lazily create PluralRules
+                rules = PluralRules.forLocale(input.loc);
+            }
+            murkyMod.setSymbols(micros.symbols, currency, unitWidth, rules);
+        } else {
+            murkyMod.setSymbols(micros.symbols, currency, unitWidth, null);
+        }
+        if (build) {
+            chain = murkyMod.createImmutableAndChain(chain);
+        } else {
+            chain = murkyMod.addToChain(chain);
+        }
+
+        // Outer modifier (CLDR units and currency long names)
+        if (longNames != null) {
+            if (rules == null) {
+                // Lazily create PluralRules
+                rules = PluralRules.forLocale(input.loc);
+            }
+            chain = longNames.withLocaleData(rules, build, chain);
+        } else {
+            // No outer modifier required
+            micros.modOuter = ConstantAffixModifier.EMPTY;
+        }
+
+        // Padding strategy
+        if (input.padder != null) {
+            micros.padding = input.padder;
+        } else {
+            micros.padding = Padder.none();
+        }
+
+        // Compact notation
+        // NOTE: Compact notation can (but might not) override the middle modifier and rounding.
+        // It therefore needs to go at the end of the chain.
+        if (input.notation instanceof CompactNotation) {
+            if (rules == null) {
+                // Lazily create PluralRules
+                rules = PluralRules.forLocale(input.loc);
+            }
+            CompactType compactType = (input.unit instanceof Currency) ? CompactType.CURRENCY : CompactType.DECIMAL;
+            chain = ((CompactNotation) input.notation).withLocaleData(input.loc, compactType, rules,
+                    build ? murkyMod : null, chain);
+        }
+
+        return chain;
+    }
+
+    //////////
+
+    private static int applyStatic(MicroProps micros, FormatQuantity inValue, NumberStringBuilder outString) {
+        inValue.adjustMagnitude(micros.multiplier);
+        micros.rounding.apply(inValue);
+        if (micros.integerWidth.maxInt == -1) {
+            inValue.setIntegerLength(micros.integerWidth.minInt, Integer.MAX_VALUE);
+        } else {
+            inValue.setIntegerLength(micros.integerWidth.minInt, micros.integerWidth.maxInt);
+        }
+        int length = writeNumber(micros, inValue, outString);
+        // NOTE: When range formatting is added, these modifiers can bubble up.
+        // For now, apply them all here at once.
+        length += micros.padding.applyModsAndMaybePad(micros, outString, 0, length);
+        return length;
+    }
+
+    private static int writeNumber(MicroProps micros, FormatQuantity input, NumberStringBuilder string) {
+        int length = 0;
+        if (input.isInfinite()) {
+            length += string.insert(length, micros.symbols.getInfinity(), NumberFormat.Field.INTEGER);
+
+        } else if (input.isNaN()) {
+            length += string.insert(length, micros.symbols.getNaN(), NumberFormat.Field.INTEGER);
+
+        } else {
+            // Add the integer digits
+            length += writeIntegerDigits(micros, input, string);
+
+            // Add the decimal point
+            if (input.getLowerDisplayMagnitude() < 0 || micros.decimal == DecimalMarkDisplay.ALWAYS) {
+                length += string.insert(length, micros.useCurrency ? micros.symbols.getMonetaryDecimalSeparatorString()
+                        : micros.symbols.getDecimalSeparatorString(), NumberFormat.Field.DECIMAL_SEPARATOR);
+            }
+
+            // Add the fraction digits
+            length += writeFractionDigits(micros, input, string);
+        }
+
+        return length;
+    }
+
+    private static int writeIntegerDigits(MicroProps micros, FormatQuantity input, NumberStringBuilder string) {
+        int length = 0;
+        int integerCount = input.getUpperDisplayMagnitude() + 1;
+        for (int i = 0; i < integerCount; i++) {
+            // Add grouping separator
+            if (micros.grouping.groupAtPosition(i, input)) {
+                length += string.insert(0, micros.useCurrency ? micros.symbols.getMonetaryGroupingSeparatorString()
+                        : micros.symbols.getGroupingSeparatorString(), NumberFormat.Field.GROUPING_SEPARATOR);
+            }
+
+            // Get and append the next digit value
+            byte nextDigit = input.getDigit(i);
+            if (micros.symbols.getCodePointZero() != -1) {
+                length += string.insertCodePoint(0, micros.symbols.getCodePointZero() + nextDigit,
+                        NumberFormat.Field.INTEGER);
+            } else {
+                length += string.insert(0, micros.symbols.getDigitStringsLocal()[nextDigit],
+                        NumberFormat.Field.INTEGER);
+            }
+        }
+        return length;
+    }
+
+    private static int writeFractionDigits(MicroProps micros, FormatQuantity input, NumberStringBuilder string) {
+        int length = 0;
+        int fractionCount = -input.getLowerDisplayMagnitude();
+        for (int i = 0; i < fractionCount; i++) {
+            // Get and append the next digit value
+            byte nextDigit = input.getDigit(-i - 1);
+            if (micros.symbols.getCodePointZero() != -1) {
+                length += string.appendCodePoint(micros.symbols.getCodePointZero() + nextDigit,
+                        NumberFormat.Field.FRACTION);
+            } else {
+                length += string.append(micros.symbols.getDigitStringsLocal()[nextDigit], NumberFormat.Field.FRACTION);
+            }
+        }
+        return length;
+    }
+}
index f18ce4500721e0c1671d94bcc10515382c84d64a..f2fb71d92d860309f16da89e2a0dea6c1f061717 100644 (file)
@@ -18,7 +18,7 @@ import com.ibm.icu.text.NumberingSystem;
 import com.ibm.icu.util.ULocale;
 import com.ibm.icu.util.UResourceBundle;
 
-class CompactData implements RoundingImpl.MultiplierProducer {
+public class CompactData implements MultiplierProducer {
 
   public static CompactData getInstance(
       ULocale locale, CompactType compactType, CompactStyle compactStyle) {
diff --git a/icu4j/main/classes/core/src/newapi/impl/CompactImpl.java b/icu4j/main/classes/core/src/newapi/impl/CompactImpl.java
deleted file mode 100644 (file)
index fe7de89..0000000
+++ /dev/null
@@ -1,125 +0,0 @@
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-package newapi.impl;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-
-import com.ibm.icu.impl.StandardPlural;
-import com.ibm.icu.impl.number.FormatQuantity;
-import com.ibm.icu.impl.number.LdmlPatternInfo;
-import com.ibm.icu.impl.number.LdmlPatternInfo.PatternParseResult;
-import com.ibm.icu.text.CompactDecimalFormat.CompactStyle;
-import com.ibm.icu.text.CompactDecimalFormat.CompactType;
-import com.ibm.icu.text.PluralRules;
-import com.ibm.icu.util.ULocale;
-
-import newapi.impl.MurkyModifier.ImmutableMurkyModifier;
-import newapi.impl.RoundingImpl.RoundingImplDummy;
-
-public class CompactImpl implements QuantityChain {
-
-  final PluralRules rules;
-  final CompactData data;
-  final Map<String, CompactModInfo> precomputedMods;
-  final QuantityChain parent;
-
-  public static CompactImpl getInstance(
-      ULocale dataLocale,
-      CompactType compactType,
-      CompactStyle compactStyle,
-      PluralRules rules,
-      MurkyModifier buildReference,
-      QuantityChain parent) {
-    CompactData data = CompactData.getInstance(dataLocale, compactType, compactStyle);
-    return new CompactImpl(data, rules, buildReference, parent);
-  }
-
-  public static CompactImpl getInstance(
-      Map<String, Map<String, String>> compactCustomData,
-      PluralRules rules,
-      MurkyModifier buildReference,
-      QuantityChain parent) {
-    CompactData data = CompactData.getInstance(compactCustomData);
-    return new CompactImpl(data, rules, buildReference, parent);
-  }
-
-  private CompactImpl(
-      CompactData data, PluralRules rules, MurkyModifier buildReference, QuantityChain parent) {
-    this.data = data;
-    this.rules = rules;
-    if (buildReference != null) {
-      precomputedMods = precomputeAllModifiers(data, buildReference);
-    } else {
-      precomputedMods = null;
-    }
-    this.parent = parent;
-  }
-
-  /** To be used by the building code path */
-  public static Map<String, CompactModInfo> precomputeAllModifiers(
-      CompactData data, MurkyModifier buildReference) {
-    Map<String, CompactModInfo> precomputedMods = new HashMap<String, CompactModInfo>();
-    Set<String> allPatterns = data.getAllPatterns();
-    for (String patternString : allPatterns) {
-      CompactModInfo info = new CompactModInfo();
-      PatternParseResult patternInfo = LdmlPatternInfo.parse(patternString);
-      buildReference.setPatternInfo(patternInfo);
-      info.mod = buildReference.createImmutable();
-      info.numDigits = patternInfo.positive.totalIntegerDigits;
-      precomputedMods.put(patternString, info);
-    }
-    return precomputedMods;
-  }
-
-  private static class CompactModInfo {
-    public ImmutableMurkyModifier mod;
-    public int numDigits;
-  }
-
-  @Override
-  public MicroProps withQuantity(FormatQuantity input) {
-    MicroProps micros = parent.withQuantity(input);
-    assert micros.rounding != null;
-
-    // Treat zero as if it had magnitude 0
-    int magnitude;
-    if (input.isZero()) {
-      magnitude = 0;
-      micros.rounding.apply(input);
-    } else {
-      // TODO: Revisit chooseMultiplierAndApply
-      int multiplier = micros.rounding.chooseMultiplierAndApply(input, data);
-      magnitude = input.isZero() ? 0 : input.getMagnitude();
-      magnitude -= multiplier;
-    }
-
-    StandardPlural plural = input.getStandardPlural(rules);
-    String patternString = data.getPattern(magnitude, plural);
-    int numDigits = -1;
-    if (patternString == null) {
-      // Use the default (non-compact) modifier.
-      // No need to take any action.
-    } else if (precomputedMods != null) {
-      // Build code path.
-      CompactModInfo info = precomputedMods.get(patternString);
-      info.mod.applyToMicros(micros, input);
-      numDigits = info.numDigits;
-    } else {
-      // Non-build code path.
-      // Overwrite the PatternInfo in the existing modMiddle
-      assert micros.modMiddle instanceof MurkyModifier;
-      PatternParseResult patternInfo = LdmlPatternInfo.parse(patternString);
-      ((MurkyModifier) micros.modMiddle).setPatternInfo(patternInfo);
-      numDigits = patternInfo.positive.totalIntegerDigits;
-    }
-
-    // FIXME: Deal with numDigits == 0  (Awaiting a test case)
-
-    // We already performed rounding.  Do not perform it again.
-    micros.rounding = RoundingImplDummy.INSTANCE;
-
-    return micros;
-  }
-}
diff --git a/icu4j/main/classes/core/src/newapi/impl/DataUtils.java b/icu4j/main/classes/core/src/newapi/impl/DataUtils.java
deleted file mode 100644 (file)
index f505d01..0000000
+++ /dev/null
@@ -1,67 +0,0 @@
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-package newapi.impl;
-
-import java.util.EnumMap;
-import java.util.Map;
-
-import com.ibm.icu.impl.CurrencyData;
-import com.ibm.icu.impl.SimpleFormatterImpl;
-import com.ibm.icu.impl.StandardPlural;
-import com.ibm.icu.impl.number.Modifier;
-import com.ibm.icu.impl.number.modifiers.SimpleModifier;
-import com.ibm.icu.text.MeasureFormat.FormatWidth;
-import com.ibm.icu.text.NumberFormat.Field;
-import com.ibm.icu.util.Currency;
-import com.ibm.icu.util.MeasureUnit;
-import com.ibm.icu.util.ULocale;
-
-public class DataUtils {
-
-  public static Map<StandardPlural, Modifier> getCurrencyLongNameModifiers(
-      ULocale loc, Currency currency) {
-    Map<String, String> data = CurrencyData.provider.getInstance(loc, true).getUnitPatterns();
-    Map<StandardPlural, Modifier> result =
-        new EnumMap<StandardPlural, Modifier>(StandardPlural.class);
-    StringBuilder sb = new StringBuilder();
-    for (Map.Entry<String, String> e : data.entrySet()) {
-      String pluralKeyword = e.getKey();
-      StandardPlural plural = StandardPlural.fromString(e.getKey());
-      String longName = currency.getName(loc, Currency.PLURAL_LONG_NAME, pluralKeyword, null);
-      String simpleFormat = e.getValue(); // e.g., "{0} {1}"
-      simpleFormat = simpleFormat.replace("{1}", longName);
-      String compiled = SimpleFormatterImpl.compileToStringMinMaxArguments(simpleFormat, sb, 1, 1);
-      Modifier mod = new SimpleModifier(compiled, Field.CURRENCY, false);
-      result.put(plural, mod);
-    }
-    return result;
-  }
-
-  public static Map<StandardPlural, Modifier> getMeasureUnitModifiers(
-      ULocale loc, MeasureUnit unit, FormatWidth width) {
-    Map<StandardPlural, String> simpleFormats = MeasureData.getMeasureData(loc, unit, width);
-    Map<StandardPlural, Modifier> result =
-        new EnumMap<StandardPlural, Modifier>(StandardPlural.class);
-    StringBuilder sb = new StringBuilder();
-    for (StandardPlural plural : StandardPlural.VALUES) {
-      if (simpleFormats.get(plural) == null) {
-        plural = StandardPlural.OTHER;
-      }
-      String simpleFormat = simpleFormats.get(plural);
-      String compiled = SimpleFormatterImpl.compileToStringMinMaxArguments(simpleFormat, sb, 1, 1);
-      Modifier mod = new SimpleModifier(compiled, Field.CURRENCY, false);
-      result.put(plural, mod);
-    }
-    return result;
-    //    Map<StandardPlural, Modifier> result =
-    //        new EnumMap<StandardPlural, Modifier>(StandardPlural.class);
-    //    // TODO: Get the data directly instead of taking the detour through MeasureFormat.
-    //    MeasureFormat mf = MeasureFormat.getInstance(loc, width);
-    //    for (StandardPlural plural : StandardPlural.VALUES) {
-    //      String compiled = mf.getPluralFormatter(unit, width, plural.ordinal());
-    //      Modifier mod = new SimpleModifier(compiled, null, false);
-    //      result.put(plural, mod);
-    //    }
-    //    return result;
-  }
-}
diff --git a/icu4j/main/classes/core/src/newapi/impl/GroupingImpl.java b/icu4j/main/classes/core/src/newapi/impl/GroupingImpl.java
deleted file mode 100644 (file)
index 8ad25cc..0000000
+++ /dev/null
@@ -1,135 +0,0 @@
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-package newapi.impl;
-
-import java.math.BigDecimal;
-
-import com.ibm.icu.impl.number.FormatQuantity;
-import com.ibm.icu.impl.number.LdmlPatternInfo.PatternParseResult;
-
-import newapi.NumberFormatter.Grouping;
-import newapi.NumberFormatter.IGrouping;
-
-public class GroupingImpl extends Grouping.Internal {
-
-  // Conveniences for Java handling of shorts
-  private static final byte B2 = 2;
-  private static final byte B3 = 3;
-
-  // For the "placeholder constructor"
-  public static final char TYPE_PLACEHOLDER = 0;
-  public static final char TYPE_MIN2 = 1;
-  public static final char TYPE_NONE = 2;
-
-  // Statically initialized objects (cannot be used statically by other ICU classes)
-  static final GroupingImpl NONE = new GroupingImpl(TYPE_NONE);
-  static final GroupingImpl GROUPING_3 = new GroupingImpl(B3, B3, false);
-  static final GroupingImpl GROUPING_3_2 = new GroupingImpl(B3, B2, false);
-  static final GroupingImpl GROUPING_3_MIN2 = new GroupingImpl(B3, B3, true);
-  static final GroupingImpl GROUPING_3_2_MIN2 = new GroupingImpl(B3, B2, true);
-
-  static GroupingImpl getInstance(byte grouping1, byte grouping2, boolean min2) {
-    if (grouping1 == -1) {
-      return NONE;
-    } else if (!min2 && grouping1 == 3 && grouping2 == 3) {
-      return GROUPING_3;
-    } else if (!min2 && grouping1 == 3 && grouping2 == 2) {
-      return GROUPING_3_2;
-    } else if (min2 && grouping1 == 3 && grouping2 == 3) {
-      return GROUPING_3_MIN2;
-    } else if (min2 && grouping1 == 3 && grouping2 == 2) {
-      return GROUPING_3_2_MIN2;
-    } else {
-      return new GroupingImpl(grouping1, grouping2, min2);
-    }
-  }
-
-  public static GroupingImpl normalizeType(IGrouping grouping, PatternParseResult patternInfo) {
-    assert grouping != null;
-    if (grouping instanceof GroupingImpl) {
-      return ((GroupingImpl) grouping).withLocaleData(patternInfo);
-    } else {
-      return new GroupingImpl(grouping);
-    }
-  }
-
-  final IGrouping lambda;
-  final byte grouping1; // -2 means "needs locale data"; -1 means "no grouping"
-  final byte grouping2;
-  final boolean min2;
-
-  /** The "placeholder constructor". Pass in one of the GroupingImpl.TYPE_* variables. */
-  public GroupingImpl(char type) {
-    lambda = null;
-    switch (type) {
-      case TYPE_PLACEHOLDER:
-        grouping1 = -2;
-        grouping2 = -2;
-        min2 = false;
-        break;
-      case TYPE_MIN2:
-        grouping1 = -2;
-        grouping2 = -2;
-        min2 = true;
-        break;
-      case TYPE_NONE:
-        grouping1 = -1;
-        grouping2 = -1;
-        min2 = false;
-        break;
-      default:
-        throw new AssertionError();
-    }
-  }
-
-  private GroupingImpl(byte grouping1, byte grouping2, boolean min2) {
-    this.lambda = null;
-    this.grouping1 = grouping1;
-    this.grouping2 = grouping2;
-    this.min2 = min2;
-  }
-
-  private GroupingImpl(IGrouping lambda) {
-    this.lambda = lambda;
-    this.grouping1 = -3;
-    this.grouping2 = -3;
-    this.min2 = false;
-  }
-
-  GroupingImpl withLocaleData(PatternParseResult patternInfo) {
-    if (grouping1 != -2) {
-      return this;
-    }
-    assert lambda == null;
-    // TODO: short or byte?
-    byte grouping1 = (byte) (patternInfo.positive.groupingSizes & 0xffff);
-    byte grouping2 = (byte) ((patternInfo.positive.groupingSizes >>> 16) & 0xffff);
-    byte grouping3 = (byte) ((patternInfo.positive.groupingSizes >>> 32) & 0xffff);
-    if (grouping2 == -1) {
-      grouping1 = -1;
-    }
-    if (grouping3 == -1) {
-      grouping2 = grouping1;
-    }
-    return getInstance(grouping1, grouping2, min2);
-  }
-
-  boolean groupAtPosition(int position, FormatQuantity value) {
-    // Check for lambda function
-    if (lambda != null) {
-      // TODO: Cache the BigDecimal
-      BigDecimal temp = value.toBigDecimal();
-      return lambda.groupAtPosition(position, temp);
-    }
-
-    assert grouping1 != -2;
-    if (grouping1 == -1 || grouping1 == 0) {
-      // Either -1 or 0 means "no grouping"
-      return false;
-    }
-    position -= grouping1;
-    return position >= 0
-        && (position % grouping2) == 0
-        && value.getUpperDisplayMagnitude() - grouping1 + 1 >= (min2 ? 2 : 1);
-  }
-}
diff --git a/icu4j/main/classes/core/src/newapi/impl/IntegerWidthImpl.java b/icu4j/main/classes/core/src/newapi/impl/IntegerWidthImpl.java
deleted file mode 100644 (file)
index 8c8695e..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-package newapi.impl;
-
-import newapi.NumberFormatter.IntegerWidth;
-import newapi.NumberFormatter.Rounding;
-
-public final class IntegerWidthImpl extends IntegerWidth.Internal {
-  public final int minInt;
-  public final int maxInt;
-
-  public static final IntegerWidthImpl DEFAULT = new IntegerWidthImpl();
-
-  /** Default constructor */
-  public IntegerWidthImpl() {
-    this(1, Integer.MAX_VALUE);
-  }
-
-  public IntegerWidthImpl(int minInt, int maxInt) {
-    this.minInt = minInt;
-    this.maxInt = maxInt;
-  }
-
-  @Override
-  public IntegerWidthImpl truncateAt(int maxInt) {
-    if (maxInt == this.maxInt) {
-      return this;
-    } else if (maxInt >= 0 && maxInt < Rounding.MAX_VALUE) {
-      return new IntegerWidthImpl(minInt, maxInt);
-    } else if (maxInt == Integer.MAX_VALUE) {
-      return new IntegerWidthImpl(minInt, maxInt);
-    } else {
-      throw new IllegalArgumentException(
-          "Integer digits must be between 0 and " + Rounding.MAX_VALUE);
-    }
-  }
-}
index 16a11d709bce0ff27bbad90e38527601c5bebe96..e135116b0f71629b703556bca70501bf889df68c 100644 (file)
@@ -9,20 +9,20 @@ import com.ibm.icu.text.PluralRules;
 import com.ibm.icu.util.MeasureUnit;
 import com.ibm.icu.util.ULocale;
 
+import newapi.Grouper;
+import newapi.IntegerWidth;
+import newapi.Notation;
+import newapi.NumberFormatter;
+import newapi.Rounder;
 import newapi.NumberFormatter.DecimalMarkDisplay;
-import newapi.NumberFormatter.IGrouping;
-import newapi.NumberFormatter.IRounding;
-import newapi.NumberFormatter.IntegerWidth;
-import newapi.NumberFormatter.Notation;
-import newapi.NumberFormatter.Padding;
 import newapi.NumberFormatter.SignDisplay;
 
 public class MacroProps implements Cloneable {
   public Notation notation;
   public MeasureUnit unit;
-  public IRounding rounding;
-  public IGrouping grouping;
-  public Padding padding;
+  public Rounder rounder;
+  public Grouper grouper;
+  public Padder padder;
   public IntegerWidth integerWidth;
   public Object symbols;
   public FormatWidth unitWidth;
@@ -42,9 +42,9 @@ public class MacroProps implements Cloneable {
   public void fallback(MacroProps fallback) {
     if (notation == null) notation = fallback.notation;
     if (unit == null) unit = fallback.unit;
-    if (rounding == null) rounding = fallback.rounding;
-    if (grouping == null) grouping = fallback.grouping;
-    if (padding == null) padding = fallback.padding;
+    if (rounder == null) rounder = fallback.rounder;
+    if (grouper == null) grouper = fallback.grouper;
+    if (padder == null) padder = fallback.padder;
     if (integerWidth == null) integerWidth = fallback.integerWidth;
     if (symbols == null) symbols = fallback.symbols;
     if (unitWidth == null) unitWidth = fallback.unitWidth;
@@ -61,9 +61,9 @@ public class MacroProps implements Cloneable {
     return Objects.hash(
         notation,
         unit,
-        rounding,
-        grouping,
-        padding,
+        rounder,
+        grouper,
+        padder,
         integerWidth,
         symbols,
         unitWidth,
@@ -80,9 +80,9 @@ public class MacroProps implements Cloneable {
     MacroProps other = (MacroProps) _other;
     return Objects.equals(notation, other.notation)
         && Objects.equals(unit, other.unit)
-        && Objects.equals(rounding, other.rounding)
-        && Objects.equals(grouping, other.grouping)
-        && Objects.equals(padding, other.padding)
+        && Objects.equals(rounder, other.rounder)
+        && Objects.equals(grouper, other.grouper)
+        && Objects.equals(padder, other.padder)
         && Objects.equals(integerWidth, other.integerWidth)
         && Objects.equals(symbols, other.symbols)
         && Objects.equals(unitWidth, other.unitWidth)
index 6c57121f2722767478daf72a86a69df411a33fc8..dc0aa88bdb22babfb196c35c6b76fdb6e2635975 100644 (file)
@@ -6,6 +6,10 @@ import com.ibm.icu.impl.number.FormatQuantity;
 import com.ibm.icu.impl.number.Modifier;
 import com.ibm.icu.text.DecimalFormatSymbols;
 
+import newapi.Grouper;
+import newapi.IntegerWidth;
+import newapi.NumberFormatter;
+import newapi.Rounder;
 import newapi.NumberFormatter.DecimalMarkDisplay;
 import newapi.NumberFormatter.SignDisplay;
 
@@ -13,16 +17,16 @@ public class MicroProps implements Cloneable, QuantityChain {
   // Populated globally:
   public SignDisplay sign;
   public DecimalFormatSymbols symbols;
-  public PaddingImpl padding;
+  public Padder padding;
   public DecimalMarkDisplay decimal;
-  public IntegerWidthImpl integerWidth;
+  public IntegerWidth integerWidth;
 
   // Populated by notation/unit:
   public Modifier modOuter;
   public Modifier modMiddle;
   public Modifier modInner;
-  public RoundingImpl rounding;
-  public GroupingImpl grouping;
+  public Rounder rounding;
+  public Grouper grouping;
   public int multiplier;
   public boolean useCurrency;
 
diff --git a/icu4j/main/classes/core/src/newapi/impl/MultiplierProducer.java b/icu4j/main/classes/core/src/newapi/impl/MultiplierProducer.java
new file mode 100644 (file)
index 0000000..814ceaa
--- /dev/null
@@ -0,0 +1,7 @@
+// © 2017 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html#License
+package newapi.impl;
+
+public interface MultiplierProducer {
+    int getMultiplier(int magnitude);
+}
\ No newline at end of file
diff --git a/icu4j/main/classes/core/src/newapi/impl/MurkyModifier.java b/icu4j/main/classes/core/src/newapi/impl/MurkyModifier.java
deleted file mode 100644 (file)
index 27159e4..0000000
+++ /dev/null
@@ -1,465 +0,0 @@
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-package newapi.impl;
-
-import com.ibm.icu.impl.StandardPlural;
-import com.ibm.icu.impl.number.AffixPatternUtils;
-import com.ibm.icu.impl.number.AffixPatternUtils.SymbolProvider;
-import com.ibm.icu.impl.number.FormatQuantity;
-import com.ibm.icu.impl.number.LdmlPatternInfo;
-import com.ibm.icu.impl.number.Modifier;
-import com.ibm.icu.impl.number.NumberStringBuilder;
-import com.ibm.icu.impl.number.modifiers.ConstantMultiFieldModifier;
-import com.ibm.icu.text.DecimalFormatSymbols;
-import com.ibm.icu.text.MeasureFormat.FormatWidth;
-import com.ibm.icu.text.PluralRules;
-import com.ibm.icu.util.Currency;
-
-import newapi.NumberFormatter.SignDisplay;
-
-/**
- * This class is a {@link Modifier} that wraps a decimal format pattern. It applies the pattern's
- * affixes in {@link Modifier#apply}.
- *
- * <p>In addition to being a Modifier, this class contains the business logic for substituting the
- * correct locale symbols into the affixes of the decimal format pattern.
- *
- * <p>In order to use this class, create a new instance and call the following four setters: {@link
- * #setPatternInfo}, {@link #setPatternAttributes}, {@link #setSymbols}, and {@link
- * #setNumberProperties}. After calling these four setters, the instance will be ready for use as a
- * Modifier.
- *
- * <p>This is a MUTABLE, NON-THREAD-SAFE class designed for performance. Do NOT save references to
- * this or attempt to use it from multiple threads! Instead, you can obtain a safe, immutable
- * decimal format pattern modifier by calling {@link MurkyModifier#createImmutable}, in effect
- * treating this instance as a builder for the immutable variant.
- */
-public class MurkyModifier implements Modifier, SymbolProvider, CharSequence, QuantityChain {
-
-  // Modifier details
-  final boolean isStrong;
-
-  // Pattern details
-  AffixPatternProvider patternInfo;
-  SignDisplay signDisplay;
-  boolean perMilleReplacesPercent;
-
-  // Symbol details
-  DecimalFormatSymbols symbols;
-  FormatWidth unitWidth;
-  String currency1;
-  String currency2;
-  String[] currency3;
-  PluralRules rules;
-
-  // Number details
-  boolean isNegative;
-  StandardPlural plural;
-
-  // QuantityChain details
-  QuantityChain parent;
-
-  // Transient CharSequence fields
-  boolean inCharSequenceMode;
-  int flags;
-  int length;
-  boolean prependSign;
-  boolean plusReplacesMinusSign;
-
-  /**
-   * @param isStrong Whether the modifier should be considered strong. For more information, see
-   *     {@link Modifier#isStrong()}. Most of the time, decimal format pattern modifiers should be
-   *     considered as non-strong.
-   */
-  public MurkyModifier(boolean isStrong) {
-    this.isStrong = isStrong;
-  }
-
-  /**
-   * Sets a reference to the parsed decimal format pattern, usually obtained from {@link
-   * LdmlPatternInfo#parse(String)}, but any implementation of {@link AffixPatternProvider} is
-   * accepted.
-   */
-  public void setPatternInfo(AffixPatternProvider patternInfo) {
-    this.patternInfo = patternInfo;
-  }
-
-  /**
-   * Sets attributes that imply changes to the literal interpretation of the pattern string affixes.
-   *
-   * @param signDisplay Whether to force a plus sign on positive numbers.
-   * @param perMille Whether to substitute the percent sign in the pattern with a permille sign.
-   */
-  public void setPatternAttributes(SignDisplay signDisplay, boolean perMille) {
-    this.signDisplay = signDisplay;
-    this.perMilleReplacesPercent = perMille;
-  }
-
-  /**
-   * Sets locale-specific details that affect the symbols substituted into the pattern string
-   * affixes.
-   *
-   * @param symbols The desired instance of DecimalFormatSymbols.
-   * @param currency The currency to be used when substituting currency values into the affixes.
-   *     Cannot be null, but a bogus currency like "XXX" can be used.
-   * @param unitWidth The width used to render currencies.
-   * @param rules Required if the triple currency sign, "¤¤¤", appears in the pattern, which can be
-   *     determined from the convenience method {@link #needsPlurals()}.
-   */
-  public void setSymbols(
-      DecimalFormatSymbols symbols, Currency currency, FormatWidth unitWidth, PluralRules rules) {
-    assert (rules != null) == needsPlurals();
-    this.symbols = symbols;
-    this.unitWidth = unitWidth;
-    this.rules = rules;
-
-    currency1 = currency.getName(symbols.getULocale(), Currency.SYMBOL_NAME, null);
-    currency2 = currency.getCurrencyCode();
-
-    if (rules != null) {
-      currency3 = new String[StandardPlural.COUNT];
-      for (StandardPlural plural : StandardPlural.VALUES) {
-        currency3[plural.ordinal()] =
-            currency.getName(
-                symbols.getULocale(), Currency.PLURAL_LONG_NAME, plural.getKeyword(), null);
-      }
-    }
-  }
-
-  /**
-   * Sets attributes of the current number being processed.
-   *
-   * @param isNegative Whether the number is negative.
-   * @param plural The plural form of the number, required only if the pattern contains the triple
-   *     currency sign, "¤¤¤" (and as indicated by {@link #needsPlurals()}).
-   */
-  public void setNumberProperties(boolean isNegative, StandardPlural plural) {
-    assert (plural != null) == needsPlurals();
-    this.isNegative = isNegative;
-    this.plural = plural;
-  }
-
-  /**
-   * Returns true if the pattern represented by this MurkyModifier requires a plural keyword in
-   * order to localize. This is currently true only if there is a currency long name placeholder in
-   * the pattern ("¤¤¤").
-   */
-  public boolean needsPlurals() {
-    return patternInfo.containsSymbolType(AffixPatternUtils.TYPE_CURRENCY_TRIPLE);
-  }
-
-  /**
-   * Creates a new quantity-dependent Modifier that behaves the same as the current instance, but
-   * which is immutable and can be saved for future use. The number properties in the current instance
-   * are mutated; all other properties are left untouched.
-   *
-   * <p>The resulting modifier cannot be used in a QuantityChain.
-   *
-   * @return An immutable that supports both positive and negative numbers.
-   */
-  public ImmutableMurkyModifier createImmutable() {
-    return createImmutableAndChain(null);
-  }
-
-  /**
-   * Creates a new quantity-dependent Modifier that behaves the same as the current instance, but
-   * which is immutable and can be saved for future use. The number properties in the current instance
-   * are mutated; all other properties are left untouched.
-   *
-   * @param parent The QuantityChain to which to chain this immutable.
-   * @return An immutable that supports both positive and negative numbers.
-   */
-  public ImmutableMurkyModifier createImmutableAndChain(QuantityChain parent) {
-    NumberStringBuilder a = new NumberStringBuilder();
-    NumberStringBuilder b = new NumberStringBuilder();
-    if (needsPlurals()) {
-      // Slower path when we require the plural keyword.
-      Modifier[] mods = new Modifier[ImmutableMurkyModifierWithPlurals.getModsLength()];
-      for (StandardPlural plural : StandardPlural.VALUES) {
-        setNumberProperties(false, plural);
-        Modifier positive = createConstantModifier(a, b);
-        setNumberProperties(true, plural);
-        Modifier negative = createConstantModifier(a, b);
-        mods[ImmutableMurkyModifierWithPlurals.getModIndex(false, plural)] = positive;
-        mods[ImmutableMurkyModifierWithPlurals.getModIndex(true, plural)] = negative;
-      }
-      return new ImmutableMurkyModifierWithPlurals(mods, rules, parent);
-    } else {
-      // Faster path when plural keyword is not needed.
-      setNumberProperties(false, null);
-      Modifier positive = createConstantModifier(a, b);
-      setNumberProperties(true, null);
-      Modifier negative = createConstantModifier(a, b);
-      return new ImmutableMurkyModifierWithoutPlurals(positive, negative, parent);
-    }
-  }
-
-  private Modifier createConstantModifier(NumberStringBuilder a, NumberStringBuilder b) {
-    insertPrefix(a.clear(), 0);
-    insertSuffix(b.clear(), 0);
-    if (patternInfo.hasCurrencySign()) {
-      return new CurrencySpacingEnabledModifier(a, b, isStrong, symbols);
-    } else {
-      return new ConstantMultiFieldModifier(a, b, isStrong);
-    }
-  }
-
-  public static interface ImmutableMurkyModifier extends QuantityChain {
-    public void applyToMicros(MicroProps micros, FormatQuantity quantity);
-  }
-
-  public static class ImmutableMurkyModifierWithoutPlurals implements ImmutableMurkyModifier {
-    final Modifier positive;
-    final Modifier negative;
-    final QuantityChain parent;
-
-    public ImmutableMurkyModifierWithoutPlurals(
-        Modifier positive, Modifier negative, QuantityChain parent) {
-      this.positive = positive;
-      this.negative = negative;
-      this.parent = parent;
-    }
-
-    @Override
-    public MicroProps withQuantity(FormatQuantity quantity) {
-      assert parent != null;
-      MicroProps micros = parent.withQuantity(quantity);
-      applyToMicros(micros, quantity);
-      return micros;
-    }
-
-    @Override
-    public void applyToMicros(MicroProps micros, FormatQuantity quantity) {
-      if (quantity.isNegative()) {
-        micros.modMiddle = negative;
-      } else {
-        micros.modMiddle = positive;
-      }
-    }
-  }
-
-  public static class ImmutableMurkyModifierWithPlurals implements ImmutableMurkyModifier {
-    final Modifier[] mods;
-    final PluralRules rules;
-    final QuantityChain parent;
-
-    public ImmutableMurkyModifierWithPlurals(
-        Modifier[] mods, PluralRules rules, QuantityChain parent) {
-      assert mods.length == getModsLength();
-      assert rules != null;
-      this.mods = mods;
-      this.rules = rules;
-      this.parent = parent;
-    }
-
-    public static int getModsLength() {
-      return 2 * StandardPlural.COUNT;
-    }
-
-    public static int getModIndex(boolean isNegative, StandardPlural plural) {
-      return plural.ordinal() * 2 + (isNegative ? 1 : 0);
-    }
-
-    @Override
-    public MicroProps withQuantity(FormatQuantity quantity) {
-      assert parent != null;
-      MicroProps micros = parent.withQuantity(quantity);
-      applyToMicros(micros, quantity);
-      return micros;
-    }
-
-    @Override
-    public void applyToMicros(MicroProps micros, FormatQuantity quantity) {
-      // TODO: Fix this. Avoid the copy.
-      FormatQuantity copy = quantity.createCopy();
-      copy.roundToInfinity();
-      StandardPlural plural = copy.getStandardPlural(rules);
-      Modifier mod = mods[getModIndex(quantity.isNegative(), plural)];
-      micros.modMiddle = mod;
-    }
-  }
-
-  public QuantityChain addToChain(QuantityChain parent) {
-    this.parent = parent;
-    return this;
-  }
-
-  @Override
-  public MicroProps withQuantity(FormatQuantity fq) {
-    MicroProps micros = parent.withQuantity(fq);
-    if (needsPlurals()) {
-      // TODO: Fix this. Avoid the copy.
-      FormatQuantity copy = fq.createCopy();
-      micros.rounding.apply(copy);
-      setNumberProperties(fq.isNegative(), copy.getStandardPlural(rules));
-    } else {
-      setNumberProperties(fq.isNegative(), null);
-    }
-    micros.modMiddle = this;
-    return micros;
-  }
-
-  @Override
-  public int apply(NumberStringBuilder output, int leftIndex, int rightIndex) {
-    int prefixLen = insertPrefix(output, leftIndex);
-    int suffixLen = insertSuffix(output, rightIndex + prefixLen);
-    CurrencySpacingEnabledModifier.applyCurrencySpacing(
-        output, leftIndex, prefixLen, rightIndex + prefixLen, suffixLen, symbols);
-    return prefixLen + suffixLen;
-  }
-
-  @Override
-  public boolean isStrong() {
-    return isStrong;
-  }
-
-  @Override
-  public String getPrefix() {
-    NumberStringBuilder sb = new NumberStringBuilder(10);
-    insertPrefix(sb, 0);
-    return sb.toString();
-  }
-
-  @Override
-  public String getSuffix() {
-    NumberStringBuilder sb = new NumberStringBuilder(10);
-    insertSuffix(sb, 0);
-    return sb.toString();
-  }
-
-  private int insertPrefix(NumberStringBuilder sb, int position) {
-    enterCharSequenceMode(true);
-    int length = AffixPatternUtils.unescape(this, sb, position, this);
-    exitCharSequenceMode();
-    return length;
-  }
-
-  private int insertSuffix(NumberStringBuilder sb, int position) {
-    enterCharSequenceMode(false);
-    int length = AffixPatternUtils.unescape(this, sb, position, this);
-    exitCharSequenceMode();
-    return length;
-  }
-
-  @Override
-  public CharSequence getSymbol(int type) {
-    switch (type) {
-      case AffixPatternUtils.TYPE_MINUS_SIGN:
-        return symbols.getMinusSignString();
-      case AffixPatternUtils.TYPE_PLUS_SIGN:
-        return symbols.getPlusSignString();
-      case AffixPatternUtils.TYPE_PERCENT:
-        return symbols.getPercentString();
-      case AffixPatternUtils.TYPE_PERMILLE:
-        return symbols.getPerMillString();
-      case AffixPatternUtils.TYPE_CURRENCY_SINGLE:
-        // FormatWidth ISO overrides the singular currency symbol
-        if (unitWidth == FormatWidth.SHORT) {
-          return currency2;
-        } else {
-          return currency1;
-        }
-      case AffixPatternUtils.TYPE_CURRENCY_DOUBLE:
-        return currency2;
-      case AffixPatternUtils.TYPE_CURRENCY_TRIPLE:
-        // NOTE: This is the code path only for patterns containing "".
-        // Most plural currencies are formatted in DataUtils.
-        assert plural != null;
-        if (currency3 == null) {
-          return currency2;
-        } else {
-          return currency3[plural.ordinal()];
-        }
-      case AffixPatternUtils.TYPE_CURRENCY_QUAD:
-        return "\uFFFD";
-      case AffixPatternUtils.TYPE_CURRENCY_QUINT:
-        return "\uFFFD";
-      default:
-        throw new AssertionError();
-    }
-  }
-
-  /** This method contains the heart of the logic for rendering LDML affix strings. */
-  private void enterCharSequenceMode(boolean isPrefix) {
-    assert !inCharSequenceMode;
-    inCharSequenceMode = true;
-
-    // Should the output render '+' where '-' would normally appear in the pattern?
-    plusReplacesMinusSign =
-        !isNegative
-            && signDisplay == SignDisplay.ALWAYS
-            && patternInfo.positiveHasPlusSign() == false;
-
-    // Should we use the negative affix pattern? (If not, we will use the positive one)
-    boolean useNegativeAffixPattern =
-        patternInfo.hasNegativeSubpattern()
-            && (isNegative || (patternInfo.negativeHasMinusSign() && plusReplacesMinusSign));
-
-    // Resolve the flags for the affix pattern.
-    flags = 0;
-    if (useNegativeAffixPattern) {
-      flags |= AffixPatternProvider.Flags.NEGATIVE_SUBPATTERN;
-    }
-    if (isPrefix) {
-      flags |= AffixPatternProvider.Flags.PREFIX;
-    }
-    if (plural != null) {
-      assert plural.ordinal() == (AffixPatternProvider.Flags.PLURAL_MASK & plural.ordinal());
-      flags |= plural.ordinal();
-    }
-
-    // Should we prepend a sign to the pattern?
-    if (!isPrefix || useNegativeAffixPattern) {
-      prependSign = false;
-    } else if (isNegative) {
-      prependSign = signDisplay != SignDisplay.NEVER;
-    } else {
-      prependSign = plusReplacesMinusSign;
-    }
-
-    // Finally, compute the length of the affix pattern.
-    length = patternInfo.length(flags) + (prependSign ? 1 : 0);
-  }
-
-  private void exitCharSequenceMode() {
-    assert inCharSequenceMode;
-    inCharSequenceMode = false;
-  }
-
-  @Override
-  public int length() {
-    if (inCharSequenceMode) {
-      return length;
-    } else {
-      NumberStringBuilder sb = new NumberStringBuilder(20);
-      apply(sb, 0, 0);
-      return sb.length();
-    }
-  }
-
-  @Override
-  public char charAt(int index) {
-    assert inCharSequenceMode;
-    char candidate;
-    if (prependSign && index == 0) {
-      candidate = '-';
-    } else if (prependSign) {
-      candidate = patternInfo.charAt(flags, index - 1);
-    } else {
-      candidate = patternInfo.charAt(flags, index);
-    }
-    if (plusReplacesMinusSign && candidate == '-') {
-      return '+';
-    }
-    if (perMilleReplacesPercent && candidate == '%') {
-      return '‰';
-    }
-    return candidate;
-  }
-
-  @Override
-  public CharSequence subSequence(int start, int end) {
-    // Should never be called in normal circumstances
-    throw new AssertionError();
-  }
-}
diff --git a/icu4j/main/classes/core/src/newapi/impl/NotationImpl.java b/icu4j/main/classes/core/src/newapi/impl/NotationImpl.java
deleted file mode 100644 (file)
index 67ddc09..0000000
+++ /dev/null
@@ -1,87 +0,0 @@
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-package newapi.impl;
-
-import java.util.Map;
-
-import com.ibm.icu.text.CompactDecimalFormat.CompactStyle;
-
-import newapi.NumberFormatter.NotationCompact;
-import newapi.NumberFormatter.NotationScientific;
-import newapi.NumberFormatter.Rounding;
-import newapi.NumberFormatter.SignDisplay;
-
-@SuppressWarnings("deprecation")
-public class NotationImpl {
-
-  public static class NotationScientificImpl extends NotationScientific.Internal
-      implements Cloneable {
-
-    int engineeringInterval;
-    boolean requireMinInt;
-    int minExponentDigits;
-    SignDisplay exponentSignDisplay;
-
-    public NotationScientificImpl(int engineeringInterval) {
-      this.engineeringInterval = engineeringInterval;
-      requireMinInt = false;
-      minExponentDigits = 1;
-      exponentSignDisplay = SignDisplay.AUTO;
-    }
-
-    public NotationScientificImpl(
-        int engineeringInterval,
-        boolean requireMinInt,
-        int minExponentDigits,
-        SignDisplay exponentSignDisplay) {
-      this.engineeringInterval = engineeringInterval;
-      this.requireMinInt = requireMinInt;
-      this.minExponentDigits = minExponentDigits;
-      this.exponentSignDisplay = exponentSignDisplay;
-    }
-
-    @Override
-    public NotationScientific withMinExponentDigits(int minExponentDigits) {
-      if (minExponentDigits >= 0 && minExponentDigits < Rounding.MAX_VALUE) {
-        NotationScientificImpl other = (NotationScientificImpl) this.clone();
-        other.minExponentDigits = minExponentDigits;
-        return other;
-      } else {
-        throw new IllegalArgumentException(
-            "Integer digits must be between 0 and " + Rounding.MAX_VALUE);
-      }
-    }
-
-    @Override
-    public NotationScientific withExponentSignDisplay(SignDisplay exponentSignDisplay) {
-      NotationScientificImpl other = (NotationScientificImpl) this.clone();
-      other.exponentSignDisplay = exponentSignDisplay;
-      return other;
-    }
-
-    @Override
-    public Object clone() {
-      try {
-        return super.clone();
-      } catch (CloneNotSupportedException e) {
-        // Should not happen since parent is Object
-        throw new AssertionError(e);
-      }
-    }
-  }
-
-  public static class NotationCompactImpl extends NotationCompact.Internal {
-    final CompactStyle compactStyle;
-    final Map<String, Map<String, String>> compactCustomData;
-
-    public NotationCompactImpl(CompactStyle compactStyle) {
-      compactCustomData = null;
-      this.compactStyle = compactStyle;
-    }
-
-    public NotationCompactImpl(Map<String, Map<String, String>> compactCustomData) {
-      compactStyle = null;
-      this.compactCustomData = compactCustomData;
-    }
-  }
-}
diff --git a/icu4j/main/classes/core/src/newapi/impl/NumberFormatterImpl.java b/icu4j/main/classes/core/src/newapi/impl/NumberFormatterImpl.java
deleted file mode 100644 (file)
index f772ed2..0000000
+++ /dev/null
@@ -1,330 +0,0 @@
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-package newapi.impl;
-
-import java.util.Locale;
-import java.util.Objects;
-import java.util.concurrent.atomic.AtomicLongFieldUpdater;
-
-import com.ibm.icu.impl.number.FormatQuantity;
-import com.ibm.icu.impl.number.FormatQuantity4;
-import com.ibm.icu.impl.number.NumberStringBuilder;
-import com.ibm.icu.impl.number.PatternString;
-import com.ibm.icu.impl.number.Properties;
-import com.ibm.icu.text.DecimalFormatSymbols;
-import com.ibm.icu.text.MeasureFormat.FormatWidth;
-import com.ibm.icu.text.NumberingSystem;
-import com.ibm.icu.util.Measure;
-import com.ibm.icu.util.MeasureUnit;
-import com.ibm.icu.util.ULocale;
-
-import newapi.NumberFormatter;
-import newapi.NumberFormatter.DecimalMarkDisplay;
-import newapi.NumberFormatter.IGrouping;
-import newapi.NumberFormatter.IRounding;
-import newapi.NumberFormatter.IntegerWidth;
-import newapi.NumberFormatter.Notation;
-import newapi.NumberFormatter.NumberFormatterResult;
-import newapi.NumberFormatter.Padding;
-import newapi.NumberFormatter.SignDisplay;
-import newapi.NumberFormatter.UnlocalizedNumberFormatter;
-
-/** @author sffc */
-public class NumberFormatterImpl extends NumberFormatter.LocalizedNumberFormatter.Internal {
-
-  private static final NumberFormatterImpl BASE = new NumberFormatterImpl();
-
-  static final int KEY_MACROS = 0;
-  static final int KEY_LOCALE = 1;
-  static final int KEY_NOTATION = 2;
-  static final int KEY_UNIT = 3;
-  static final int KEY_ROUNDING = 4;
-  static final int KEY_GROUPING = 5;
-  static final int KEY_PADDING = 6;
-  static final int KEY_INTEGER = 7;
-  static final int KEY_SYMBOLS = 8;
-  static final int KEY_UNIT_WIDTH = 9;
-  static final int KEY_SIGN = 10;
-  static final int KEY_DECIMAL = 11;
-  static final int KEY_THRESHOLD = 12;
-  static final int KEY_MAX = 13;
-
-  public static NumberFormatterImpl with() {
-    return BASE;
-  }
-
-  /** Internal method to set a starting macros. */
-  public static NumberFormatterImpl fromMacros(MacroProps macros) {
-    return new NumberFormatterImpl(BASE, KEY_MACROS, macros);
-  }
-
-  /**
-   * Internal method to construct a chain from a pattern using {@link NumberPropertyMapper}. Could
-   * be added to the public API if the feature is requested. In that case, a more efficient
-   * implementation may be desired.
-   */
-  public static UnlocalizedNumberFormatter fromPattern(
-      String string, DecimalFormatSymbols symbols) {
-    Properties props = PatternString.parseToProperties(string);
-    MacroProps macros = NumberPropertyMapper.oldToNew(props, symbols, null);
-    return fromMacros(macros);
-  }
-
-  static final AtomicLongFieldUpdater<NumberFormatterImpl> callCount =
-      AtomicLongFieldUpdater.newUpdater(NumberFormatterImpl.class, "callCountInternal");
-
-  // TODO: Reduce the number of fields.
-  final NumberFormatterImpl parent;
-  final int key;
-  final Object value;
-  volatile MacroProps resolvedMacros;
-  volatile long callCountInternal; // do not access directly; use callCount instead
-  volatile NumberFormatterImpl savedWithUnit;
-  volatile Worker1 compiled;
-
-  /** Base constructor; called during startup only. Sets the threshold to the default value of 3. */
-  private NumberFormatterImpl() {
-    parent = null;
-    key = KEY_THRESHOLD;
-    value = new Long(3);
-  }
-
-  /** Primary constructor */
-  private NumberFormatterImpl(NumberFormatterImpl parent, int key, Object value) {
-    this.parent = parent;
-    this.key = key;
-    this.value = value;
-  }
-
-  @Override
-  public NumberFormatterImpl notation(Notation notation) {
-    return new NumberFormatterImpl(this, KEY_NOTATION, notation);
-  }
-
-  @Override
-  public NumberFormatterImpl unit(MeasureUnit unit) {
-    return new NumberFormatterImpl(this, KEY_UNIT, unit);
-  }
-
-  @Override
-  public NumberFormatterImpl rounding(IRounding rounding) {
-    return new NumberFormatterImpl(this, KEY_ROUNDING, rounding);
-  }
-
-  @Override
-  public NumberFormatterImpl grouping(IGrouping grouping) {
-    return new NumberFormatterImpl(this, KEY_GROUPING, grouping);
-  }
-
-  @Override
-  public NumberFormatterImpl padding(Padding padding) {
-    return new NumberFormatterImpl(this, KEY_PADDING, padding);
-  }
-
-  @Override
-  public NumberFormatterImpl integerWidth(IntegerWidth style) {
-    return new NumberFormatterImpl(this, KEY_INTEGER, style);
-  }
-
-  @Override
-  public NumberFormatterImpl symbols(DecimalFormatSymbols symbols) {
-    return new NumberFormatterImpl(this, KEY_SYMBOLS, symbols);
-  }
-
-  @Override
-  public NumberFormatterImpl symbols(NumberingSystem ns) {
-    return new NumberFormatterImpl(this, KEY_SYMBOLS, ns);
-  }
-
-  @Override
-  public NumberFormatterImpl unitWidth(FormatWidth style) {
-    return new NumberFormatterImpl(this, KEY_UNIT_WIDTH, style);
-  }
-
-  @Override
-  public NumberFormatterImpl sign(SignDisplay style) {
-    return new NumberFormatterImpl(this, KEY_SIGN, style);
-  }
-
-  @Override
-  public NumberFormatterImpl decimal(DecimalMarkDisplay style) {
-    return new NumberFormatterImpl(this, KEY_DECIMAL, style);
-  }
-
-  @Override
-  public NumberFormatterImpl locale(Locale locale) {
-    return new NumberFormatterImpl(this, KEY_LOCALE, ULocale.forLocale(locale));
-  }
-
-  @Override
-  public NumberFormatterImpl locale(ULocale locale) {
-    return new NumberFormatterImpl(this, KEY_LOCALE, locale);
-  }
-
-  /**
-   * Internal fluent setter to support a custom regulation threshold. A threshold of 1 causes the
-   * data structures to be built right away. A threshold of 0 prevents the data structures from
-   * being built.
-   */
-  public NumberFormatterImpl threshold(Long threshold) {
-    return new NumberFormatterImpl(this, KEY_THRESHOLD, threshold);
-  }
-
-  @Override
-  public String toSkeleton() {
-    return SkeletonBuilder.macrosToSkeleton(resolve());
-  }
-
-  @Override
-  public NumberFormatterResult format(long input) {
-    return format(new FormatQuantity4(input));
-  }
-
-  @Override
-  public NumberFormatterResult format(double input) {
-    return format(new FormatQuantity4(input));
-  }
-
-  @Override
-  public NumberFormatterResult format(Number input) {
-    return format(new FormatQuantity4(input));
-  }
-
-  @Override
-  public NumberFormatterResult format(Measure input) {
-    MeasureUnit unit = input.getUnit();
-    Number number = input.getNumber();
-    // Use this formatter if possible
-    if (Objects.equals(resolve().unit, unit)) {
-      return format(number);
-    }
-    // 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.
-    NumberFormatterImpl withUnit = savedWithUnit;
-    if (withUnit == null || !Objects.equals(withUnit.resolve().unit, unit)) {
-      withUnit = new NumberFormatterImpl(this, KEY_UNIT, unit);
-      savedWithUnit = withUnit;
-    }
-    return withUnit.format(number);
-  }
-
-  /**
-   * This is the core entrypoint to the number formatting pipeline. It performs self-regulation: a
-   * static code path for the first few calls, and compiling a more efficient data structure if
-   * called repeatedly.
-   *
-   * @param fq The quantity to be formatted.
-   * @return The formatted number result.
-   */
-  public NumberFormatterResult format(FormatQuantity fq) {
-    MacroProps macros = resolve();
-    NumberStringBuilder string = new NumberStringBuilder();
-    long currentCount = callCount.incrementAndGet(this);
-    MicroProps micros;
-    if (currentCount == macros.threshold.longValue()) {
-      compiled = Worker1.fromMacros(macros);
-      micros = compiled.apply(fq, string);
-    } else if (compiled != null) {
-      micros = compiled.apply(fq, string);
-    } else {
-      micros = Worker1.applyStatic(macros, fq, string);
-    }
-    return new NumberFormatterResult(string, fq, micros);
-  }
-
-  @Override
-  public int hashCode() {
-    return resolve().hashCode();
-  }
-
-  @Override
-  public boolean equals(Object other) {
-    if (this == other) return true;
-    if (other == null) return false;
-    if (!(other instanceof NumberFormatterImpl)) return false;
-    return resolve().equals(((NumberFormatterImpl) other).resolve());
-  }
-
-  private MacroProps resolve() {
-    if (resolvedMacros != null) {
-      return resolvedMacros;
-    }
-    // Although the linked-list fluent storage approach requires this method,
-    // my benchmarks show that linked-list is still faster than a full clone
-    // of a MacroProps object at each step.
-    MacroProps macros = new MacroProps();
-    NumberFormatterImpl current = this;
-    while (current != null) {
-      switch (current.key) {
-        case KEY_MACROS:
-          macros.fallback((MacroProps) current.value);
-          break;
-        case KEY_LOCALE:
-          if (macros.loc == null) {
-            macros.loc = (ULocale) current.value;
-          }
-          break;
-        case KEY_NOTATION:
-          if (macros.notation == null) {
-            macros.notation = (Notation) current.value;
-          }
-          break;
-        case KEY_UNIT:
-          if (macros.unit == null) {
-            macros.unit = (MeasureUnit) current.value;
-          }
-          break;
-        case KEY_ROUNDING:
-          if (macros.rounding == null) {
-            macros.rounding = (IRounding) current.value;
-          }
-          break;
-        case KEY_GROUPING:
-          if (macros.grouping == null) {
-            macros.grouping = (IGrouping) current.value;
-          }
-          break;
-        case KEY_PADDING:
-          if (macros.padding == null) {
-            macros.padding = (Padding) current.value;
-          }
-          break;
-        case KEY_INTEGER:
-          if (macros.integerWidth == null) {
-            macros.integerWidth = (IntegerWidth) current.value;
-          }
-          break;
-        case KEY_SYMBOLS:
-          if (macros.symbols == null) {
-            macros.symbols = /*(Object)*/ current.value;
-          }
-          break;
-        case KEY_UNIT_WIDTH:
-          if (macros.unitWidth == null) {
-            macros.unitWidth = (FormatWidth) current.value;
-          }
-          break;
-        case KEY_SIGN:
-          if (macros.sign == null) {
-            macros.sign = (SignDisplay) current.value;
-          }
-          break;
-        case KEY_DECIMAL:
-          if (macros.decimal == null) {
-            macros.decimal = (DecimalMarkDisplay) current.value;
-          }
-          break;
-        case KEY_THRESHOLD:
-          if (macros.threshold == null) {
-            macros.threshold = (Long) current.value;
-          }
-          break;
-        default:
-          throw new AssertionError("Unknown key: " + current.key);
-      }
-      current = current.parent;
-    }
-    resolvedMacros = macros;
-    return macros;
-  }
-}
diff --git a/icu4j/main/classes/core/src/newapi/impl/NumberPropertyMapper.java b/icu4j/main/classes/core/src/newapi/impl/NumberPropertyMapper.java
deleted file mode 100644 (file)
index 81cf952..0000000
+++ /dev/null
@@ -1,453 +0,0 @@
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-package newapi.impl;
-
-import java.math.BigDecimal;
-import java.math.MathContext;
-
-import com.ibm.icu.impl.StandardPlural;
-import com.ibm.icu.impl.number.AffixPatternUtils;
-import com.ibm.icu.impl.number.LdmlPatternInfo;
-import com.ibm.icu.impl.number.LdmlPatternInfo.PatternParseResult;
-import com.ibm.icu.impl.number.Properties;
-import com.ibm.icu.impl.number.RoundingUtils;
-import com.ibm.icu.text.CompactDecimalFormat.CompactStyle;
-import com.ibm.icu.text.CurrencyPluralInfo;
-import com.ibm.icu.text.DecimalFormatSymbols;
-import com.ibm.icu.util.Currency;
-import com.ibm.icu.util.Currency.CurrencyUsage;
-import com.ibm.icu.util.ULocale;
-
-import newapi.NumberFormatter.CurrencyRounding;
-import newapi.NumberFormatter.DecimalMarkDisplay;
-import newapi.NumberFormatter.IntegerWidth;
-import newapi.NumberFormatter.Notation;
-import newapi.NumberFormatter.Rounding;
-import newapi.NumberFormatter.SignDisplay;
-import newapi.impl.NotationImpl.NotationScientificImpl;
-import newapi.impl.RoundingImpl.RoundingImplCurrency;
-import newapi.impl.RoundingImpl.RoundingImplFraction;
-import newapi.impl.RoundingImpl.RoundingImplIncrement;
-import newapi.impl.RoundingImpl.RoundingImplSignificant;
-
-/** @author sffc */
-public final class NumberPropertyMapper {
-
-  /** Convenience method to create a NumberFormatterImpl directly. */
-  public static NumberFormatterImpl create(
-      Properties properties, DecimalFormatSymbols symbols, ULocale uloc) {
-    MacroProps macros = oldToNew(properties, symbols, null);
-    return NumberFormatterImpl.fromMacros(macros).locale(uloc);
-  }
-
-  /**
-   * Creates a new {@link MacroProps} object based on the content of a {@link Properties} object. In
-   * other words, maps Properties to MacroProps. This function is used by the JDK-compatibility API
-   * to call into the ICU 60 fluent number formatting pipeline.
-   *
-   * @param properties The property bag to be mapped.
-   * @param symbols The symbols associated with the property bag.
-   * @param exportedProperties A property bag in which to store validated properties.
-   * @return A new MacroProps containing all of the information in the Properties.
-   */
-  public static MacroProps oldToNew(
-      Properties properties, DecimalFormatSymbols symbols, Properties exportedProperties) {
-    MacroProps macros = new MacroProps();
-    ULocale locale = symbols.getULocale();
-
-    /////////////
-    // SYMBOLS //
-    /////////////
-
-    macros.symbols = symbols;
-
-    //////////////////
-    // PLURAL RULES //
-    //////////////////
-
-    macros.rules = properties.getPluralRules();
-
-    /////////////
-    // AFFIXES //
-    /////////////
-
-    AffixPatternProvider affixProvider;
-    if (properties.getCurrencyPluralInfo() == null) {
-      affixProvider =
-          new PropertiesAffixPatternProvider(
-              properties.getPositivePrefix() != null
-                  ? AffixPatternUtils.escape(properties.getPositivePrefix())
-                  : properties.getPositivePrefixPattern(),
-              properties.getPositiveSuffix() != null
-                  ? AffixPatternUtils.escape(properties.getPositiveSuffix())
-                  : properties.getPositiveSuffixPattern(),
-              properties.getNegativePrefix() != null
-                  ? AffixPatternUtils.escape(properties.getNegativePrefix())
-                  : properties.getNegativePrefixPattern(),
-              properties.getNegativeSuffix() != null
-                  ? AffixPatternUtils.escape(properties.getNegativeSuffix())
-                  : properties.getNegativeSuffixPattern());
-    } else {
-      affixProvider = new CurrencyPluralInfoAffixProvider(properties.getCurrencyPluralInfo());
-    }
-    macros.affixProvider = affixProvider;
-
-    ///////////
-    // UNITS //
-    ///////////
-
-    boolean useCurrency =
-        ((properties.getCurrency() != null)
-            || properties.getCurrencyPluralInfo() != null
-            || properties.getCurrencyUsage() != null
-            || affixProvider.hasCurrencySign());
-    Currency currency = CustomSymbolCurrency.resolve(properties.getCurrency(), locale, symbols);
-    CurrencyUsage currencyUsage = properties.getCurrencyUsage();
-    boolean explicitCurrencyUsage = currencyUsage != null;
-    if (!explicitCurrencyUsage) {
-      currencyUsage = CurrencyUsage.STANDARD;
-    }
-    if (useCurrency) {
-      macros.unit = currency;
-    }
-
-    ///////////////////////
-    // ROUNDING STRATEGY //
-    ///////////////////////
-
-    int maxInt = properties.getMaximumIntegerDigits();
-    int minInt = properties.getMinimumIntegerDigits();
-    int maxFrac = properties.getMaximumFractionDigits();
-    int minFrac = properties.getMinimumFractionDigits();
-    int minSig = properties.getMinimumSignificantDigits();
-    int maxSig = properties.getMaximumSignificantDigits();
-    BigDecimal roundingIncrement = properties.getRoundingIncrement();
-    MathContext mathContext = RoundingUtils.getMathContextOrUnlimited(properties);
-    boolean explicitMinMaxFrac = minFrac != -1 || maxFrac != -1;
-    boolean explicitMinMaxSig = minSig != -1 || maxSig != -1;
-    // Validate min/max int/frac.
-    // For backwards compatibility, minimum overrides maximum if the two conflict.
-    // The following logic ensures that there is always a minimum of at least one digit.
-    if (minInt == 0 && maxFrac != 0) {
-      // Force a digit after the decimal point.
-      minFrac = minFrac <= 0 ? 1 : minFrac;
-      maxFrac = maxFrac < 0 ? Integer.MAX_VALUE : maxFrac < minFrac ? minFrac : maxFrac;
-      minInt = 0;
-      maxInt = maxInt < 0 ? Integer.MAX_VALUE : maxInt;
-    } else {
-      // Force a digit before the decimal point.
-      minFrac = minFrac < 0 ? 0 : minFrac;
-      maxFrac = maxFrac < 0 ? Integer.MAX_VALUE : maxFrac < minFrac ? minFrac : maxFrac;
-      minInt = minInt <= 0 ? 1 : minInt;
-      maxInt = maxInt < 0 ? Integer.MAX_VALUE : maxInt < minInt ? minInt : maxInt;
-    }
-    Rounding rounding = null;
-    if (explicitCurrencyUsage) {
-      rounding = RoundingImplCurrency.getInstance(currencyUsage).withCurrency(currency);
-    } else if (roundingIncrement != null) {
-      rounding = RoundingImplIncrement.getInstance(roundingIncrement);
-    } else if (explicitMinMaxSig) {
-      minSig = minSig < 1 ? 1 : minSig > 1000 ? 1000 : minSig;
-      maxSig = maxSig < 0 ? 1000 : maxSig < minSig ? minSig : maxSig > 1000 ? 1000 : maxSig;
-      rounding = RoundingImplSignificant.getInstance(minSig, maxSig);
-    } else if (explicitMinMaxFrac) {
-      rounding = RoundingImplFraction.getInstance(minFrac, maxFrac);
-    } else if (useCurrency) {
-      rounding = RoundingImplCurrency.getInstance(currencyUsage);
-    }
-    if (rounding != null) {
-      rounding = rounding.withMode(mathContext);
-      macros.rounding = rounding;
-    }
-
-    ///////////////////
-    // INTEGER WIDTH //
-    ///////////////////
-
-    macros.integerWidth = IntegerWidth.zeroFillTo(minInt).truncateAt(maxInt);
-
-    ///////////////////////
-    // GROUPING STRATEGY //
-    ///////////////////////
-
-    int grouping1 = properties.getGroupingSize();
-    int grouping2 = properties.getSecondaryGroupingSize();
-    int minGrouping = properties.getMinimumGroupingDigits();
-    assert grouping1 >= -2; // value of -2 means to forward no grouping information
-    grouping1 = grouping1 > 0 ? grouping1 : grouping2 > 0 ? grouping2 : grouping1;
-    grouping2 = grouping2 > 0 ? grouping2 : grouping1;
-    // TODO: Is it important to handle minGrouping > 2?
-    macros.grouping =
-        GroupingImpl.getInstance((byte) grouping1, (byte) grouping2, minGrouping == 2);
-
-    /////////////
-    // PADDING //
-    /////////////
-
-    if (properties.getFormatWidth() != -1) {
-      macros.padding =
-          PaddingImpl.getInstance(
-              properties.getPadString(), properties.getFormatWidth(), properties.getPadPosition());
-    }
-
-    ///////////////////////////////
-    // DECIMAL MARK ALWAYS SHOWN //
-    ///////////////////////////////
-
-    macros.decimal =
-        properties.getDecimalSeparatorAlwaysShown()
-            ? DecimalMarkDisplay.ALWAYS
-            : DecimalMarkDisplay.AUTO;
-
-    ///////////////////////
-    // SIGN ALWAYS SHOWN //
-    ///////////////////////
-
-    macros.sign = properties.getSignAlwaysShown() ? SignDisplay.ALWAYS : SignDisplay.AUTO;
-
-    /////////////////////////
-    // SCIENTIFIC NOTATION //
-    /////////////////////////
-
-    if (properties.getMinimumExponentDigits() != -1) {
-      // Scientific notation is required.
-      // The mapping from property bag to scientific notation is nontrivial due to LDML rules.
-      // The maximum of 8 engineering digits has unknown origins and is not in the spec.
-      int engineering =
-          (maxInt != Integer.MAX_VALUE) ? maxInt : properties.getMaximumIntegerDigits();
-      engineering = (engineering < 0) ? 0 : (engineering > 8) ? minInt : engineering;
-      // Bug #13289: if maxInt > minInt > 1, then minInt should be 1.
-      // Clear out IntegerWidth to prevent padding extra zeros.
-      if (maxInt > minInt && minInt > 1) {
-        macros.integerWidth = null;
-      }
-      macros.notation =
-          new NotationScientificImpl(
-              // Engineering interval:
-              engineering,
-              // Enforce minimum integer digits (for patterns like "000.00E0"):
-              (engineering == minInt),
-              // Minimum exponent digits:
-              properties.getMinimumExponentDigits(),
-              // Exponent sign always shown:
-              properties.getExponentSignAlwaysShown() ? SignDisplay.ALWAYS : SignDisplay.AUTO);
-      // Scientific notation also involves overriding the rounding mode.
-      if (macros.rounding instanceof RoundingImplFraction) {
-        int minInt_ = properties.getMinimumIntegerDigits();
-        int minFrac_ = properties.getMinimumFractionDigits();
-        int maxFrac_ = properties.getMaximumFractionDigits();
-        if (minInt_ == 0 && maxFrac_ == 0) {
-          // Patterns like "#E0" and "##E0", which mean no rounding!
-          macros.rounding = Rounding.NONE.withMode(mathContext);
-        } else if (minInt_ == 0 && minFrac_ == 0) {
-          // Patterns like "#.##E0" (no zeros in the mantissa), which mean round to maxFrac+1
-          macros.rounding = new RoundingImplSignificant(1, maxFrac_ + 1).withMode(mathContext);
-        } else {
-          // All other scientific patterns, which mean round to minInt+maxFrac
-          macros.rounding =
-              new RoundingImplSignificant(minInt_ + minFrac_, minInt_ + maxFrac_)
-                  .withMode(mathContext);
-        }
-      }
-    }
-
-    //////////////////////
-    // COMPACT NOTATION //
-    //////////////////////
-
-    if (properties.getCompactStyle() != null) {
-      if (properties.getCompactCustomData() != null) {
-        macros.notation = new NotationImpl.NotationCompactImpl(properties.getCompactCustomData());
-      } else if (properties.getCompactStyle() == CompactStyle.LONG) {
-        macros.notation = Notation.COMPACT_LONG;
-      } else {
-        macros.notation = Notation.COMPACT_SHORT;
-      }
-      // Do not forward the affix provider.
-      macros.affixProvider = null;
-    }
-
-    /////////////////
-    // MULTIPLIERS //
-    /////////////////
-
-    if (properties.getMagnitudeMultiplier() != 0) {
-      macros.multiplier = new MultiplierImpl(properties.getMagnitudeMultiplier());
-    } else if (properties.getMultiplier() != null) {
-      macros.multiplier = new MultiplierImpl(properties.getMultiplier());
-    }
-
-    //////////////////////
-    // PROPERTY EXPORTS //
-    //////////////////////
-
-    if (exportedProperties != null) {
-
-      exportedProperties.setMathContext(mathContext);
-      exportedProperties.setRoundingMode(mathContext.getRoundingMode());
-      exportedProperties.setMinimumIntegerDigits(minInt);
-      exportedProperties.setMaximumIntegerDigits(maxInt);
-
-      Rounding rounding_;
-      if (rounding instanceof CurrencyRounding) {
-        rounding_ = ((CurrencyRounding) rounding).withCurrency(currency);
-      } else {
-        rounding_ = rounding;
-      }
-      int minFrac_ = minFrac;
-      int maxFrac_ = maxFrac;
-      int minSig_ = minSig;
-      int maxSig_ = maxSig;
-      BigDecimal increment_ = null;
-      if (rounding_ instanceof RoundingImplFraction) {
-        minFrac_ = ((RoundingImplFraction) rounding_).minFrac;
-        maxFrac_ = ((RoundingImplFraction) rounding_).maxFrac;
-      } else if (rounding_ instanceof RoundingImplIncrement) {
-        increment_ = ((RoundingImplIncrement) rounding_).increment;
-        minFrac_ = increment_.scale();
-        maxFrac_ = increment_.scale();
-      } else if (rounding_ instanceof RoundingImplSignificant) {
-        minSig_ = ((RoundingImplSignificant) rounding_).minSig;
-        maxSig_ = ((RoundingImplSignificant) rounding_).maxSig;
-      }
-
-      exportedProperties.setMinimumFractionDigits(minFrac_);
-      exportedProperties.setMaximumFractionDigits(maxFrac_);
-      exportedProperties.setMinimumSignificantDigits(minSig_);
-      exportedProperties.setMaximumSignificantDigits(maxSig_);
-      exportedProperties.setRoundingIncrement(increment_);
-    }
-
-    return macros;
-  }
-
-  private static class PropertiesAffixPatternProvider implements AffixPatternProvider {
-    private final String posPrefixPattern;
-    private final String posSuffixPattern;
-    private final String negPrefixPattern;
-    private final String negSuffixPattern;
-
-    public PropertiesAffixPatternProvider(String ppp, String psp, String npp, String nsp) {
-      if (ppp == null) ppp = "";
-      if (psp == null) psp = "";
-      if (npp == null && nsp != null) npp = "-"; // TODO: This is a hack.
-      if (nsp == null && npp != null) nsp = "";
-      posPrefixPattern = ppp;
-      posSuffixPattern = psp;
-      negPrefixPattern = npp;
-      negSuffixPattern = nsp;
-    }
-
-    @Override
-    public char charAt(int flags, int i) {
-      boolean prefix = (flags & Flags.PREFIX) != 0;
-      boolean negative = (flags & Flags.NEGATIVE_SUBPATTERN) != 0;
-      if (prefix && negative) {
-        return negPrefixPattern.charAt(i);
-      } else if (prefix) {
-        return posPrefixPattern.charAt(i);
-      } else if (negative) {
-        return negSuffixPattern.charAt(i);
-      } else {
-        return posSuffixPattern.charAt(i);
-      }
-    }
-
-    @Override
-    public int length(int flags) {
-      boolean prefix = (flags & Flags.PREFIX) != 0;
-      boolean negative = (flags & Flags.NEGATIVE_SUBPATTERN) != 0;
-      if (prefix && negative) {
-        return negPrefixPattern.length();
-      } else if (prefix) {
-        return posPrefixPattern.length();
-      } else if (negative) {
-        return negSuffixPattern.length();
-      } else {
-        return posSuffixPattern.length();
-      }
-    }
-
-    @Override
-    public boolean positiveHasPlusSign() {
-      return AffixPatternUtils.containsType(posPrefixPattern, AffixPatternUtils.TYPE_PLUS_SIGN)
-          || AffixPatternUtils.containsType(posSuffixPattern, AffixPatternUtils.TYPE_PLUS_SIGN);
-    }
-
-    @Override
-    public boolean hasNegativeSubpattern() {
-      return negPrefixPattern != null;
-    }
-
-    @Override
-    public boolean negativeHasMinusSign() {
-      return AffixPatternUtils.containsType(negPrefixPattern, AffixPatternUtils.TYPE_MINUS_SIGN)
-          || AffixPatternUtils.containsType(negSuffixPattern, AffixPatternUtils.TYPE_MINUS_SIGN);
-    }
-
-    @Override
-    public boolean hasCurrencySign() {
-      return AffixPatternUtils.hasCurrencySymbols(posPrefixPattern)
-          || AffixPatternUtils.hasCurrencySymbols(posSuffixPattern)
-          || AffixPatternUtils.hasCurrencySymbols(negPrefixPattern)
-          || AffixPatternUtils.hasCurrencySymbols(negSuffixPattern);
-    }
-
-    @Override
-    public boolean containsSymbolType(int type) {
-      return AffixPatternUtils.containsType(posPrefixPattern, type)
-          || AffixPatternUtils.containsType(posSuffixPattern, type)
-          || AffixPatternUtils.containsType(negPrefixPattern, type)
-          || AffixPatternUtils.containsType(negSuffixPattern, type);
-    }
-  }
-
-  private static class CurrencyPluralInfoAffixProvider implements AffixPatternProvider {
-    private final AffixPatternProvider[] affixesByPlural;
-
-    public CurrencyPluralInfoAffixProvider(CurrencyPluralInfo cpi) {
-      affixesByPlural = new PatternParseResult[StandardPlural.COUNT];
-      for (StandardPlural plural : StandardPlural.VALUES) {
-        affixesByPlural[plural.ordinal()] =
-            LdmlPatternInfo.parse(cpi.getCurrencyPluralPattern(plural.getKeyword()));
-      }
-    }
-
-    @Override
-    public char charAt(int flags, int i) {
-      int pluralOrdinal = (flags & Flags.PLURAL_MASK);
-      return affixesByPlural[pluralOrdinal].charAt(flags, i);
-    }
-
-    @Override
-    public int length(int flags) {
-      int pluralOrdinal = (flags & Flags.PLURAL_MASK);
-      return affixesByPlural[pluralOrdinal].length(flags);
-    }
-
-    @Override
-    public boolean positiveHasPlusSign() {
-      return affixesByPlural[StandardPlural.OTHER.ordinal()].positiveHasPlusSign();
-    }
-
-    @Override
-    public boolean hasNegativeSubpattern() {
-      return affixesByPlural[StandardPlural.OTHER.ordinal()].hasNegativeSubpattern();
-    }
-
-    @Override
-    public boolean negativeHasMinusSign() {
-      return affixesByPlural[StandardPlural.OTHER.ordinal()].negativeHasMinusSign();
-    }
-
-    @Override
-    public boolean hasCurrencySign() {
-      return affixesByPlural[StandardPlural.OTHER.ordinal()].hasCurrencySign();
-    }
-
-    @Override
-    public boolean containsSymbolType(int type) {
-      return affixesByPlural[StandardPlural.OTHER.ordinal()].containsSymbolType(type);
-    }
-  }
-}
diff --git a/icu4j/main/classes/core/src/newapi/impl/Padder.java b/icu4j/main/classes/core/src/newapi/impl/Padder.java
new file mode 100644 (file)
index 0000000..faacd50
--- /dev/null
@@ -0,0 +1,107 @@
+// © 2017 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html#License
+package newapi.impl;
+
+import com.ibm.icu.impl.number.NumberStringBuilder;
+import com.ibm.icu.impl.number.ThingsNeedingNewHome.PadPosition;
+
+public class Padder {
+
+    private static final Padder NONE = new Padder(null, -1, null);
+
+    String paddingString;
+    int targetWidth;
+    PadPosition position;
+
+    public Padder(String paddingString, int targetWidth, PadPosition position) {
+        // TODO: Add a few default instances
+        this.paddingString = (paddingString == null) ? " " : paddingString;
+        this.targetWidth = targetWidth;
+        this.position = (position == null) ? PadPosition.BEFORE_PREFIX : position;
+    }
+
+    public static Padder none() {
+        return NONE;
+    }
+
+    public static Padder codePoints(int cp, int targetWidth, PadPosition position) {
+        // TODO: Validate the code point
+        if (targetWidth >= 0) {
+            String paddingString = String.valueOf(Character.toChars(cp));
+            return new Padder(paddingString, targetWidth, position);
+        } else {
+            throw new IllegalArgumentException("Padding width must not be negative");
+        }
+    }
+
+    public int applyModsAndMaybePad(MicroProps micros, NumberStringBuilder string, int leftIndex, int rightIndex) {
+        // Apply modInner (scientific notation) before padding
+        int innerLength = micros.modInner.apply(string, leftIndex, rightIndex);
+
+        // No padding; apply the mods and leave.
+        if (targetWidth < 0) {
+            return applyMicroMods(micros, string, leftIndex, rightIndex + innerLength);
+        }
+
+        // Estimate the padding width needed.
+        // TODO: Make this more efficient (less copying)
+        // TODO: How to handle when padding is inserted between a currency sign and the number
+        // when currency spacing is in play?
+        NumberStringBuilder backup = new NumberStringBuilder(string);
+        int length = innerLength + applyMicroMods(micros, string, leftIndex, rightIndex + innerLength);
+        int requiredPadding = targetWidth - string.codePointCount();
+
+        if (requiredPadding <= 0) {
+            // Padding is not required.
+            return length;
+        }
+
+        length = innerLength;
+        string.copyFrom(backup);
+        if (position == PadPosition.AFTER_PREFIX) {
+            length += addPaddingHelper(paddingString, requiredPadding, string, leftIndex);
+        } else if (position == PadPosition.BEFORE_SUFFIX) {
+            length += addPaddingHelper(paddingString, requiredPadding, string, rightIndex + length);
+        }
+        length += applyMicroMods(micros, string, leftIndex, rightIndex + length);
+        if (position == PadPosition.BEFORE_PREFIX) {
+            length = addPaddingHelper(paddingString, requiredPadding, string, leftIndex);
+        } else if (position == PadPosition.AFTER_SUFFIX) {
+            length = addPaddingHelper(paddingString, requiredPadding, string, rightIndex + length);
+        }
+
+        // The length might not be exactly right due to currency spacing.
+        // Make an adjustment if needed.
+        while (string.codePointCount() < targetWidth) {
+            int insertIndex;
+            switch (position) {
+            case AFTER_PREFIX:
+                insertIndex = leftIndex + length;
+                break;
+            case BEFORE_SUFFIX:
+                insertIndex = rightIndex + length;
+                break;
+            default:
+                // Should not happen since currency spacing is always on the inside.
+                throw new AssertionError();
+            }
+            length += string.insert(insertIndex, paddingString, null);
+        }
+
+        return length;
+    }
+
+    private static int applyMicroMods(MicroProps micros, NumberStringBuilder string, int leftIndex, int rightIndex) {
+        int length = micros.modMiddle.apply(string, leftIndex, rightIndex);
+        length += micros.modOuter.apply(string, leftIndex, rightIndex + length);
+        return length;
+    }
+
+    private static int addPaddingHelper(String paddingString, int requiredPadding, NumberStringBuilder string,
+            int index) {
+        for (int i = 0; i < requiredPadding; i++) {
+            string.insert(index, paddingString, null);
+        }
+        return paddingString.length() * requiredPadding;
+    }
+}
\ No newline at end of file
diff --git a/icu4j/main/classes/core/src/newapi/impl/PaddingImpl.java b/icu4j/main/classes/core/src/newapi/impl/PaddingImpl.java
deleted file mode 100644 (file)
index 6700d87..0000000
+++ /dev/null
@@ -1,109 +0,0 @@
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-package newapi.impl;
-
-import com.ibm.icu.impl.number.NumberStringBuilder;
-import com.ibm.icu.impl.number.ThingsNeedingNewHome.PadPosition;
-
-import newapi.NumberFormatter.Padding;
-
-public class PaddingImpl extends Padding.Internal {
-
-  String paddingString;
-  int targetWidth;
-  PadPosition position;
-
-  public static final PaddingImpl NONE = new PaddingImpl();
-
-  public static PaddingImpl getInstance(
-      String paddingString, int targetWidth, PadPosition position) {
-    // TODO: Add a few default implementations
-    return new PaddingImpl(paddingString, targetWidth, position);
-  }
-
-  /** Default constructor, producing an empty instance */
-  public PaddingImpl() {
-    paddingString = null;
-    targetWidth = -1;
-    position = null;
-  }
-
-  private PaddingImpl(String paddingString, int targetWidth, PadPosition position) {
-    this.paddingString = (paddingString == null) ? " " : paddingString;
-    this.targetWidth = targetWidth;
-    this.position = (position == null) ? PadPosition.BEFORE_PREFIX : position;
-  }
-
-  public int applyModsAndMaybePad(
-      MicroProps micros, NumberStringBuilder string, int leftIndex, int rightIndex) {
-    // Apply modInner (scientific notation) before padding
-    int innerLength = micros.modInner.apply(string, leftIndex, rightIndex);
-
-    // No padding; apply the mods and leave.
-    if (targetWidth < 0) {
-      return applyMicroMods(micros, string, leftIndex, rightIndex + innerLength);
-    }
-
-    // Estimate the padding width needed.
-    // TODO: Make this more efficient (less copying)
-    // TODO: How to handle when padding is inserted between a currency sign and the number
-    // when currency spacing is in play?
-    NumberStringBuilder backup = new NumberStringBuilder(string);
-    int length = innerLength + applyMicroMods(micros, string, leftIndex, rightIndex + innerLength);
-    int requiredPadding = targetWidth - string.codePointCount();
-
-    if (requiredPadding <= 0) {
-      // Padding is not required.
-      return length;
-    }
-
-    length = innerLength;
-    string.copyFrom(backup);
-    if (position == PadPosition.AFTER_PREFIX) {
-      length += addPaddingHelper(paddingString, requiredPadding, string, leftIndex);
-    } else if (position == PadPosition.BEFORE_SUFFIX) {
-      length += addPaddingHelper(paddingString, requiredPadding, string, rightIndex + length);
-    }
-    length += applyMicroMods(micros, string, leftIndex, rightIndex + length);
-    if (position == PadPosition.BEFORE_PREFIX) {
-      length = addPaddingHelper(paddingString, requiredPadding, string, leftIndex);
-    } else if (position == PadPosition.AFTER_SUFFIX) {
-      length = addPaddingHelper(paddingString, requiredPadding, string, rightIndex + length);
-    }
-
-    // The length might not be exactly right due to currency spacing.
-    // Make an adjustment if needed.
-    while (string.codePointCount() < targetWidth) {
-      int insertIndex;
-      switch (position) {
-        case AFTER_PREFIX:
-          insertIndex = leftIndex + length;
-          break;
-        case BEFORE_SUFFIX:
-          insertIndex = rightIndex + length;
-          break;
-        default:
-          // Should not happen since currency spacing is always on the inside.
-          throw new AssertionError();
-      }
-      length += string.insert(insertIndex, paddingString, null);
-    }
-
-    return length;
-  }
-
-  private static int applyMicroMods(
-      MicroProps micros, NumberStringBuilder string, int leftIndex, int rightIndex) {
-    int length = micros.modMiddle.apply(string, leftIndex, rightIndex);
-    length += micros.modOuter.apply(string, leftIndex, rightIndex + length);
-    return length;
-  }
-
-  private static int addPaddingHelper(
-      String paddingString, int requiredPadding, NumberStringBuilder string, int index) {
-    for (int i = 0; i < requiredPadding; i++) {
-      string.insert(index, paddingString, null);
-    }
-    return paddingString.length() * requiredPadding;
-  }
-}
diff --git a/icu4j/main/classes/core/src/newapi/impl/PositiveDecimalImpl.java b/icu4j/main/classes/core/src/newapi/impl/PositiveDecimalImpl.java
deleted file mode 100644 (file)
index 01df495..0000000
+++ /dev/null
@@ -1,95 +0,0 @@
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-package newapi.impl;
-// License & terms of use: http://www.unicode.org/copyright.html#License
-
-import com.ibm.icu.impl.number.FormatQuantity;
-import com.ibm.icu.impl.number.NumberStringBuilder;
-import com.ibm.icu.text.NumberFormat;
-
-import newapi.NumberFormatter.DecimalMarkDisplay;
-
-public class PositiveDecimalImpl {
-
-  public static int apply(MicroProps micros, FormatQuantity input, NumberStringBuilder string) {
-    int length = 0;
-    if (input.isInfinite()) {
-      length += string.insert(length, micros.symbols.getInfinity(), NumberFormat.Field.INTEGER);
-
-    } else if (input.isNaN()) {
-      length += string.insert(length, micros.symbols.getNaN(), NumberFormat.Field.INTEGER);
-
-    } else {
-      // Add the integer digits
-      length += addIntegerDigits(micros, input, string);
-
-      // Add the decimal point
-      if (input.getLowerDisplayMagnitude() < 0
-          || micros.decimal == DecimalMarkDisplay.ALWAYS) {
-        length +=
-            string.insert(
-                length,
-                micros.useCurrency
-                    ? micros.symbols.getMonetaryDecimalSeparatorString()
-                    : micros.symbols.getDecimalSeparatorString(),
-                NumberFormat.Field.DECIMAL_SEPARATOR);
-      }
-
-      // Add the fraction digits
-      length += addFractionDigits(micros, input, string);
-    }
-
-    return length;
-  }
-
-  private static int addIntegerDigits(
-      MicroProps micros, FormatQuantity input, NumberStringBuilder string) {
-    int length = 0;
-    int integerCount = input.getUpperDisplayMagnitude() + 1;
-    for (int i = 0; i < integerCount; i++) {
-      // Add grouping separator
-      if (micros.grouping.groupAtPosition(i, input)) {
-        length +=
-            string.insert(
-                0,
-                micros.useCurrency
-                    ? micros.symbols.getMonetaryGroupingSeparatorString()
-                    : micros.symbols.getGroupingSeparatorString(),
-                NumberFormat.Field.GROUPING_SEPARATOR);
-      }
-
-      // Get and append the next digit value
-      byte nextDigit = input.getDigit(i);
-      if (micros.symbols.getCodePointZero() != -1) {
-        length +=
-            string.insertCodePoint(
-                0, micros.symbols.getCodePointZero() + nextDigit, NumberFormat.Field.INTEGER);
-      } else {
-        length +=
-            string.insert(
-                0, micros.symbols.getDigitStringsLocal()[nextDigit], NumberFormat.Field.INTEGER);
-      }
-    }
-    return length;
-  }
-
-  private static int addFractionDigits(
-      MicroProps micros, FormatQuantity input, NumberStringBuilder string) {
-    int length = 0;
-    int fractionCount = -input.getLowerDisplayMagnitude();
-    for (int i = 0; i < fractionCount; i++) {
-      // Get and append the next digit value
-      byte nextDigit = input.getDigit(-i - 1);
-      if (micros.symbols.getCodePointZero() != -1) {
-        length +=
-            string.appendCodePoint(
-                micros.symbols.getCodePointZero() + nextDigit, NumberFormat.Field.FRACTION);
-      } else {
-        length +=
-            string.append(
-                micros.symbols.getDigitStringsLocal()[nextDigit], NumberFormat.Field.FRACTION);
-      }
-    }
-    return length;
-  }
-}
diff --git a/icu4j/main/classes/core/src/newapi/impl/QuantityDependentModOuter.java b/icu4j/main/classes/core/src/newapi/impl/QuantityDependentModOuter.java
deleted file mode 100644 (file)
index 1b544b9..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-package newapi.impl;
-
-import java.util.Map;
-
-import com.ibm.icu.impl.StandardPlural;
-import com.ibm.icu.impl.number.FormatQuantity;
-import com.ibm.icu.impl.number.Modifier;
-import com.ibm.icu.text.PluralRules;
-
-public class QuantityDependentModOuter implements QuantityChain {
-  final Map<StandardPlural, Modifier> data;
-  final PluralRules rules;
-  final QuantityChain parent;
-
-  public QuantityDependentModOuter(Map<StandardPlural, Modifier> data, PluralRules rules, QuantityChain parent) {
-    this.data = data;
-    this.rules = rules;
-    this.parent = parent;
-  }
-
-  @Override
-  public MicroProps withQuantity(FormatQuantity quantity) {
-    MicroProps micros = parent.withQuantity(quantity);
-    // TODO: Avoid the copy here?
-    FormatQuantity copy = quantity.createCopy();
-    micros.rounding.apply(copy);
-    micros.modOuter = data.get(copy.getStandardPlural(rules));
-    return micros;
-  }
-}
diff --git a/icu4j/main/classes/core/src/newapi/impl/RoundingImpl.java b/icu4j/main/classes/core/src/newapi/impl/RoundingImpl.java
deleted file mode 100644 (file)
index 30f204c..0000000
+++ /dev/null
@@ -1,445 +0,0 @@
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-package newapi.impl;
-
-import java.math.BigDecimal;
-import java.math.MathContext;
-import java.math.RoundingMode;
-
-import com.ibm.icu.impl.number.FormatQuantity;
-import com.ibm.icu.impl.number.FormatQuantity4;
-import com.ibm.icu.impl.number.LdmlPatternInfo.PatternParseResult;
-import com.ibm.icu.impl.number.RoundingUtils;
-import com.ibm.icu.util.Currency;
-import com.ibm.icu.util.Currency.CurrencyUsage;
-
-import newapi.NumberFormatter.CurrencyRounding;
-import newapi.NumberFormatter.FractionRounding;
-import newapi.NumberFormatter.IRounding;
-import newapi.NumberFormatter.Rounding;
-
-/**
- * The internal version of {@link Rounding} with additional methods.
- *
- * <p>Although it seems as though RoundingImpl should extend Rounding, it actually extends
- * FractionRounding. This is because instances of FractionRounding are self-contained rounding
- * instances themselves, and they need to implement RoundingImpl. When ICU adopts Java 8, there will
- * be more options for the polymorphism, such as multiple inheritance with interfaces having default
- * methods and static factory methods on interfaces.
- */
-@SuppressWarnings("deprecation")
-public abstract class RoundingImpl extends FractionRounding.Internal implements Cloneable {
-
-  public static RoundingImpl forPattern(PatternParseResult patternInfo) {
-    if (patternInfo.positive.rounding != null) {
-      return RoundingImplIncrement.getInstance(patternInfo.positive.rounding.toBigDecimal());
-    } else if (patternInfo.positive.minimumSignificantDigits > 0) {
-      return RoundingImplSignificant.getInstance(
-          patternInfo.positive.minimumSignificantDigits,
-          patternInfo.positive.maximumSignificantDigits);
-    } else if (patternInfo.positive.exponentDigits > 0) {
-      // FIXME
-      throw new UnsupportedOperationException();
-    } else {
-      return RoundingImplFraction.getInstance(
-          patternInfo.positive.minimumFractionDigits, patternInfo.positive.maximumFractionDigits);
-    }
-  }
-
-  /**
-   * Returns a RoundingImpl no matter what is the type of the provided argument. If the argument is
-   * already a RoundingImpl, this method just returns the same object. Otherwise, it does some
-   * processing to build a RoundingImpl.
-   *
-   * @param rounding The input object, which might or might not be a RoundingImpl.
-   * @param currency A currency object to use in case the input object needs it.
-   * @return A RoundingImpl object.
-   */
-  public static RoundingImpl normalizeType(IRounding rounding, Currency currency) {
-    if (rounding instanceof RoundingImpl) {
-      return (RoundingImpl) rounding;
-    } else if (rounding instanceof RoundingImplCurrency) {
-      return ((RoundingImplCurrency) rounding).withCurrency(currency);
-    } else {
-      return RoundingImplLambda.getInstance(rounding);
-    }
-  }
-
-  private static final MathContext DEFAULT_MATH_CONTEXT =
-      RoundingUtils.mathContextUnlimited(RoundingMode.HALF_EVEN);
-
-  public MathContext mathContext;
-
-  public RoundingImpl() {
-    this.mathContext = DEFAULT_MATH_CONTEXT;
-    // TODO: This is ugly, but necessary if a RoundingImpl is created
-    // before this class has been initialized.
-    if (this.mathContext == null) {
-      this.mathContext = RoundingUtils.mathContextUnlimited(RoundingMode.HALF_EVEN);
-    }
-  }
-
-  @Override
-  public Rounding withMode(RoundingMode roundingMode) {
-    return withMode(RoundingUtils.mathContextUnlimited(roundingMode));
-  }
-
-  @Override
-  public Rounding withMode(MathContext mathContext) {
-    if (this.mathContext.equals(mathContext)) {
-      return this;
-    }
-    RoundingImpl other = (RoundingImpl) this.clone();
-    other.mathContext = mathContext;
-    return other;
-  }
-
-  abstract void apply(FormatQuantity value);
-
-  @Override
-  public BigDecimal round(BigDecimal input) {
-    // Provided for API compatibility.
-    FormatQuantity fq = new FormatQuantity4(input);
-    this.apply(fq);
-    return fq.toBigDecimal();
-  }
-
-  static interface MultiplierProducer {
-    int getMultiplier(int magnitude);
-  }
-
-  int chooseMultiplierAndApply(FormatQuantity input, MultiplierProducer producer) {
-    // TODO: Make a better and more efficient implementation.
-    // TODO: Avoid the object creation here.
-    FormatQuantity copy = input.createCopy();
-
-    assert !input.isZero();
-    int magnitude = input.getMagnitude();
-    int multiplier = producer.getMultiplier(magnitude);
-    input.adjustMagnitude(multiplier);
-    apply(input);
-
-    // If the number turned to zero when rounding, do not re-attempt the rounding.
-    if (!input.isZero() && input.getMagnitude() == magnitude + multiplier + 1) {
-      magnitude += 1;
-      input.copyFrom(copy);
-      multiplier = producer.getMultiplier(magnitude);
-      input.adjustMagnitude(multiplier);
-      assert input.getMagnitude() == magnitude + multiplier - 1;
-      apply(input);
-      assert input.getMagnitude() == magnitude + multiplier;
-    }
-
-    return multiplier;
-  }
-
-  @Override
-  public Object clone() {
-    try {
-      return super.clone();
-    } catch (CloneNotSupportedException e) {
-      // Should not happen since parent is Object
-      throw new AssertionError(e);
-    }
-  }
-
-  /** A dummy class used when the number has already been rounded elsewhere. */
-  public static class RoundingImplDummy extends RoundingImpl {
-    public static final RoundingImplDummy INSTANCE = new RoundingImplDummy();
-
-    private RoundingImplDummy() {}
-
-    @Override
-    void apply(FormatQuantity value) {}
-  }
-
-  public static class RoundingImplInfinity extends RoundingImpl {
-    @Override
-    void apply(FormatQuantity value) {
-      value.roundToInfinity();
-      value.setFractionLength(0, Integer.MAX_VALUE);
-    }
-  }
-
-  public static class RoundingImplFraction extends RoundingImpl {
-    int minFrac;
-    int maxFrac;
-
-    private static final RoundingImplFraction FIXED_0 = new RoundingImplFraction(0, 0);
-    private static final RoundingImplFraction FIXED_2 = new RoundingImplFraction(2, 2);
-
-    /** Assumes that minFrac <= maxFrac. */
-    public static RoundingImplFraction getInstance(int minFrac, int maxFrac) {
-      assert minFrac >= 0 && minFrac <= maxFrac;
-      if (minFrac == 0 && maxFrac == 0) {
-        return FIXED_0;
-      } else if (minFrac == 2 && maxFrac == 2) {
-        return FIXED_2;
-      } else {
-        return new RoundingImplFraction(minFrac, maxFrac);
-      }
-    }
-
-    /** Hook for public static final; uses integer rounding */
-    public RoundingImplFraction() {
-      this(0, 0);
-    }
-
-    private RoundingImplFraction(int minFrac, int maxFrac) {
-      this.minFrac = minFrac;
-      this.maxFrac = maxFrac;
-    }
-
-    @Override
-    void apply(FormatQuantity value) {
-      value.roundToMagnitude(getRoundingMagnitude(maxFrac), mathContext);
-      value.setFractionLength(Math.max(0, -getDisplayMagnitude(minFrac)), Integer.MAX_VALUE);
-    }
-
-    static int getRoundingMagnitude(int maxFrac) {
-      if (maxFrac == Integer.MAX_VALUE) {
-        return Integer.MIN_VALUE;
-      }
-      return -maxFrac;
-    }
-
-    static int getDisplayMagnitude(int minFrac) {
-      if (minFrac == 0) {
-        return Integer.MAX_VALUE;
-      }
-      return -minFrac;
-    }
-
-    @Override
-    public Rounding withMinFigures(int minFigures) {
-      if (minFigures > 0 && minFigures <= MAX_VALUE) {
-        return RoundingImplFractionSignificant.getInstance(this, minFigures, -1);
-      } else {
-        throw new IllegalArgumentException("Significant digits must be between 0 and " + MAX_VALUE);
-      }
-    }
-
-    @Override
-    public Rounding withMaxFigures(int maxFigures) {
-      if (maxFigures > 0 && maxFigures <= MAX_VALUE) {
-        return RoundingImplFractionSignificant.getInstance(this, -1, maxFigures);
-      } else {
-        throw new IllegalArgumentException("Significant digits must be between 0 and " + MAX_VALUE);
-      }
-    }
-  }
-
-  public static class RoundingImplSignificant extends RoundingImpl {
-    int minSig;
-    int maxSig;
-
-    private static final RoundingImplSignificant FIXED_2 = new RoundingImplSignificant(2, 2);
-    private static final RoundingImplSignificant FIXED_3 = new RoundingImplSignificant(3, 3);
-    private static final RoundingImplSignificant RANGE_2_3 = new RoundingImplSignificant(2, 3);
-
-    /** Assumes that minSig <= maxSig. */
-    public static RoundingImplSignificant getInstance(int minSig, int maxSig) {
-      assert minSig >= 0 && minSig <= maxSig;
-      if (minSig == 2 && maxSig == 2) {
-        return FIXED_2;
-      } else if (minSig == 3 && maxSig == 3) {
-        return FIXED_3;
-      } else if (minSig == 2 && maxSig == 3) {
-        return RANGE_2_3;
-      } else {
-        return new RoundingImplSignificant(minSig, maxSig);
-      }
-    }
-
-    RoundingImplSignificant(int minSig, int maxSig) {
-      this.minSig = minSig;
-      this.maxSig = maxSig;
-    }
-
-    @Override
-    void apply(FormatQuantity value) {
-      value.roundToMagnitude(getRoundingMagnitude(value, maxSig), mathContext);
-      value.setFractionLength(Math.max(0, -getDisplayMagnitude(value, minSig)), Integer.MAX_VALUE);
-    }
-
-    /** Version of {@link #apply} that obeys minInt constraints. */
-    public void apply(FormatQuantity quantity, int minInt) {
-      assert quantity.isZero();
-      quantity.setFractionLength(minSig - minInt, Integer.MAX_VALUE);
-    }
-
-    static int getRoundingMagnitude(FormatQuantity value, int maxSig) {
-      if (maxSig == Integer.MAX_VALUE) {
-        return Integer.MIN_VALUE;
-      }
-      int magnitude = value.isZero() ? 0 : value.getMagnitude();
-      return magnitude - maxSig + 1;
-    }
-
-    static int getDisplayMagnitude(FormatQuantity value, int minSig) {
-      int magnitude = value.isZero() ? 0 : value.getMagnitude();
-      return magnitude - minSig + 1;
-    }
-  }
-
-  public static class RoundingImplFractionSignificant extends RoundingImpl {
-    int minFrac;
-    int maxFrac;
-    int minSig;
-    int maxSig;
-
-    // Package-private
-    static final RoundingImplFractionSignificant COMPACT_STRATEGY =
-        new RoundingImplFractionSignificant(0, 0, 2, -1);
-
-    public static Rounding getInstance(FractionRounding _base, int minSig, int maxSig) {
-      assert _base instanceof RoundingImplFraction;
-      RoundingImplFraction base = (RoundingImplFraction) _base;
-      if (base.minFrac == 0 && base.maxFrac == 0 && minSig == 2 /* && maxSig == -1 */) {
-        return COMPACT_STRATEGY;
-      } else {
-        return new RoundingImplFractionSignificant(base.minFrac, base.maxFrac, minSig, maxSig);
-      }
-    }
-
-    /** Assumes that minFrac <= maxFrac and minSig <= maxSig except for -1. */
-    private RoundingImplFractionSignificant(int minFrac, int maxFrac, int minSig, int maxSig) {
-      // Exactly one of the arguments should be -1, either minSig or maxSig.
-      assert (minFrac != -1 && maxFrac != -1 && minSig == -1 && maxSig != -1 && minFrac <= maxFrac)
-          || (minFrac != -1 && maxFrac != -1 && minSig != -1 && maxSig == -1 && minFrac <= maxFrac);
-      this.minFrac = minFrac;
-      this.maxFrac = maxFrac;
-      this.minSig = minSig;
-      this.maxSig = maxSig;
-    }
-
-    @Override
-    void apply(FormatQuantity value) {
-      int displayMag = RoundingImplFraction.getDisplayMagnitude(minFrac);
-      int roundingMag = RoundingImplFraction.getRoundingMagnitude(maxFrac);
-      if (minSig == -1) {
-        // Max Sig override
-        int candidate = RoundingImplSignificant.getRoundingMagnitude(value, maxSig);
-        roundingMag = Math.max(roundingMag, candidate);
-      } else {
-        // Min Sig override
-        int candidate = RoundingImplSignificant.getDisplayMagnitude(value, minSig);
-        roundingMag = Math.min(roundingMag, candidate);
-      }
-      value.roundToMagnitude(roundingMag, mathContext);
-      value.setFractionLength(Math.max(0, -displayMag), Integer.MAX_VALUE);
-    }
-  }
-
-  public static class RoundingImplIncrement extends RoundingImpl {
-    BigDecimal increment;
-
-    private static final RoundingImplIncrement NICKEL =
-        new RoundingImplIncrement(BigDecimal.valueOf(0.5));
-
-    public static RoundingImplIncrement getInstance(BigDecimal increment) {
-      assert increment != null;
-      if (increment.compareTo(NICKEL.increment) == 0) {
-        return NICKEL;
-      } else {
-        return new RoundingImplIncrement(increment);
-      }
-    }
-
-    private RoundingImplIncrement(BigDecimal increment) {
-      this.increment = increment;
-    }
-
-    @Override
-    void apply(FormatQuantity value) {
-      value.roundToIncrement(increment, mathContext);
-      value.setFractionLength(increment.scale(), increment.scale());
-    }
-  }
-
-  public static class RoundingImplLambda extends RoundingImpl {
-    IRounding lambda;
-
-    public static RoundingImplLambda getInstance(IRounding lambda) {
-      assert !(lambda instanceof Rounding);
-      return new RoundingImplLambda(lambda);
-    }
-
-    private RoundingImplLambda(IRounding lambda) {
-      this.lambda = lambda;
-    }
-
-    @Override
-    void apply(FormatQuantity value) {
-      // TODO: Cache the BigDecimal between calls?
-      BigDecimal temp = value.toBigDecimal();
-      temp = lambda.round(temp);
-      value.setToBigDecimal(temp);
-      value.setFractionLength(temp.scale(), Integer.MAX_VALUE);
-    }
-  }
-
-  /**
-   * NOTE: This is unlike the other classes here. It is NOT a standalone rounder and it does NOT
-   * extend RoundingImpl.
-   */
-  public static class RoundingImplCurrency extends CurrencyRounding.Internal {
-    final CurrencyUsage usage;
-    final MathContext mc;
-
-    private static final RoundingImplCurrency MONETARY_STANDARD =
-        new RoundingImplCurrency(CurrencyUsage.STANDARD, DEFAULT_MATH_CONTEXT);
-
-    private static final RoundingImplCurrency MONETARY_CASH =
-        new RoundingImplCurrency(CurrencyUsage.CASH, DEFAULT_MATH_CONTEXT);
-
-    public static RoundingImplCurrency getInstance(CurrencyUsage usage) {
-      if (usage == CurrencyUsage.STANDARD) {
-        return MONETARY_STANDARD;
-      } else if (usage == CurrencyUsage.CASH) {
-        return MONETARY_CASH;
-      } else {
-        throw new AssertionError();
-      }
-    }
-
-    private RoundingImplCurrency(CurrencyUsage usage, MathContext mc) {
-      this.usage = usage;
-      this.mc = mc;
-    }
-
-    @Override
-    public RoundingImpl withCurrency(Currency currency) {
-      assert currency != null;
-      double incrementDouble = currency.getRoundingIncrement(usage);
-      if (incrementDouble != 0.0) {
-        BigDecimal increment = BigDecimal.valueOf(incrementDouble);
-        return RoundingImplIncrement.getInstance(increment);
-      } else {
-        int minMaxFrac = currency.getDefaultFractionDigits(usage);
-        return RoundingImplFraction.getInstance(minMaxFrac, minMaxFrac);
-      }
-    }
-
-    @Override
-    public RoundingImplCurrency withMode(RoundingMode roundingMode) {
-      // This is similar to RoundingImpl#withMode().
-      return withMode(RoundingUtils.mathContextUnlimited(roundingMode));
-    }
-
-    @Override
-    public RoundingImplCurrency withMode(MathContext mathContext) {
-      // This is similar to RoundingImpl#withMode().
-      if (mc.equals(mathContext)) {
-        return this;
-      }
-      return new RoundingImplCurrency(usage, mathContext);
-    }
-
-    @Override
-    public BigDecimal round(BigDecimal input) {
-      throw new UnsupportedOperationException(
-          "A currency must be specified before calling this method.");
-    }
-  }
-}
diff --git a/icu4j/main/classes/core/src/newapi/impl/ScientificImpl.java b/icu4j/main/classes/core/src/newapi/impl/ScientificImpl.java
deleted file mode 100644 (file)
index 526b50a..0000000
+++ /dev/null
@@ -1,145 +0,0 @@
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-package newapi.impl;
-
-import com.ibm.icu.impl.number.FormatQuantity;
-import com.ibm.icu.impl.number.Modifier;
-import com.ibm.icu.impl.number.NumberStringBuilder;
-import com.ibm.icu.text.DecimalFormatSymbols;
-import com.ibm.icu.text.NumberFormat;
-
-import newapi.NumberFormatter.SignDisplay;
-import newapi.impl.RoundingImpl.RoundingImplDummy;
-import newapi.impl.RoundingImpl.RoundingImplSignificant;
-
-public class ScientificImpl implements QuantityChain, RoundingImpl.MultiplierProducer {
-
-  final NotationImpl.NotationScientificImpl notation;
-  final DecimalFormatSymbols symbols;
-  final ScientificModifier[] precomputedMods;
-  final QuantityChain parent;
-
-  public static ScientificImpl getInstance(
-      NotationImpl.NotationScientificImpl notation,
-      DecimalFormatSymbols symbols,
-      boolean build,
-      QuantityChain parent) {
-    return new ScientificImpl(notation, symbols, build, parent);
-  }
-
-  private ScientificImpl(
-      NotationImpl.NotationScientificImpl notation,
-      DecimalFormatSymbols symbols,
-      boolean build,
-      QuantityChain parent) {
-    this.notation = notation;
-    this.symbols = symbols;
-    this.parent = parent;
-
-    if (build) {
-      // Pre-build the modifiers for exponents -12 through 12
-      precomputedMods = new ScientificModifier[25];
-      for (int i = -12; i <= 12; i++) {
-        precomputedMods[i + 12] = new ScientificModifier(i);
-      }
-    } else {
-      precomputedMods = null;
-    }
-  }
-
-  @Override
-  public MicroProps withQuantity(FormatQuantity quantity) {
-    MicroProps micros = parent.withQuantity(quantity);
-    assert micros.rounding != null;
-
-    // Treat zero as if it had magnitude 0
-    int exponent;
-    if (quantity.isZero()) {
-      if (notation.requireMinInt && micros.rounding instanceof RoundingImplSignificant) {
-        // Shown "00.000E0" on pattern "00.000E0"
-        ((RoundingImplSignificant) micros.rounding).apply(quantity, notation.engineeringInterval);
-        exponent = 0;
-      } else {
-        micros.rounding.apply(quantity);
-        exponent = 0;
-      }
-    } else {
-      exponent = -micros.rounding.chooseMultiplierAndApply(quantity, this);
-    }
-
-    // Add the Modifier for the scientific format.
-    if (precomputedMods != null && exponent >= -12 && exponent <= 12) {
-      micros.modInner = precomputedMods[exponent + 12];
-    } else {
-      micros.modInner = new ScientificModifier(exponent);
-    }
-
-    // We already performed rounding.  Do not perform it again.
-    micros.rounding = RoundingImplDummy.INSTANCE;
-
-    return micros;
-  }
-
-  private class ScientificModifier implements Modifier {
-    final int exponent;
-
-    ScientificModifier(int exponent) {
-      this.exponent = exponent;
-    }
-
-    @Override
-    public int apply(NumberStringBuilder output, int leftIndex, int rightIndex) {
-      // FIXME: Localized exponent separator location.
-      int i = rightIndex;
-      // Append the exponent separator and sign
-      i += output.insert(i, symbols.getExponentSeparator(), NumberFormat.Field.EXPONENT_SYMBOL);
-      if (exponent < 0 && notation.exponentSignDisplay != SignDisplay.NEVER) {
-        i += output.insert(i, symbols.getMinusSignString(), NumberFormat.Field.EXPONENT_SIGN);
-      } else if (notation.exponentSignDisplay == SignDisplay.ALWAYS) {
-        i += output.insert(i, symbols.getPlusSignString(), NumberFormat.Field.EXPONENT_SIGN);
-      }
-      // Append the exponent digits (using a simple inline algorithm)
-      int disp = Math.abs(exponent);
-      for (int j = 0; j < notation.minExponentDigits || disp > 0; j++, disp /= 10) {
-        int d = disp % 10;
-        String digitString = symbols.getDigitStringsLocal()[d];
-        i += output.insert(i - j, digitString, NumberFormat.Field.EXPONENT);
-      }
-      return i - rightIndex;
-    }
-
-    @Override
-    public boolean isStrong() {
-      return true;
-    }
-
-    @Override
-    public String getPrefix() {
-      // Should never get called
-      throw new AssertionError();
-    }
-
-    @Override
-    public String getSuffix() {
-      // Should never get called
-      throw new AssertionError();
-    }
-  }
-
-  @Override
-  public int getMultiplier(int magnitude) {
-    int interval = notation.engineeringInterval;
-    int digitsShown;
-    if (notation.requireMinInt) {
-      // For patterns like "000.00E0" and ".00E0"
-      digitsShown = interval;
-    } else if (interval <= 1) {
-      // For patterns like "0.00E0" and "@@@E0"
-      digitsShown = 1;
-    } else {
-      // For patterns like "##0.00"
-      digitsShown = ((magnitude % interval + interval) % interval) + 1;
-    }
-    return digitsShown - magnitude - 1;
-  }
-}
diff --git a/icu4j/main/classes/core/src/newapi/impl/SkeletonBuilder.java b/icu4j/main/classes/core/src/newapi/impl/SkeletonBuilder.java
deleted file mode 100644 (file)
index 8663f71..0000000
+++ /dev/null
@@ -1,624 +0,0 @@
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-package newapi.impl;
-
-import java.math.BigDecimal;
-import java.math.MathContext;
-import java.math.RoundingMode;
-
-import com.ibm.icu.impl.number.ThingsNeedingNewHome.PadPosition;
-import com.ibm.icu.text.CompactDecimalFormat.CompactStyle;
-import com.ibm.icu.text.DecimalFormatSymbols;
-import com.ibm.icu.text.MeasureFormat.FormatWidth;
-import com.ibm.icu.text.NumberingSystem;
-import com.ibm.icu.util.Currency;
-import com.ibm.icu.util.Currency.CurrencyUsage;
-import com.ibm.icu.util.Dimensionless;
-import com.ibm.icu.util.MeasureUnit;
-
-import newapi.NumberFormatter.DecimalMarkDisplay;
-import newapi.NumberFormatter.FractionRounding;
-import newapi.NumberFormatter.Grouping;
-import newapi.NumberFormatter.IGrouping;
-import newapi.NumberFormatter.IRounding;
-import newapi.NumberFormatter.IntegerWidth;
-import newapi.NumberFormatter.Notation;
-import newapi.NumberFormatter.NotationCompact;
-import newapi.NumberFormatter.NotationScientific;
-import newapi.NumberFormatter.NotationSimple;
-import newapi.NumberFormatter.Padding;
-import newapi.NumberFormatter.Rounding;
-import newapi.NumberFormatter.SignDisplay;
-import newapi.impl.RoundingImpl.RoundingImplCurrency;
-import newapi.impl.RoundingImpl.RoundingImplFraction;
-import newapi.impl.RoundingImpl.RoundingImplFractionSignificant;
-import newapi.impl.RoundingImpl.RoundingImplIncrement;
-import newapi.impl.RoundingImpl.RoundingImplInfinity;
-import newapi.impl.RoundingImpl.RoundingImplSignificant;
-
-public final class SkeletonBuilder {
-
-  public static String macrosToSkeleton(MacroProps macros) {
-    // Print out the values in their canonical order.
-    StringBuilder sb = new StringBuilder();
-    if (macros.notation != null) {
-      // sb.append("notation=");
-      notationToSkeleton(macros.notation, sb);
-      sb.append(' ');
-    }
-    if (macros.unit != null) {
-      // sb.append("unit=");
-      unitToSkeleton(macros.unit, sb);
-      sb.append(' ');
-    }
-    if (macros.rounding != null) {
-      // sb.append("rounding=");
-      roundingToSkeleton(macros.rounding, sb);
-      sb.append(' ');
-    }
-    if (macros.grouping != null) {
-      sb.append("grouping=");
-      groupingToSkeleton(macros.grouping, sb);
-      sb.append(' ');
-    }
-    if (macros.padding != null) {
-      sb.append("padding=");
-      paddingToSkeleton(macros.padding, sb);
-      sb.append(' ');
-    }
-    if (macros.integerWidth != null) {
-      sb.append("integer-width=");
-      integerWidthToSkeleton(macros.integerWidth, sb);
-      sb.append(' ');
-    }
-    if (macros.symbols != null) {
-      sb.append("symbols=");
-      symbolsToSkeleton(macros.symbols, sb);
-      sb.append(' ');
-    }
-    if (macros.unitWidth != null) {
-      sb.append("unit-width=");
-      unitWidthToSkeleton(macros.unitWidth, sb);
-      sb.append(' ');
-    }
-    if (macros.sign != null) {
-      sb.append("sign=");
-      signToSkeleton(macros.sign, sb);
-      sb.append(' ');
-    }
-    if (macros.decimal != null) {
-      sb.append("decimal=");
-      decimalToSkeleton(macros.decimal, sb);
-      sb.append(' ');
-    }
-    if (sb.length() > 0) {
-      // Remove the trailing space
-      sb.setLength(sb.length() - 1);
-    }
-    return sb.toString();
-  }
-
-  public static MacroProps skeletonToMacros(String skeleton) {
-    MacroProps macros = new MacroProps();
-    for (int offset = 0; offset < skeleton.length(); ) {
-      char c = skeleton.charAt(offset);
-      switch (c) {
-        case ' ':
-          offset++;
-          break;
-        case 'E':
-        case 'C':
-        case 'I':
-          offset += skeletonToNotation(skeleton, offset, macros);
-          break;
-        case '%':
-        case 'B':
-        case '$':
-        case 'U':
-          offset += skeletonToUnit(skeleton, offset, macros);
-          break;
-        case 'F':
-        case 'S':
-        case 'M':
-        case 'G':
-        case 'Y':
-          offset += skeletonToRounding(skeleton, offset, macros);
-          break;
-        default:
-          if (skeleton.regionMatches(offset, "notation=", 0, 9)) {
-            offset += 9;
-            offset += skeletonToNotation(skeleton, offset, macros);
-          } else if (skeleton.regionMatches(offset, "unit=", 0, 5)) {
-            offset += 5;
-            offset += skeletonToUnit(skeleton, offset, macros);
-          } else if (skeleton.regionMatches(offset, "rounding=", 0, 9)) {
-            offset += 9;
-            offset += skeletonToRounding(skeleton, offset, macros);
-          } else if (skeleton.regionMatches(offset, "grouping=", 0, 9)) {
-            offset += 9;
-            offset += skeletonToGrouping(skeleton, offset, macros);
-          } else if (skeleton.regionMatches(offset, "padding=", 0, 9)) {
-            offset += 8;
-            offset += skeletonToPadding(skeleton, offset, macros);
-          } else if (skeleton.regionMatches(offset, "integer-width=", 0, 9)) {
-            offset += 14;
-            offset += skeletonToIntegerWidth(skeleton, offset, macros);
-          } else if (skeleton.regionMatches(offset, "symbols=", 0, 9)) {
-            offset += 8;
-            offset += skeletonToSymbols(skeleton, offset, macros);
-          } else if (skeleton.regionMatches(offset, "unit-width=", 0, 9)) {
-            offset += 11;
-            offset += skeletonToUnitWidth(skeleton, offset, macros);
-          } else if (skeleton.regionMatches(offset, "sign=", 0, 9)) {
-            offset += 5;
-            offset += skeletonToSign(skeleton, offset, macros);
-          } else if (skeleton.regionMatches(offset, "decimal=", 0, 9)) {
-            offset += 8;
-            offset += skeletonToDecimal(skeleton, offset, macros);
-          } else {
-            throw new IllegalArgumentException(
-                "Unexpected token at offset " + offset + " in skeleton string: " + c);
-          }
-      }
-    }
-    return macros;
-  }
-
-  private static void notationToSkeleton(Notation value, StringBuilder sb) {
-    if (value instanceof NotationScientific) {
-      NotationImpl.NotationScientificImpl notation = (NotationImpl.NotationScientificImpl) value;
-      sb.append('E');
-      if (notation.engineeringInterval != 1) {
-        sb.append(notation.engineeringInterval);
-      }
-      if (notation.exponentSignDisplay == SignDisplay.ALWAYS) {
-        sb.append('+');
-      } else if (notation.exponentSignDisplay == SignDisplay.NEVER) {
-        sb.append('!');
-      } else {
-        assert notation.exponentSignDisplay == SignDisplay.AUTO;
-      }
-      if (notation.minExponentDigits != 1) {
-        for (int i = 0; i < notation.minExponentDigits; i++) {
-          sb.append('0');
-        }
-      }
-    } else if (value instanceof NotationCompact) {
-      NotationImpl.NotationCompactImpl notation = (NotationImpl.NotationCompactImpl) value;
-      if (notation.compactStyle == CompactStyle.SHORT) {
-        sb.append('C');
-      } else {
-        // FIXME: CCC or CCCC instead?
-        sb.append("CC");
-      }
-    } else {
-      assert value instanceof NotationSimple;
-      sb.append('I');
-    }
-  }
-
-  private static int skeletonToNotation(String skeleton, int offset, MacroProps output) {
-    int originalOffset = offset;
-    char c0 = skeleton.charAt(offset++);
-    Notation result = null;
-    if (c0 == 'E') {
-      int engineering = 1;
-      SignDisplay sign = SignDisplay.AUTO;
-      int minExponentDigits = 0;
-      char c = safeCharAt(skeleton, offset++);
-      if (c >= '1' && c <= '9') {
-        engineering = c - '0';
-        c = safeCharAt(skeleton, offset++);
-      }
-      if (c == '+') {
-        sign = SignDisplay.ALWAYS;
-        c = safeCharAt(skeleton, offset++);
-      }
-      if (c == '!') {
-        sign = SignDisplay.NEVER;
-        c = safeCharAt(skeleton, offset++);
-      }
-      while (c == '0') {
-        minExponentDigits++;
-        c = safeCharAt(skeleton, offset++);
-      }
-      minExponentDigits = Math.max(1, minExponentDigits);
-      result = new NotationImpl.NotationScientificImpl(engineering, false, minExponentDigits, sign);
-    } else if (c0 == 'C') {
-      char c = safeCharAt(skeleton, offset++);
-      if (c == 'C') {
-        result = Notation.COMPACT_LONG;
-      } else {
-        result = Notation.COMPACT_SHORT;
-      }
-    } else if (c0 == 'I') {
-      result = Notation.SIMPLE;
-    }
-    output.notation = result;
-    return offset - originalOffset;
-  }
-
-  private static void unitToSkeleton(MeasureUnit value, StringBuilder sb) {
-    if (value.getType().equals("dimensionless")) {
-      if (value.getSubtype().equals("percent")) {
-        sb.append('%');
-      } else if (value.getSubtype().equals("permille")) {
-        sb.append("%%");
-      } else {
-        assert value.getSubtype().equals("base");
-        sb.append('B');
-      }
-    } else if (value.getType().equals("currency")) {
-      sb.append('$');
-      sb.append(value.getSubtype());
-    } else {
-      sb.append("U:");
-      sb.append(value.getType());
-      sb.append(':');
-      sb.append(value.getSubtype());
-    }
-  }
-
-  private static int skeletonToUnit(String skeleton, int offset, MacroProps output) {
-    int originalOffset = offset;
-    char c0 = skeleton.charAt(offset++);
-    MeasureUnit result = null;
-    if (c0 == '%') {
-      char c = safeCharAt(skeleton, offset++);
-      if (c == '%') {
-        result = Dimensionless.PERCENT;
-      } else {
-        result = Dimensionless.PERMILLE;
-      }
-    } else if (c0 == 'B') {
-      result = Dimensionless.BASE;
-    } else if (c0 == '$') {
-      String currencyCode = skeleton.substring(offset, offset + 3);
-      offset += 3;
-      result = Currency.getInstance(currencyCode);
-    } else if (c0 == 'U') {
-      StringBuilder sb = new StringBuilder();
-      offset += consumeUntil(skeleton, offset, ':', sb);
-      String type = sb.toString();
-      sb.setLength(0);
-      offset += consumeUntil(skeleton, offset, ' ', sb);
-      String subtype = sb.toString();
-      for (MeasureUnit candidate : MeasureUnit.getAvailable(type)) {
-        if (candidate.getSubtype().equals(subtype)) {
-          result = candidate;
-          break;
-        }
-      }
-    }
-    output.unit = result;
-    return offset - originalOffset;
-  }
-
-  private static void roundingToSkeleton(IRounding value, StringBuilder sb) {
-    if (!(value instanceof Rounding)) {
-      // FIXME: Throw an exception here instead?
-      return;
-    }
-    MathContext mathContext;
-    if (value instanceof RoundingImplFraction) {
-      RoundingImplFraction rounding = (RoundingImplFraction) value;
-      sb.append('F');
-      minMaxToSkeletonHelper(rounding.minFrac, rounding.maxFrac, sb);
-      mathContext = rounding.mathContext;
-    } else if (value instanceof RoundingImplSignificant) {
-      RoundingImplSignificant rounding = (RoundingImplSignificant) value;
-      sb.append('S');
-      minMaxToSkeletonHelper(rounding.minSig, rounding.maxSig, sb);
-      mathContext = rounding.mathContext;
-    } else if (value instanceof RoundingImplFractionSignificant) {
-      RoundingImplFractionSignificant rounding = (RoundingImplFractionSignificant) value;
-      sb.append('F');
-      minMaxToSkeletonHelper(rounding.minFrac, rounding.maxFrac, sb);
-      if (rounding.minSig != -1) {
-        sb.append('>');
-        sb.append(rounding.minSig);
-      } else {
-        sb.append('<');
-        sb.append(rounding.maxSig);
-      }
-      mathContext = rounding.mathContext;
-    } else if (value instanceof RoundingImplIncrement) {
-      RoundingImplIncrement rounding = (RoundingImplIncrement) value;
-      sb.append('M');
-      sb.append(rounding.increment.toString());
-      mathContext = rounding.mathContext;
-    } else if (value instanceof RoundingImplCurrency) {
-      RoundingImplCurrency rounding = (RoundingImplCurrency) value;
-      sb.append('G');
-      sb.append(rounding.usage.name());
-      mathContext = rounding.mc;
-    } else {
-      RoundingImplInfinity rounding = (RoundingImplInfinity) value;
-      sb.append('Y');
-      mathContext = rounding.mathContext;
-    }
-    // RoundingMode
-    RoundingMode roundingMode = mathContext.getRoundingMode();
-    if (roundingMode != RoundingMode.HALF_EVEN) {
-      sb.append(';');
-      sb.append(roundingMode.name());
-    }
-  }
-
-  private static void minMaxToSkeletonHelper(int minFrac, int maxFrac, StringBuilder sb) {
-    if (minFrac == maxFrac) {
-      sb.append(minFrac);
-    } else {
-      boolean showMaxFrac = (maxFrac >= 0 && maxFrac < Integer.MAX_VALUE);
-      if (minFrac > 0 || !showMaxFrac) {
-        sb.append(minFrac);
-      }
-      sb.append('-');
-      if (showMaxFrac) {
-        sb.append(maxFrac);
-      }
-    }
-  }
-
-  private static int skeletonToRounding(String skeleton, int offset, MacroProps output) {
-    int originalOffset = offset;
-    char c0 = skeleton.charAt(offset++);
-    Rounding result = null;
-    if (c0 == 'F') {
-      int[] minMax = new int[2];
-      offset += skeletonToMinMaxHelper(skeleton, offset, minMax);
-      FractionRounding temp = RoundingImplFraction.getInstance(minMax[0], minMax[1]);
-      char c1 = skeleton.charAt(offset++);
-      if (c1 == '<') {
-        char c2 = skeleton.charAt(offset++);
-        result = temp.withMaxFigures(c2 - '0');
-      } else if (c1 == '>') {
-        char c2 = skeleton.charAt(offset++);
-        result = temp.withMinFigures(c2 - '0');
-      } else {
-        result = temp;
-      }
-    } else if (c0 == 'S') {
-      int[] minMax = new int[2];
-      offset += skeletonToMinMaxHelper(skeleton, offset, minMax);
-      result = RoundingImplSignificant.getInstance(minMax[0], minMax[1]);
-    } else if (c0 == 'M') {
-      StringBuilder sb = new StringBuilder();
-      offset += consumeUntil(skeleton, offset, ' ', sb);
-      BigDecimal increment = new BigDecimal(sb.toString());
-      result = RoundingImplIncrement.getInstance(increment);
-    } else if (c0 == 'G') {
-      StringBuilder sb = new StringBuilder();
-      offset += consumeUntil(skeleton, offset, ' ', sb);
-      CurrencyUsage usage = Enum.valueOf(CurrencyUsage.class, sb.toString());
-      result = Rounding.currency(usage);
-    } else if (c0 == 'Y') {
-      result = Rounding.NONE;
-    }
-    output.rounding = result;
-    return offset - originalOffset;
-  }
-
-  private static int skeletonToMinMaxHelper(String skeleton, int offset, int[] output) {
-    int originalOffset = offset;
-    char c0 = safeCharAt(skeleton, offset++);
-    char c1 = safeCharAt(skeleton, offset++);
-    // TODO: This algorithm breaks if the number is more than 1 char wide.
-    if (c1 == '-') {
-      output[0] = c0 - '0';
-      char c2 = safeCharAt(skeleton, offset++);
-      if (c2 == ' ') {
-        output[1] = Integer.MAX_VALUE;
-      } else {
-        output[1] = c2 - '0';
-      }
-    } else if ('0' <= c1 && c1 <= '9') {
-      output[0] = 0;
-      output[1] = c1 - '0';
-    } else {
-      offset--;
-      output[0] = c0 - '0';
-      output[1] = c0 - '0';
-    }
-    return offset - originalOffset;
-  }
-
-  private static void groupingToSkeleton(IGrouping value, StringBuilder sb) {
-    if (!(value instanceof Grouping)) {
-      // FIXME: Throw an exception here instead?
-      sb.append("custom");
-      return;
-    }
-    if (value.equals(Grouping.DEFAULT)) {
-      sb.append("DEFAULT");
-    } else if (value.equals(Grouping.MIN_2_DIGITS)) {
-      sb.append("DEFAULT_MIN_2_DIGITS");
-    } else if (value.equals(Grouping.NONE)) {
-      sb.append("NONE");
-    } else {
-      GroupingImpl grouping = (GroupingImpl) value;
-      if (grouping.grouping2 == -1 || grouping.grouping2 == 0) {
-        sb.append("NONE");
-      } else {
-        sb.append(grouping.grouping1);
-        if (grouping.grouping2 != grouping.grouping1) {
-          sb.append(',');
-          sb.append(grouping.grouping2);
-        }
-        if (grouping.min2) {
-          sb.append('&');
-        }
-      }
-    }
-  }
-
-  private static int skeletonToGrouping(String skeleton, int offset, MacroProps output) {
-    int originalOffset = offset;
-    char c0 = skeleton.charAt(offset++);
-    Grouping result = null;
-    if ('0' <= c0 && c0 <= '9') {
-      char c1 = safeCharAt(skeleton, offset++);
-      if (c1 == ',') {
-        char c2 = safeCharAt(skeleton, offset++);
-        char c3 = safeCharAt(skeleton, offset++);
-        result = GroupingImpl.getInstance((byte) (c0 - '0'), (byte) (c2 - '0'), c3 == '&');
-      } else {
-        result = GroupingImpl.getInstance((byte) (c0 - '0'), (byte) (c0 - '0'), c1 == '&');
-      }
-    } else {
-      StringBuilder sb = new StringBuilder();
-      offset += consumeUntil(skeleton, --offset, ' ', sb);
-      String name = sb.toString();
-      if (name.equals("DEFAULT")) {
-        result = Grouping.DEFAULT;
-      } else if (name.equals("DEFAULT_MIN_2_DIGITS")) {
-        result = Grouping.MIN_2_DIGITS;
-      } else if (name.equals("NONE")) {
-        result = Grouping.NONE;
-      }
-    }
-    output.grouping = result;
-    return offset - originalOffset;
-  }
-
-  private static void paddingToSkeleton(Padding value, StringBuilder sb) {
-    PaddingImpl padding = (PaddingImpl) value;
-    if (padding == Padding.NONE) {
-      sb.append("NONE");
-      return;
-    }
-    sb.append(padding.targetWidth);
-    sb.append(':');
-    sb.append(padding.position.name());
-    sb.append(':');
-    if (!padding.paddingString.equals(" ")) {
-      sb.append(padding.paddingString);
-    }
-  }
-
-  private static int skeletonToPadding(String skeleton, int offset, MacroProps output) {
-    int originalOffset = offset;
-    char c0 = skeleton.charAt(offset++);
-    if (c0 == 'N') {
-      offset += consumeUntil(skeleton, --offset, ' ', null);
-    } else if ('0' <= c0 && c0 <= '9') {
-      long intResult = consumeInt(skeleton, --offset);
-      offset += intResult & 0xffffffff;
-      int width = (int) (intResult >>> 32);
-      char c1 = safeCharAt(skeleton, offset++);
-      if (c1 != ':') {
-        return offset - originalOffset - 1;
-      }
-      StringBuilder sb = new StringBuilder();
-      offset += consumeUntil(skeleton, offset, ':', sb);
-      String padPositionString = sb.toString();
-      sb.setLength(0);
-      offset += consumeUntil(skeleton, offset, ' ', sb);
-      String string = (sb.length() == 0) ? " " : sb.toString();
-      PadPosition position = Enum.valueOf(PadPosition.class, padPositionString);
-      output.padding = PaddingImpl.getInstance(string, width, position);
-    }
-    return offset - originalOffset;
-  }
-
-  private static void integerWidthToSkeleton(IntegerWidth value, StringBuilder sb) {
-    IntegerWidthImpl impl = (IntegerWidthImpl) value;
-    sb.append(impl.minInt);
-    if (impl.maxInt != impl.minInt) {
-      sb.append('-');
-      if (impl.maxInt < Integer.MAX_VALUE) {
-        sb.append(impl.maxInt);
-      }
-    }
-  }
-
-  private static int skeletonToIntegerWidth(String skeleton, int offset, MacroProps output) {
-    int originalOffset = offset;
-    long intResult = consumeInt(skeleton, offset);
-    offset += intResult & 0xffffffff;
-    int minInt = (int) (intResult >>> 32);
-    char c1 = safeCharAt(skeleton, --offset);
-    int maxInt;
-    if (c1 == '-') {
-        intResult = consumeInt(skeleton, offset);
-        offset += intResult & 0xffffffff;
-        maxInt = (int) (intResult >>> 32);
-    }
-  }
-
-  private static void symbolsToSkeleton(Object value, StringBuilder sb) {
-    if (value instanceof DecimalFormatSymbols) {
-      // TODO: Check to see if any of the symbols are not default?
-      sb.append("loc:");
-      sb.append(((DecimalFormatSymbols) value).getULocale());
-    } else {
-      sb.append("ns:");
-      sb.append(((NumberingSystem) value).getName());
-    }
-  }
-
-  private static void unitWidthToSkeleton(FormatWidth value, StringBuilder sb) {
-    sb.append(value.name());
-  }
-
-  private static int skeletonToUnitWidth(String skeleton, int offset, MacroProps output) {
-    int originalOffset = offset;
-    StringBuilder sb = new StringBuilder();
-    offset += consumeUntil(skeleton, offset, ' ', sb);
-    output.unitWidth = Enum.valueOf(FormatWidth.class, sb.toString());
-    return offset - originalOffset;
-  }
-
-  private static void signToSkeleton(SignDisplay value, StringBuilder sb) {
-    sb.append(value.name());
-  }
-
-  private static int skeletonToSign(String skeleton, int offset, MacroProps output) {
-    int originalOffset = offset;
-    StringBuilder sb = new StringBuilder();
-    offset += consumeUntil(skeleton, offset, ' ', sb);
-    output.sign = Enum.valueOf(SignDisplay.class, sb.toString());
-    return offset - originalOffset;
-  }
-
-  private static void decimalToSkeleton(DecimalMarkDisplay value, StringBuilder sb) {
-    sb.append(value.name());
-  }
-
-  private static int skeletonToDecimal(String skeleton, int offset, MacroProps output) {
-    int originalOffset = offset;
-    StringBuilder sb = new StringBuilder();
-    offset += consumeUntil(skeleton, offset, ' ', sb);
-    output.decimal = Enum.valueOf(DecimalMarkDisplay.class, sb.toString());
-    return offset - originalOffset;
-  }
-
-  private static char safeCharAt(String str, int offset) {
-    if (offset < str.length()) {
-      return str.charAt(offset);
-    } else {
-      return ' ';
-    }
-  }
-
-  private static int consumeUntil(String skeleton, int offset, char brk, StringBuilder sb) {
-    int originalOffset = offset;
-    char c = safeCharAt(skeleton, offset++);
-    while (c != brk) {
-      if (sb != null) sb.append(c);
-      c = safeCharAt(skeleton, offset++);
-    }
-    return offset - originalOffset;
-  }
-
-  private static long consumeInt(String skeleton, int offset) {
-    int originalOffset = offset;
-    char c = safeCharAt(skeleton, offset++);
-    int result = 0;
-    while ('0' <= c && c <= '9') {
-      result = (result * 10) + (c - '0');
-      c = safeCharAt(skeleton, offset++);
-    }
-    return (offset - originalOffset) | (((long) result) << 32);
-  }
-}
diff --git a/icu4j/main/classes/core/src/newapi/impl/Worker1.java b/icu4j/main/classes/core/src/newapi/impl/Worker1.java
deleted file mode 100644 (file)
index 99cdf87..0000000
+++ /dev/null
@@ -1,258 +0,0 @@
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-package newapi.impl;
-
-import java.util.Map;
-
-import com.ibm.icu.impl.StandardPlural;
-import com.ibm.icu.impl.number.FormatQuantity;
-import com.ibm.icu.impl.number.LdmlPatternInfo;
-import com.ibm.icu.impl.number.LdmlPatternInfo.PatternParseResult;
-import com.ibm.icu.impl.number.Modifier;
-import com.ibm.icu.impl.number.NumberStringBuilder;
-import com.ibm.icu.impl.number.modifiers.ConstantAffixModifier;
-import com.ibm.icu.text.CompactDecimalFormat.CompactStyle;
-import com.ibm.icu.text.CompactDecimalFormat.CompactType;
-import com.ibm.icu.text.DecimalFormatSymbols;
-import com.ibm.icu.text.MeasureFormat.FormatWidth;
-import com.ibm.icu.text.NumberFormat;
-import com.ibm.icu.text.NumberingSystem;
-import com.ibm.icu.text.PluralRules;
-import com.ibm.icu.util.Currency;
-import com.ibm.icu.util.Currency.CurrencyUsage;
-import com.ibm.icu.util.Dimensionless;
-import com.ibm.icu.util.ULocale;
-
-import newapi.NumberFormatter.DecimalMarkDisplay;
-import newapi.NumberFormatter.Grouping;
-import newapi.NumberFormatter.NotationCompact;
-import newapi.NumberFormatter.NotationScientific;
-import newapi.NumberFormatter.Rounding;
-import newapi.NumberFormatter.SignDisplay;
-
-public class Worker1 {
-
-  public static Worker1 fromMacros(MacroProps macros) {
-    return new Worker1(make(macros, true));
-  }
-
-  public static MicroProps applyStatic(
-      MacroProps macros, FormatQuantity inValue, NumberStringBuilder outString) {
-    MicroProps micros = make(macros, false).withQuantity(inValue);
-    applyStatic(micros, inValue, outString);
-    return micros;
-  }
-
-  private static final Currency DEFAULT_CURRENCY = Currency.getInstance("XXX");
-
-  final QuantityChain microsGenerator;
-
-  private Worker1(QuantityChain microsGenerator) {
-    this.microsGenerator = microsGenerator;
-  }
-
-  public MicroProps apply(FormatQuantity inValue, NumberStringBuilder outString) {
-    MicroProps micros = microsGenerator.withQuantity(inValue);
-    applyStatic(micros, inValue, outString);
-    return micros;
-  }
-
-  //////////
-
-  private static QuantityChain make(MacroProps input, boolean build) {
-
-    String innerPattern = null;
-    Map<StandardPlural, Modifier> outerMods = null;
-    Rounding defaultRounding = Rounding.NONE;
-    Currency currency = DEFAULT_CURRENCY;
-    FormatWidth unitWidth = null;
-    boolean perMille = false;
-    PluralRules rules = input.rules;
-
-    MicroProps micros = new MicroProps(build);
-    QuantityChain chain = micros;
-
-    // Copy over the simple settings
-    micros.sign = input.sign == null ? SignDisplay.AUTO : input.sign;
-    micros.decimal = input.decimal == null ? DecimalMarkDisplay.AUTO : input.decimal;
-    micros.multiplier = 0;
-    micros.integerWidth =
-        input.integerWidth == null
-            ? IntegerWidthImpl.DEFAULT
-            : (IntegerWidthImpl) input.integerWidth;
-
-    if (input.unit == null || input.unit == Dimensionless.BASE) {
-      // No units; default format
-      innerPattern = NumberFormat.getPatternForStyle(input.loc, NumberFormat.NUMBERSTYLE);
-    } else if (input.unit == Dimensionless.PERCENT) {
-      // Percent
-      innerPattern = NumberFormat.getPatternForStyle(input.loc, NumberFormat.PERCENTSTYLE);
-      micros.multiplier += 2;
-    } else if (input.unit == Dimensionless.PERMILLE) {
-      // Permille
-      innerPattern = NumberFormat.getPatternForStyle(input.loc, NumberFormat.PERCENTSTYLE);
-      micros.multiplier += 3;
-      perMille = true;
-    } else if (input.unit instanceof Currency && input.unitWidth != FormatWidth.WIDE) {
-      // Narrow, short, or ISO currency.
-      // TODO: Accounting style?
-      innerPattern = NumberFormat.getPatternForStyle(input.loc, NumberFormat.CURRENCYSTYLE);
-      defaultRounding = Rounding.currency(CurrencyUsage.STANDARD);
-      currency = (Currency) input.unit;
-      micros.useCurrency = true;
-      unitWidth = (input.unitWidth == null) ? FormatWidth.NARROW : input.unitWidth;
-    } else if (input.unit instanceof Currency) {
-      // Currency long name
-      innerPattern = NumberFormat.getPatternForStyle(input.loc, NumberFormat.NUMBERSTYLE);
-      outerMods = DataUtils.getCurrencyLongNameModifiers(input.loc, (Currency) input.unit);
-      defaultRounding = Rounding.currency(CurrencyUsage.STANDARD);
-      currency = (Currency) input.unit;
-      micros.useCurrency = true;
-      unitWidth = input.unitWidth = FormatWidth.WIDE;
-    } else {
-      // MeasureUnit
-      innerPattern = NumberFormat.getPatternForStyle(input.loc, NumberFormat.NUMBERSTYLE);
-      unitWidth = (input.unitWidth == null) ? FormatWidth.SHORT : input.unitWidth;
-      outerMods = DataUtils.getMeasureUnitModifiers(input.loc, input.unit, unitWidth);
-    }
-
-    // Parse the pattern, which is used for grouping and affixes only.
-    PatternParseResult patternInfo = LdmlPatternInfo.parse(innerPattern);
-
-    // Symbols
-    if (input.symbols == null) {
-      micros.symbols = DecimalFormatSymbols.getInstance(input.loc);
-    } else if (input.symbols instanceof DecimalFormatSymbols) {
-      micros.symbols = (DecimalFormatSymbols) input.symbols;
-    } else if (input.symbols instanceof NumberingSystem) {
-      // TODO: Do this more efficiently. Will require modifying DecimalFormatSymbols.
-      NumberingSystem ns = (NumberingSystem) input.symbols;
-      ULocale temp = input.loc.setKeywordValue("numbers", ns.getName());
-      micros.symbols = DecimalFormatSymbols.getInstance(temp);
-    } else {
-      throw new AssertionError();
-    }
-
-    // TODO: Normalize the currency (accept symbols from DecimalFormatSymbols)?
-    // currency = CustomSymbolCurrency.resolve(currency, input.loc, micros.symbols);
-
-    // Multiplier (compatibility mode value).
-    // An int magnitude multiplier is used when not in compatibility mode to
-    // reduce object creations.
-    if (input.multiplier != null) {
-      chain = input.multiplier.copyAndChain(chain);
-    }
-
-    // Rounding strategy
-    if (input.rounding != null) {
-      micros.rounding = RoundingImpl.normalizeType(input.rounding, currency);
-    } else if (input.notation instanceof NotationCompact) {
-      micros.rounding = RoundingImpl.RoundingImplFractionSignificant.COMPACT_STRATEGY;
-    } else {
-      micros.rounding = RoundingImpl.normalizeType(defaultRounding, currency);
-    }
-
-    // Grouping strategy
-    if (input.grouping != null) {
-      micros.grouping = GroupingImpl.normalizeType(input.grouping, patternInfo);
-    } else if (input.notation instanceof NotationCompact) {
-      // Compact notation uses minGrouping by default since ICU 59
-      micros.grouping = GroupingImpl.normalizeType(Grouping.MIN_2_DIGITS, patternInfo);
-    } else {
-      micros.grouping = GroupingImpl.normalizeType(Grouping.DEFAULT, patternInfo);
-    }
-
-    // Inner modifier (scientific notation)
-    if (input.notation instanceof NotationScientific) {
-      assert input.notation instanceof NotationImpl.NotationScientificImpl;
-      chain =
-          ScientificImpl.getInstance(
-              (NotationImpl.NotationScientificImpl) input.notation, micros.symbols, build, chain);
-    } else {
-      // No inner modifier required
-      micros.modInner = ConstantAffixModifier.EMPTY;
-    }
-
-    // Middle modifier (patterns, positive/negative, currency symbols, percent)
-    // The default middle modifier is weak (thus the false argument).
-    MurkyModifier murkyMod = new MurkyModifier(false);
-    murkyMod.setPatternInfo((input.affixProvider != null) ? input.affixProvider : patternInfo);
-    murkyMod.setPatternAttributes(micros.sign, perMille);
-    if (murkyMod.needsPlurals()) {
-      if (rules == null) {
-        // Lazily create PluralRules
-        rules = PluralRules.forLocale(input.loc);
-      }
-      murkyMod.setSymbols(micros.symbols, currency, unitWidth, rules);
-    } else {
-      murkyMod.setSymbols(micros.symbols, currency, unitWidth, null);
-    }
-    if (build) {
-      chain = murkyMod.createImmutableAndChain(chain);
-    } else {
-      chain = murkyMod.addToChain(chain);
-    }
-
-    // Outer modifier (CLDR units and currency long names)
-    if (outerMods != null) {
-      if (rules == null) {
-        // Lazily create PluralRules
-        rules = PluralRules.forLocale(input.loc);
-      }
-      chain = new QuantityDependentModOuter(outerMods, rules, chain);
-    } else {
-      // No outer modifier required
-      micros.modOuter = ConstantAffixModifier.EMPTY;
-    }
-
-    // Padding strategy
-    if (input.padding != null) {
-      micros.padding = (PaddingImpl) input.padding;
-    } else {
-      micros.padding = PaddingImpl.NONE;
-    }
-
-    // Compact notation
-    // NOTE: Compact notation can (but might not) override the middle modifier and rounding.
-    // It therefore needs to go at the end of the chain.
-    if (input.notation instanceof NotationCompact) {
-      assert input.notation instanceof NotationImpl.NotationCompactImpl;
-      if (rules == null) {
-        // Lazily create PluralRules
-        rules = PluralRules.forLocale(input.loc);
-      }
-      CompactStyle compactStyle = ((NotationImpl.NotationCompactImpl) input.notation).compactStyle;
-      if (compactStyle == null) {
-        // Use compact custom data
-        chain =
-            CompactImpl.getInstance(
-                ((NotationImpl.NotationCompactImpl) input.notation).compactCustomData,
-                rules,
-                build ? murkyMod : null,
-                chain);
-      } else {
-        CompactType compactType =
-            (input.unit instanceof Currency) ? CompactType.CURRENCY : CompactType.DECIMAL;
-        chain =
-            CompactImpl.getInstance(
-                input.loc, compactType, compactStyle, rules, build ? murkyMod : null, chain);
-      }
-    }
-
-    return chain;
-  }
-
-  //////////
-
-  private static int applyStatic(
-      MicroProps micros, FormatQuantity inValue, NumberStringBuilder outString) {
-    inValue.adjustMagnitude(micros.multiplier);
-    micros.rounding.apply(inValue);
-    inValue.setIntegerLength(micros.integerWidth.minInt, micros.integerWidth.maxInt);
-    int length = PositiveDecimalImpl.apply(micros, inValue, outString);
-    // NOTE: When range formatting is added, these modifiers can bubble up.
-    // For now, apply them all here at once.
-    length += micros.padding.applyModsAndMaybePad(micros, outString, 0, length);
-    return length;
-  }
-}
similarity index 71%
rename from icu4j/main/classes/core/src/newapi/demo.java
rename to icu4j/main/classes/core/src/newapi/impl/demo.java
index 6cc79fe1d0bdc782ad035d079cc0f4f4224fe347..3f8dcea922a531bacf93d08770c6e3ed059a67cf 100644 (file)
@@ -1,6 +1,6 @@
 // © 2017 and later: Unicode, Inc. and others.
 // License & terms of use: http://www.unicode.org/copyright.html#License
-package newapi;
+package newapi.impl;
 
 import java.math.RoundingMode;
 
@@ -13,22 +13,23 @@ import com.ibm.icu.util.Dimensionless;
 import com.ibm.icu.util.MeasureUnit;
 import com.ibm.icu.util.ULocale;
 
+import newapi.Grouper;
+import newapi.Notation;
+import newapi.NumberFormatter;
+import newapi.Rounder;
+import newapi.UnlocalizedNumberFormatter;
 import newapi.NumberFormatter.DecimalMarkDisplay;
-import newapi.NumberFormatter.Grouping;
-import newapi.NumberFormatter.Notation;
-import newapi.NumberFormatter.Rounding;
 import newapi.NumberFormatter.SignDisplay;
-import newapi.NumberFormatter.UnlocalizedNumberFormatter;
 
 public class demo {
   public static void main(String[] args) {
     System.out.println(NumberingSystem.LATIN.getDescription());
     UnlocalizedNumberFormatter formatter =
         NumberFormatter.with()
-            .notation(Notation.COMPACT_SHORT)
-            .notation(Notation.SCIENTIFIC.withExponentSignDisplay(SignDisplay.ALWAYS))
-            .notation(Notation.ENGINEERING.withMinExponentDigits(2))
-            .notation(Notation.SIMPLE)
+            .notation(Notation.compactShort())
+            .notation(Notation.scientific().withExponentSignDisplay(SignDisplay.ALWAYS))
+            .notation(Notation.engineering().withMinExponentDigits(2))
+            .notation(Notation.simple())
             .unit(Currency.getInstance("GBP"))
             .unit(Dimensionless.PERCENT)
             .unit(MeasureUnit.CUBIC_METER)
@@ -38,16 +39,16 @@ public class demo {
 //                (BigDecimal input) -> {
 //                  return input.divide(new BigDecimal("0.02"), 0).multiply(new BigDecimal("0.02"));
 //                })
-            .rounding(Rounding.fixedFraction(2).withMode(RoundingMode.HALF_UP))
-            .rounding(Rounding.INTEGER.withMode(RoundingMode.CEILING))
-            .rounding(Rounding.currency(CurrencyUsage.STANDARD))
+            .rounding(Rounder.fixedFraction(2).withMode(RoundingMode.HALF_UP))
+            .rounding(Rounder.integer().withMode(RoundingMode.CEILING))
+            .rounding(Rounder.currency(CurrencyUsage.STANDARD))
 //            .grouping(
 //                (int position, BigDecimal number) -> {
 //                  return (position % 3) == 0;
 //                })
-            .grouping(Grouping.DEFAULT)
-            .grouping(Grouping.NONE)
-            .grouping(Grouping.MIN_2_DIGITS)
+            .grouping(Grouper.defaults())
+            .grouping(Grouper.none())
+            .grouping(Grouper.min2())
             // .padding(Padding.codePoints(' ', 8, PadPosition.AFTER_PREFIX))
             .sign(SignDisplay.ALWAYS)
             .decimal(DecimalMarkDisplay.ALWAYS)
index 1c8dd8277ed0f7b8f81882fc28d80fbe53f1e9d6..3374fbf498eb5f5cedf42912d8233b6f9f7b37a0 100644 (file)
@@ -18,10 +18,8 @@ import com.ibm.icu.text.DecimalFormat_ICU58;
 import com.ibm.icu.util.CurrencyAmount;
 import com.ibm.icu.util.ULocale;
 
-import newapi.NumberFormatter.LocalizedNumberFormatter;
-import newapi.impl.MacroProps;
-import newapi.impl.NumberFormatterImpl;
-import newapi.impl.NumberPropertyMapper;
+import newapi.LocalizedNumberFormatter;
+import newapi.NumberPropertyMapper;
 
 public class NumberFormatDataDrivenTest {
 
@@ -557,8 +555,7 @@ public class NumberFormatDataDrivenTest {
                       : PatternString.IGNORE_ROUNDING_NEVER);
           propertiesFromTuple(tuple, properties);
           DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(locale);
-          MacroProps macros = NumberPropertyMapper.oldToNew(properties, symbols, null);
-          LocalizedNumberFormatter fmt = NumberFormatterImpl.fromMacros(macros).locale(locale);
+          LocalizedNumberFormatter fmt = NumberPropertyMapper.create(properties, symbols).locale(locale);
           Number number = toNumber(tuple.format);
           String expected = tuple.output;
           String actual = fmt.format(number).toString();
index c92905b261f6378371ed6287118c984207f41887..6d1b361750446c898b77d4f3fffdf0d126023278 100644 (file)
@@ -20,10 +20,11 @@ import com.ibm.icu.impl.number.FormatQuantity3;
 import com.ibm.icu.impl.number.FormatQuantity4;
 import com.ibm.icu.impl.number.Properties;
 import com.ibm.icu.text.CompactDecimalFormat.CompactStyle;
+import com.ibm.icu.text.DecimalFormatSymbols;
 import com.ibm.icu.util.ULocale;
 
-import newapi.impl.NumberFormatterImpl;
-import newapi.impl.NumberPropertyMapper;
+import newapi.LocalizedNumberFormatter;
+import newapi.NumberPropertyMapper;
 
 /** TODO: This is a temporary name for this class. Suggestions for a better name? */
 public class FormatQuantityTest extends TestFmwk {
@@ -32,27 +33,29 @@ public class FormatQuantityTest extends TestFmwk {
   public void testBehavior() throws ParseException {
 
     // Make a list of several formatters to test the behavior of FormatQuantity.
-    List<NumberFormatterImpl> formats = new ArrayList<NumberFormatterImpl>();
+    List<LocalizedNumberFormatter> formats = new ArrayList<LocalizedNumberFormatter>();
+
+    DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(ULocale.ENGLISH);
 
     Properties properties = new Properties();
-    formats.add(NumberPropertyMapper.create(properties, null, ULocale.ENGLISH));
+    formats.add(NumberPropertyMapper.create(properties, symbols).locale(ULocale.ENGLISH));
 
     properties =
         new Properties()
             .setMinimumSignificantDigits(3)
             .setMaximumSignificantDigits(3)
             .setCompactStyle(CompactStyle.LONG);
-    formats.add(NumberPropertyMapper.create(properties, null, ULocale.ENGLISH));
+    formats.add(NumberPropertyMapper.create(properties, symbols).locale(ULocale.ENGLISH));
 
     properties =
         new Properties()
             .setMinimumExponentDigits(1)
             .setMaximumIntegerDigits(3)
             .setMaximumFractionDigits(1);
-    formats.add(NumberPropertyMapper.create(properties, null, ULocale.ENGLISH));
+    formats.add(NumberPropertyMapper.create(properties, symbols).locale(ULocale.ENGLISH));
 
     properties = new Properties().setRoundingIncrement(new BigDecimal("0.5"));
-    formats.add(NumberPropertyMapper.create(properties, null, ULocale.ENGLISH));
+    formats.add(NumberPropertyMapper.create(properties, symbols).locale(ULocale.ENGLISH));
 
     String[] cases = {
       "1.0",
@@ -110,7 +113,7 @@ public class FormatQuantityTest extends TestFmwk {
     }
   }
 
-  static void testFormatQuantity(int t, String str, List<NumberFormatterImpl> formats, int mode) {
+  static void testFormatQuantity(int t, String str, List<LocalizedNumberFormatter> formats, int mode) {
     if (mode == 2) {
       assertEquals("Double is not valid", Double.toString(Double.parseDouble(str)), str);
     }
@@ -228,8 +231,8 @@ public class FormatQuantityTest extends TestFmwk {
   }
 
   private static void testFormatQuantityWithFormats(
-      FormatQuantity rq0, FormatQuantity rq1, List<NumberFormatterImpl> formats) {
-    for (NumberFormatterImpl format : formats) {
+      FormatQuantity rq0, FormatQuantity rq1, List<LocalizedNumberFormatter> formats) {
+    for (LocalizedNumberFormatter format : formats) {
       FormatQuantity q0 = rq0.createCopy();
       FormatQuantity q1 = rq1.createCopy();
       String s1 = format.format(q0).toString();
index d544e6fb0b17eb7cfef3af15487732ba3d82f41e..f9cfe3f90760eff53b11f984e87964532234bd51 100644 (file)
@@ -12,8 +12,8 @@ import com.ibm.icu.text.MeasureFormat.FormatWidth;
 import com.ibm.icu.util.Currency;
 import com.ibm.icu.util.ULocale;
 
+import newapi.MurkyModifier;
 import newapi.NumberFormatter.SignDisplay;
-import newapi.impl.MurkyModifier;
 
 public class MurkyModifierTest {
 
index 89cb4ac64976fdda0f5d10450115ce70e507b992..5ba0e95756df03d750ed81c483a46ff2ae459520 100644 (file)
@@ -6,7 +6,6 @@ import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 
 import java.math.BigDecimal;
-import java.math.RoundingMode;
 import java.util.Locale;
 
 import org.junit.Ignore;
@@ -24,19 +23,18 @@ import com.ibm.icu.util.Measure;
 import com.ibm.icu.util.MeasureUnit;
 import com.ibm.icu.util.ULocale;
 
+import newapi.FormattedNumber;
+import newapi.Grouper;
+import newapi.IntegerWidth;
+import newapi.LocalizedNumberFormatter;
+import newapi.Notation;
 import newapi.NumberFormatter;
 import newapi.NumberFormatter.DecimalMarkDisplay;
-import newapi.NumberFormatter.Grouping;
-import newapi.NumberFormatter.IRounding;
-import newapi.NumberFormatter.IntegerWidth;
-import newapi.NumberFormatter.LocalizedNumberFormatter;
-import newapi.NumberFormatter.Notation;
-import newapi.NumberFormatter.NumberFormatterResult;
-import newapi.NumberFormatter.Padding;
-import newapi.NumberFormatter.Rounding;
 import newapi.NumberFormatter.SignDisplay;
-import newapi.NumberFormatter.UnlocalizedNumberFormatter;
-import newapi.impl.NumberFormatterImpl;
+import newapi.impl.Padder;
+import newapi.NumberPropertyMapper;
+import newapi.Rounder;
+import newapi.UnlocalizedNumberFormatter;
 
 public class NumberFormatterTest {
 
@@ -75,7 +73,7 @@ public class NumberFormatterTest {
     assertFormatDescending(
         "Scientific",
         "E",
-        NumberFormatter.with().notation(Notation.SCIENTIFIC),
+        NumberFormatter.with().notation(Notation.scientific()),
         ULocale.ENGLISH,
         "8.765E4",
         "8.765E3",
@@ -90,7 +88,7 @@ public class NumberFormatterTest {
     assertFormatDescending(
         "Engineering",
         "E3",
-        NumberFormatter.with().notation(Notation.ENGINEERING),
+        NumberFormatter.with().notation(Notation.engineering()),
         ULocale.ENGLISH,
         "87.65E3",
         "8.765E3",
@@ -106,7 +104,7 @@ public class NumberFormatterTest {
         "Scientific sign always shown",
         "E+",
         NumberFormatter.with()
-            .notation(Notation.SCIENTIFIC.withExponentSignDisplay(SignDisplay.ALWAYS)),
+            .notation(Notation.scientific().withExponentSignDisplay(SignDisplay.ALWAYS)),
         ULocale.ENGLISH,
         "8.765E+4",
         "8.765E+3",
@@ -121,7 +119,7 @@ public class NumberFormatterTest {
     assertFormatDescending(
         "Scientific min exponent digits",
         "E00",
-        NumberFormatter.with().notation(Notation.SCIENTIFIC.withMinExponentDigits(2)),
+        NumberFormatter.with().notation(Notation.scientific().withMinExponentDigits(2)),
         ULocale.ENGLISH,
         "8.765E04",
         "8.765E03",
@@ -136,7 +134,7 @@ public class NumberFormatterTest {
     assertFormatSingle(
         "Scientific Negative",
         "E",
-        NumberFormatter.with().notation(Notation.SCIENTIFIC),
+        NumberFormatter.with().notation(Notation.scientific()),
         ULocale.ENGLISH,
         -1000000,
         "-1E6");
@@ -147,7 +145,7 @@ public class NumberFormatterTest {
     assertFormatDescending(
         "Compact Short",
         "C",
-        NumberFormatter.with().notation(Notation.COMPACT_SHORT),
+        NumberFormatter.with().notation(Notation.compactShort()),
         ULocale.ENGLISH,
         "88K",
         "8.8K",
@@ -162,7 +160,7 @@ public class NumberFormatterTest {
     assertFormatDescending(
         "Compact Long",
         "CC",
-        NumberFormatter.with().notation(Notation.COMPACT_LONG),
+        NumberFormatter.with().notation(Notation.compactLong()),
         ULocale.ENGLISH,
         "88 thousand",
         "8.8 thousand",
@@ -177,7 +175,7 @@ public class NumberFormatterTest {
     assertFormatDescending(
         "Compact Short Currency",
         "C $USD",
-        NumberFormatter.with().notation(Notation.COMPACT_SHORT).unit(USD),
+        NumberFormatter.with().notation(Notation.compactShort()).unit(USD),
         ULocale.ENGLISH,
         "$88K",
         "$8.8K",
@@ -193,7 +191,7 @@ public class NumberFormatterTest {
     assertFormatDescending(
         "Compact Long Currency",
         "CC $USD",
-        NumberFormatter.with().notation(Notation.COMPACT_LONG).unit(USD),
+        NumberFormatter.with().notation(Notation.compactLong()).unit(USD),
         ULocale.ENGLISH,
         "$88K",
         "$8.8K",
@@ -208,7 +206,7 @@ public class NumberFormatterTest {
     assertFormatSingle(
         "Compact Plural One",
         "CC",
-        NumberFormatter.with().notation(Notation.COMPACT_LONG),
+        NumberFormatter.with().notation(Notation.compactLong()),
         ULocale.forLanguageTag("es"),
         1000000,
         "1 millón");
@@ -216,7 +214,7 @@ public class NumberFormatterTest {
     assertFormatSingle(
         "Compact Plural Other",
         "CC",
-        NumberFormatter.with().notation(Notation.COMPACT_LONG),
+        NumberFormatter.with().notation(Notation.compactLong()),
         ULocale.forLanguageTag("es"),
         2000000,
         "2 millones");
@@ -224,7 +222,7 @@ public class NumberFormatterTest {
     assertFormatSingle(
         "Compact with Negative Sign",
         "C",
-        NumberFormatter.with().notation(Notation.COMPACT_SHORT),
+        NumberFormatter.with().notation(Notation.compactShort()),
         ULocale.ENGLISH,
         -9876543.21,
         "-9.9M");
@@ -266,7 +264,7 @@ public class NumberFormatterTest {
         "Compact Meters Long",
         "CC U:length:meter unit-width=WIDE",
         NumberFormatter.with()
-            .notation(Notation.COMPACT_LONG)
+            .notation(Notation.compactLong())
             .unit(MeasureUnit.METER)
             .unitWidth(FormatWidth.WIDE),
         ULocale.ENGLISH,
@@ -354,8 +352,8 @@ public class NumberFormatterTest {
 
     assertFormatSingle(
         "Currency Long Name from Pattern Syntax",
-        "$GBP F0 grouping=NONE integer-width=1- symbols=loc:en_US sign=AUTO decimal=AUTO",
-        NumberFormatterImpl.fromPattern("0 ¤¤¤", DecimalFormatSymbols.getInstance()).unit(GBP),
+        "$GBP F0 grouping=none integer-width=1- symbols=loc:en_US sign=AUTO decimal=AUTO",
+        NumberPropertyMapper.create("0 ¤¤¤", DecimalFormatSymbols.getInstance()).unit(GBP),
         ULocale.ENGLISH,
         1234567.89,
         "1234568 British pounds");
@@ -415,7 +413,7 @@ public class NumberFormatterTest {
     assertFormatDescending(
         "Integer",
         "F0",
-        NumberFormatter.with().rounding(Rounding.INTEGER),
+        NumberFormatter.with().rounding(Rounder.integer()),
         ULocale.ENGLISH,
         "87,650",
         "8,765",
@@ -430,7 +428,7 @@ public class NumberFormatterTest {
     assertFormatDescending(
         "Fixed Fraction",
         "F3",
-        NumberFormatter.with().rounding(Rounding.fixedFraction(3)),
+        NumberFormatter.with().rounding(Rounder.fixedFraction(3)),
         ULocale.ENGLISH,
         "87,650.000",
         "8,765.000",
@@ -445,7 +443,7 @@ public class NumberFormatterTest {
     assertFormatDescending(
         "Min Fraction",
         "F1-",
-        NumberFormatter.with().rounding(Rounding.minFraction(1)),
+        NumberFormatter.with().rounding(Rounder.minFraction(1)),
         ULocale.ENGLISH,
         "87,650.0",
         "8,765.0",
@@ -460,7 +458,7 @@ public class NumberFormatterTest {
     assertFormatDescending(
         "Max Fraction",
         "F-1",
-        NumberFormatter.with().rounding(Rounding.maxFraction(1)),
+        NumberFormatter.with().rounding(Rounder.maxFraction(1)),
         ULocale.ENGLISH,
         "87,650",
         "8,765",
@@ -475,7 +473,7 @@ public class NumberFormatterTest {
     assertFormatDescending(
         "Min/Max Fraction",
         "F1-3",
-        NumberFormatter.with().rounding(Rounding.minMaxFraction(1, 3)),
+        NumberFormatter.with().rounding(Rounder.minMaxFraction(1, 3)),
         ULocale.ENGLISH,
         "87,650.0",
         "8,765.0",
@@ -493,7 +491,7 @@ public class NumberFormatterTest {
     assertFormatSingle(
         "Fixed Significant",
         "S3",
-        NumberFormatter.with().rounding(Rounding.fixedFigures(3)),
+        NumberFormatter.with().rounding(Rounder.fixedFigures(3)),
         ULocale.ENGLISH,
         -98,
         "-98.0");
@@ -501,7 +499,7 @@ public class NumberFormatterTest {
     assertFormatSingle(
         "Fixed Significant Rounding",
         "S3",
-        NumberFormatter.with().rounding(Rounding.fixedFigures(3)),
+        NumberFormatter.with().rounding(Rounder.fixedFigures(3)),
         ULocale.ENGLISH,
         -98.7654321,
         "-98.8");
@@ -509,7 +507,7 @@ public class NumberFormatterTest {
     assertFormatSingle(
         "Fixed Significant Zero",
         "S3",
-        NumberFormatter.with().rounding(Rounding.fixedFigures(3)),
+        NumberFormatter.with().rounding(Rounder.fixedFigures(3)),
         ULocale.ENGLISH,
         0,
         "0.00");
@@ -517,7 +515,7 @@ public class NumberFormatterTest {
     assertFormatSingle(
         "Min Significant",
         "S2-",
-        NumberFormatter.with().rounding(Rounding.minFigures(2)),
+        NumberFormatter.with().rounding(Rounder.minFigures(2)),
         ULocale.ENGLISH,
         -9,
         "-9.0");
@@ -525,7 +523,7 @@ public class NumberFormatterTest {
     assertFormatSingle(
         "Max Significant",
         "S-4",
-        NumberFormatter.with().rounding(Rounding.maxFigures(4)),
+        NumberFormatter.with().rounding(Rounder.maxFigures(4)),
         ULocale.ENGLISH,
         98.7654321,
         "98.77");
@@ -533,7 +531,7 @@ public class NumberFormatterTest {
     assertFormatSingle(
         "Min/Max Significant",
         "S3-4",
-        NumberFormatter.with().rounding(Rounding.minMaxFigures(3, 4)),
+        NumberFormatter.with().rounding(Rounder.minMaxFigures(3, 4)),
         ULocale.ENGLISH,
         9.99999,
         "10.0");
@@ -544,7 +542,7 @@ public class NumberFormatterTest {
     assertFormatDescending(
         "Basic Significant", // for comparison
         "S-2",
-        NumberFormatter.with().rounding(Rounding.maxFigures(2)),
+        NumberFormatter.with().rounding(Rounder.maxFigures(2)),
         ULocale.ENGLISH,
         "88,000",
         "8,800",
@@ -559,7 +557,7 @@ public class NumberFormatterTest {
     assertFormatDescending(
         "FracSig minMaxFrac minSig",
         "F1-2>3",
-        NumberFormatter.with().rounding(Rounding.minMaxFraction(1, 2).withMinFigures(3)),
+        NumberFormatter.with().rounding(Rounder.minMaxFraction(1, 2).withMinFigures(3)),
         ULocale.ENGLISH,
         "87,650.0",
         "8,765.0",
@@ -574,7 +572,7 @@ public class NumberFormatterTest {
     assertFormatDescending(
         "FracSig minMaxFrac maxSig A",
         "F1-3<2",
-        NumberFormatter.with().rounding(Rounding.minMaxFraction(1, 3).withMaxFigures(2)),
+        NumberFormatter.with().rounding(Rounder.minMaxFraction(1, 3).withMaxFigures(2)),
         ULocale.ENGLISH,
         "88,000.0", // maxSig beats maxFrac
         "8,800.0", // maxSig beats maxFrac
@@ -589,7 +587,7 @@ public class NumberFormatterTest {
     assertFormatDescending(
         "FracSig minMaxFrac maxSig B",
         "F2<2",
-        NumberFormatter.with().rounding(Rounding.fixedFraction(2).withMaxFigures(2)),
+        NumberFormatter.with().rounding(Rounder.fixedFraction(2).withMaxFigures(2)),
         ULocale.ENGLISH,
         "88,000.00", // maxSig beats maxFrac
         "8,800.00", // maxSig beats maxFrac
@@ -607,7 +605,7 @@ public class NumberFormatterTest {
     assertFormatDescending(
         "Rounding None",
         "Y",
-        NumberFormatter.with().rounding(Rounding.NONE),
+        NumberFormatter.with().rounding(Rounder.none()),
         ULocale.ENGLISH,
         "87,650",
         "8,765",
@@ -622,7 +620,7 @@ public class NumberFormatterTest {
     assertFormatDescending(
         "Increment",
         "M0.5",
-        NumberFormatter.with().rounding(Rounding.increment(BigDecimal.valueOf(0.5))),
+        NumberFormatter.with().rounding(Rounder.increment(BigDecimal.valueOf(0.5))),
         ULocale.ENGLISH,
         "87,650.0",
         "8,765.0",
@@ -637,7 +635,7 @@ public class NumberFormatterTest {
     assertFormatDescending(
         "Currency Standard",
         "$CZK GSTANDARD",
-        NumberFormatter.with().rounding(Rounding.currency(CurrencyUsage.STANDARD)).unit(CZK),
+        NumberFormatter.with().rounding(Rounder.currency(CurrencyUsage.STANDARD)).unit(CZK),
         ULocale.ENGLISH,
         "CZK 87,650.00",
         "CZK 8,765.00",
@@ -652,7 +650,7 @@ public class NumberFormatterTest {
     assertFormatDescending(
         "Currency Cash",
         "$CZK GCASH",
-        NumberFormatter.with().rounding(Rounding.currency(CurrencyUsage.CASH)).unit(CZK),
+        NumberFormatter.with().rounding(Rounder.currency(CurrencyUsage.CASH)).unit(CZK),
         ULocale.ENGLISH,
         "CZK 87,650",
         "CZK 8,765",
@@ -667,7 +665,7 @@ public class NumberFormatterTest {
     assertFormatDescending(
         "Currency not in top-level fluent chain",
         "F0",
-        NumberFormatter.with().rounding(Rounding.currency(CurrencyUsage.CASH).withCurrency(CZK)),
+        NumberFormatter.with().rounding(Rounder.currency(CurrencyUsage.CASH).withCurrency(CZK)),
         ULocale.ENGLISH,
         "87,650",
         "8,765",
@@ -678,28 +676,6 @@ public class NumberFormatterTest {
         "0",
         "0",
         "0");
-
-    assertFormatDescending(
-        "Lambda Function",
-        "",
-        NumberFormatter.with()
-            .rounding(
-                new IRounding() {
-                  @Override
-                  public BigDecimal round(BigDecimal input) {
-                    return input.setScale(2, RoundingMode.FLOOR);
-                  }
-                }),
-        ULocale.ENGLISH,
-        "87,650.00",
-        "8,765.00",
-        "876.50",
-        "87.65",
-        "8.76",
-        "0.87",
-        "0.08",
-        "0.00",
-        "0.00");
   }
 
   @Test
@@ -708,8 +684,8 @@ public class NumberFormatterTest {
     // Note that en-US is already performed in the unitPercent() function.
     assertFormatDescending(
         "Indic Grouping",
-        "%% grouping=DEFAULT",
-        NumberFormatter.with().unit(Dimensionless.PERMILLE).grouping(Grouping.DEFAULT),
+        "%% grouping=defaults",
+        NumberFormatter.with().unit(Dimensionless.PERMILLE).grouping(Grouper.defaults()),
         new ULocale("en-IN"),
         "8,76,50,000‰",
         "87,65,000‰",
@@ -723,8 +699,8 @@ public class NumberFormatterTest {
 
     assertFormatDescending(
         "Western Grouping, Min 2",
-        "%% grouping=DEFAULT_MIN_2_DIGITS",
-        NumberFormatter.with().unit(Dimensionless.PERMILLE).grouping(Grouping.MIN_2_DIGITS),
+        "%% grouping=min2",
+        NumberFormatter.with().unit(Dimensionless.PERMILLE).grouping(Grouper.min2()),
         ULocale.ENGLISH,
         "87,650,000‰",
         "8,765,000‰",
@@ -738,8 +714,8 @@ public class NumberFormatterTest {
 
     assertFormatDescending(
         "Indic Grouping, Min 2",
-        "%% grouping=DEFAULT_MIN_2_DIGITS",
-        NumberFormatter.with().unit(Dimensionless.PERMILLE).grouping(Grouping.MIN_2_DIGITS),
+        "%% grouping=min2",
+        NumberFormatter.with().unit(Dimensionless.PERMILLE).grouping(Grouper.min2()),
         new ULocale("en-IN"),
         "8,76,50,000‰",
         "87,65,000‰",
@@ -753,8 +729,8 @@ public class NumberFormatterTest {
 
     assertFormatDescending(
         "No Grouping",
-        "%% grouping=NONE",
-        NumberFormatter.with().unit(Dimensionless.PERMILLE).grouping(Grouping.NONE),
+        "%% grouping=none",
+        NumberFormatter.with().unit(Dimensionless.PERMILLE).grouping(Grouper.none()),
         new ULocale("en-IN"),
         "87650000‰",
         "8765000‰",
@@ -771,8 +747,8 @@ public class NumberFormatterTest {
   public void padding() {
     assertFormatDescending(
         "Padding",
-        "padding=NONE",
-        NumberFormatter.with().padding(Padding.NONE),
+        "",
+        NumberFormatter.with().padding(Padder.none()),
         ULocale.ENGLISH,
         "87,650",
         "8,765",
@@ -786,8 +762,8 @@ public class NumberFormatterTest {
 
     assertFormatDescending(
         "Padding",
-        "padding=8:AFTER_PREFIX:*",
-        NumberFormatter.with().padding(Padding.codePoints('*', 8, PadPosition.AFTER_PREFIX)),
+        "",
+        NumberFormatter.with().padding(Padder.codePoints('*', 8, PadPosition.AFTER_PREFIX)),
         ULocale.ENGLISH,
         "**87,650",
         "***8,765",
@@ -801,8 +777,8 @@ public class NumberFormatterTest {
 
     assertFormatDescending(
         "Padding with code points",
-        "padding=8:AFTER_PREFIX:𐇤",
-        NumberFormatter.with().padding(Padding.codePoints(0x101E4, 8, PadPosition.AFTER_PREFIX)),
+        "",
+        NumberFormatter.with().padding(Padder.codePoints(0x101E4, 8, PadPosition.AFTER_PREFIX)),
         ULocale.ENGLISH,
         "𐇤𐇤87,650",
         "𐇤𐇤𐇤8,765",
@@ -816,9 +792,9 @@ public class NumberFormatterTest {
 
     assertFormatDescending(
         "Padding with wide digits",
-        "padding=8:AFTER_PREFIX:* symbols=ns:mathsanb",
+        "symbols=ns:mathsanb",
         NumberFormatter.with()
-            .padding(Padding.codePoints('*', 8, PadPosition.AFTER_PREFIX))
+            .padding(Padder.codePoints('*', 8, PadPosition.AFTER_PREFIX))
             .symbols(NumberingSystem.getInstanceByName("mathsanb")),
         ULocale.ENGLISH,
         "**𝟴𝟳,𝟲𝟱𝟬",
@@ -833,9 +809,9 @@ public class NumberFormatterTest {
 
     assertFormatDescending(
         "Padding with currency spacing",
-        "$GBP padding=10:AFTER_PREFIX:* unit-width=SHORT",
+        "$GBP unit-width=SHORT",
         NumberFormatter.with()
-            .padding(Padding.codePoints('*', 10, PadPosition.AFTER_PREFIX))
+            .padding(Padder.codePoints('*', 10, PadPosition.AFTER_PREFIX))
             .unit(GBP)
             .unitWidth(FormatWidth.SHORT),
         ULocale.ENGLISH,
@@ -851,25 +827,25 @@ public class NumberFormatterTest {
 
     assertFormatSingle(
         "Pad Before Prefix",
-        "padding=8:BEFORE_PREFIX:*",
-        NumberFormatter.with().padding(Padding.codePoints('*', 8, PadPosition.BEFORE_PREFIX)),
+        "",
+        NumberFormatter.with().padding(Padder.codePoints('*', 8, PadPosition.BEFORE_PREFIX)),
         ULocale.ENGLISH,
         -88.88,
         "**-88.88");
 
     assertFormatSingle(
         "Pad After Prefix",
-        "padding=8:AFTER_PREFIX:*",
-        NumberFormatter.with().padding(Padding.codePoints('*', 8, PadPosition.AFTER_PREFIX)),
+        "",
+        NumberFormatter.with().padding(Padder.codePoints('*', 8, PadPosition.AFTER_PREFIX)),
         ULocale.ENGLISH,
         -88.88,
         "-**88.88");
 
     assertFormatSingle(
         "Pad Before Suffix",
-        "% padding=8:BEFORE_SUFFIX:*",
+        "%",
         NumberFormatter.with()
-            .padding(Padding.codePoints('*', 8, PadPosition.BEFORE_SUFFIX))
+            .padding(Padder.codePoints('*', 8, PadPosition.BEFORE_SUFFIX))
             .unit(Dimensionless.PERCENT),
         ULocale.ENGLISH,
         0.8888,
@@ -877,9 +853,9 @@ public class NumberFormatterTest {
 
     assertFormatSingle(
         "Pad After Suffix",
-        "% padding=8:AFTER_SUFFIX:*",
+        "%",
         NumberFormatter.with()
-            .padding(Padding.codePoints('*', 8, PadPosition.AFTER_SUFFIX))
+            .padding(Padder.codePoints('*', 8, PadPosition.AFTER_SUFFIX))
             .unit(Dimensionless.PERCENT),
         ULocale.ENGLISH,
         0.8888,
@@ -891,7 +867,7 @@ public class NumberFormatterTest {
     assertFormatDescending(
         "Integer Width Default",
         "integer-width=1-",
-        NumberFormatter.with().integerWidth(IntegerWidth.DEFAULT),
+        NumberFormatter.with().integerWidth(IntegerWidth.zeroFillTo(1)),
         ULocale.ENGLISH,
         "87,650",
         "8,765",
@@ -936,7 +912,7 @@ public class NumberFormatterTest {
     assertFormatDescending(
         "Integer Width Max 3",
         "integer-width=1-3",
-        NumberFormatter.with().integerWidth(IntegerWidth.DEFAULT.truncateAt(3)),
+        NumberFormatter.with().integerWidth(IntegerWidth.zeroFillTo(1).truncateAt(3)),
         ULocale.ENGLISH,
         "650",
         "765",
@@ -985,7 +961,7 @@ public class NumberFormatterTest {
         "French Symbols with Japanese Data 2",
         "C symbols=loc:fr",
         NumberFormatter.with()
-            .notation(Notation.COMPACT_SHORT)
+            .notation(Notation.compactShort())
             .symbols(DecimalFormatSymbols.getInstance(ULocale.FRENCH)),
         ULocale.JAPAN,
         12345,
@@ -1157,8 +1133,8 @@ public class NumberFormatterTest {
       String posSuffix = (String) cas[2];
       String negPrefix = (String) cas[3];
       String negSuffix = (String) cas[4];
-      NumberFormatterResult positive = f.format(1);
-      NumberFormatterResult negative = f.format(-1);
+      FormattedNumber positive = f.format(1);
+      FormattedNumber negative = f.format(-1);
       assertEquals(posPrefix, positive.getPrefix());
       assertEquals(posSuffix, positive.getSuffix());
       assertEquals(negPrefix, negative.getPrefix());
@@ -1176,7 +1152,7 @@ public class NumberFormatterTest {
         NumberFormatter.with()
             .unit(USD)
             .unitWidth(FormatWidth.WIDE)
-            .rounding(Rounding.fixedFraction(0)),
+            .rounding(Rounder.fixedFraction(0)),
         ULocale.ENGLISH,
         1,
         "1 US dollar");
@@ -1187,7 +1163,7 @@ public class NumberFormatterTest {
         NumberFormatter.with()
             .unit(USD)
             .unitWidth(FormatWidth.WIDE)
-            .rounding(Rounding.fixedFraction(2)),
+            .rounding(Rounder.fixedFraction(2)),
         ULocale.ENGLISH,
         1,
         "1.00 US dollars");
@@ -1203,8 +1179,8 @@ public class NumberFormatterTest {
     assertEquals(message + ": Skeleton:", skeleton, f.toSkeleton());
     final double[] inputs =
         new double[] {87650, 8765, 876.5, 87.65, 8.765, 0.8765, 0.08765, 0.008765, 0};
-    NumberFormatterImpl l1 = ((NumberFormatterImpl) f).threshold(0L).locale(locale); // no self-regulation
-    NumberFormatterImpl l2 = ((NumberFormatterImpl) f).threshold(1L).locale(locale); // all self-regulation
+    LocalizedNumberFormatter l1 = f.threshold(0L).locale(locale); // no self-regulation
+    LocalizedNumberFormatter l2 = f.threshold(1L).locale(locale); // all self-regulation
     for (int i = 0; i < 9; i++) {
       double d = inputs[i];
       String actual1 = l1.format(d).toString();
@@ -1222,8 +1198,8 @@ public class NumberFormatterTest {
       Number input,
       String expected) {
     assertEquals(message + ": Skeleton:", skeleton, f.toSkeleton());
-    NumberFormatterImpl l1 = ((NumberFormatterImpl) f).threshold(0L).locale(locale); // no self-regulation
-    NumberFormatterImpl l2 = ((NumberFormatterImpl) f).threshold(1L).locale(locale); // all self-regulation
+    LocalizedNumberFormatter l1 = f.threshold(0L).locale(locale); // no self-regulation
+    LocalizedNumberFormatter l2 = f.threshold(1L).locale(locale); // all self-regulation
     String actual1 = l1.format(input).toString();
     assertEquals(message + ": L1: " + input, expected, actual1);
     String actual2 = l2.format(input).toString();
@@ -1238,8 +1214,8 @@ public class NumberFormatterTest {
       Measure input,
       String expected) {
     assertEquals(message + ": Skeleton:", skeleton, f.toSkeleton());
-    NumberFormatterImpl l1 = ((NumberFormatterImpl) f).threshold(0L).locale(locale); // no self-regulation
-    NumberFormatterImpl l2 = ((NumberFormatterImpl) f).threshold(1L).locale(locale); // all self-regulation
+    LocalizedNumberFormatter l1 = f.threshold(0L).locale(locale); // no self-regulation
+    LocalizedNumberFormatter l2 = f.threshold(1L).locale(locale); // all self-regulation
     String actual1 = l1.format(input).toString();
     assertEquals(message + ": L1: " + input, expected, actual1);
     String actual2 = l2.format(input).toString();