]> granicus.if.org Git - icu/commitdiff
ICU-13177 Assorted cleanup and minor changes. Preparation for C++.
authorShane Carr <shane@unicode.org>
Thu, 24 Aug 2017 06:03:12 +0000 (06:03 +0000)
committerShane Carr <shane@unicode.org>
Thu, 24 Aug 2017 06:03:12 +0000 (06:03 +0000)
X-SVN-Rev: 40351

25 files changed:
icu4j/main/classes/core/src/com/ibm/icu/impl/number/NumberStringBuilder.java
icu4j/main/classes/core/src/newapi/NumberFormatter.java
icu4j/main/classes/core/src/newapi/demo.java
icu4j/main/classes/core/src/newapi/impl/CompactImpl.java
icu4j/main/classes/core/src/newapi/impl/GroupingImpl.java
icu4j/main/classes/core/src/newapi/impl/IntegerWidthImpl.java
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/MultiplierImpl.java
icu4j/main/classes/core/src/newapi/impl/MurkyModifier.java
icu4j/main/classes/core/src/newapi/impl/NotationImpl.java
icu4j/main/classes/core/src/newapi/impl/NumberFormatterImpl.java
icu4j/main/classes/core/src/newapi/impl/NumberPropertyMapper.java
icu4j/main/classes/core/src/newapi/impl/PositiveDecimalImpl.java
icu4j/main/classes/core/src/newapi/impl/QuantityChain.java
icu4j/main/classes/core/src/newapi/impl/QuantityDependentModOuter.java
icu4j/main/classes/core/src/newapi/impl/ScientificImpl.java
icu4j/main/classes/core/src/newapi/impl/SkeletonBuilder.java
icu4j/main/classes/core/src/newapi/impl/Worker1.java
icu4j/main/tests/core/.classpath
icu4j/main/tests/core/src/com/ibm/icu/dev/data/numberformattestspecification.txt
icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatDataDrivenTest.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
icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberStringBuilderTest.java

index ad4a52f6be6a19efbd1c4a39fd3757c02bc1380e..2d4878fb5e65fab08d76b6869e19a65f88edb99f 100644 (file)
@@ -44,15 +44,14 @@ public class NumberStringBuilder implements CharSequence {
   }
 
   public NumberStringBuilder(NumberStringBuilder source) {
-    this(source.chars.length);
     copyFrom(source);
   }
 
   public void copyFrom(NumberStringBuilder source) {
+    chars = Arrays.copyOf(source.chars, source.chars.length);
+    fields = Arrays.copyOf(source.fields, source.fields.length);
     zero = source.zero;
     length = source.length;
-    System.arraycopy(source.chars, zero, chars, zero, length);
-    System.arraycopy(source.fields, zero, fields, zero, length);
   }
 
   @Override
@@ -66,19 +65,23 @@ public class NumberStringBuilder implements CharSequence {
 
   @Override
   public char charAt(int index) {
-    if (index < 0 || index > length) {
-      throw new IndexOutOfBoundsException();
-    }
+    assert index >= 0;
+    assert index < length;
     return chars[zero + index];
   }
 
   public Field fieldAt(int index) {
-    if (index < 0 || index > length) {
-      throw new IndexOutOfBoundsException();
-    }
+    assert index >= 0;
+    assert index < length;
     return fields[zero + index];
   }
 
+  public NumberStringBuilder clear() {
+    zero = chars.length / 2;
+    length = 0;
+    return this;
+  }
+
   /**
    * Appends the specified codePoint to the end of the string.
    *
@@ -196,11 +199,14 @@ public class NumberStringBuilder implements CharSequence {
       throw new IllegalArgumentException("Cannot call insert/append on myself");
     }
     int count = other.length;
-    if (count == 0) return 0; // nothing to insert
+    if (count == 0) {
+      // Nothing to insert.
+      return 0;
+    }
     int position = prepareForInsert(index, count);
     for (int i = 0; i < count; i++) {
-      this.chars[position + i] = other.chars[other.zero + i];
-      this.fields[position + i] = other.fields[other.zero + i];
+      this.chars[position + i] = other.charAt(i);
+      this.fields[position + i] = other.fieldAt(i);
     }
     return count;
   }
@@ -229,25 +235,43 @@ public class NumberStringBuilder implements CharSequence {
   }
 
   private int prepareForInsertHelper(int index, int count) {
-    // Keeping this code out of prepareForInsert() increases the speed of append operations.
-    if (length + count > chars.length) {
-      char[] newChars = new char[(length + count) * 2];
-      Field[] newFields = new Field[(length + count) * 2];
-      int newZero = newChars.length / 2 - (length + count) / 2;
-      System.arraycopy(chars, zero, newChars, newZero, index);
-      System.arraycopy(chars, zero + index, newChars, newZero + index + count, length - index);
-      System.arraycopy(fields, zero, newFields, newZero, index);
-      System.arraycopy(fields, zero + index, newFields, newZero + index + count, length - index);
+    // Java note: Keeping this code out of prepareForInsert() increases the speed of append operations.
+    int oldCapacity = chars.length;
+    int oldZero = zero;
+    char[] oldChars = chars;
+    Field[] oldFields = fields;
+    if (length + count > oldCapacity) {
+      int newCapacity = (length + count) * 2;
+      int newZero = newCapacity / 2 - (length + count) / 2;
+
+      char[] newChars = new char[newCapacity];
+      Field[] newFields = new Field[newCapacity];
+
+      // First copy the prefix and then the suffix, leaving room for the new chars that the
+      // caller wants to insert.
+      System.arraycopy(oldChars, oldZero, newChars, newZero, index);
+      System.arraycopy(
+          oldChars, oldZero + index, newChars, newZero + index + count, length - index);
+      System.arraycopy(oldFields, oldZero, newFields, newZero, index);
+      System.arraycopy(
+          oldFields, oldZero + index, newFields, newZero + index + count, length - index);
+
       chars = newChars;
       fields = newFields;
       zero = newZero;
       length += count;
     } else {
-      int newZero = chars.length / 2 - (length + count) / 2;
-      System.arraycopy(chars, zero, chars, newZero, length);
-      System.arraycopy(chars, newZero + index, chars, newZero + index + count, length - index);
-      System.arraycopy(fields, zero, fields, newZero, length);
-      System.arraycopy(fields, newZero + index, fields, newZero + index + count, length - index);
+      int newZero = oldCapacity / 2 - (length + count) / 2;
+
+      // First copy the entire string to the location of the prefix, and then move the suffix
+      // to make room for the new chars that the caller wants to insert.
+      System.arraycopy(oldChars, oldZero, oldChars, newZero, length);
+      System.arraycopy(
+          oldChars, newZero + index, oldChars, newZero + index + count, length - index);
+      System.arraycopy(oldFields, oldZero, oldFields, newZero, length);
+      System.arraycopy(
+          oldFields, newZero + index, oldFields, newZero + index + count, length - index);
+
       zero = newZero;
       length += count;
     }
@@ -348,8 +372,9 @@ public class NumberStringBuilder implements CharSequence {
   public boolean contentEquals(NumberStringBuilder other) {
     if (length != other.length) return false;
     for (int i = 0; i < length; i++) {
-      if (chars[zero + i] != other.chars[other.zero + i]) return false;
-      if (fields[zero + i] != other.fields[other.zero + i]) return false;
+      if (charAt(i) != other.charAt(i) || fieldAt(i) != other.fieldAt(i)) {
+        return false;
+      }
     }
     return true;
   }
@@ -390,6 +415,7 @@ public class NumberStringBuilder implements CharSequence {
           "You must pass an instance of com.ibm.icu.text.NumberFormat.Field as your FieldPosition attribute.  You passed: "
               + rawField.getClass().toString());
     }
+
     /* com.ibm.icu.text.NumberFormat. */ Field field = (Field) rawField;
 
     boolean seenStart = false;
@@ -398,8 +424,9 @@ public class NumberStringBuilder implements CharSequence {
       Field _field = (i < zero + length) ? fields[i] : null;
       if (seenStart && field != _field) {
         // Special case: GROUPING_SEPARATOR counts as an INTEGER.
-        if (field == NumberFormat.Field.INTEGER && _field == NumberFormat.Field.GROUPING_SEPARATOR)
+        if (field == NumberFormat.Field.INTEGER && _field == NumberFormat.Field.GROUPING_SEPARATOR) {
           continue;
+        }
         fp.setEndIndex(i - zero + offset);
         break;
       } else if (!seenStart && field == _field) {
@@ -413,8 +440,8 @@ public class NumberStringBuilder implements CharSequence {
 
     // Backwards compatibility: FRACTION needs to start after INTEGER if empty
     if (field == NumberFormat.Field.FRACTION && !seenStart) {
-      fp.setBeginIndex(fractionStart);
-      fp.setEndIndex(fractionStart);
+      fp.setBeginIndex(fractionStart + offset);
+      fp.setEndIndex(fractionStart + offset);
     }
   }
 
@@ -439,12 +466,7 @@ public class NumberStringBuilder implements CharSequence {
     if (current != null) {
       as.addAttribute(current, current, currentStart, length);
     }
-    return as.getIterator();
-  }
 
-  public NumberStringBuilder clear() {
-    zero = chars.length / 2;
-    length = 0;
-    return this;
+    return as.getIterator();
   }
 }
index affce4410eb1afd27eb5dbc7dcc55e1b66a49b66..8d6c975e26ee1f3460fea272bf43842418dcda42 100644 (file)
@@ -62,13 +62,13 @@ public final class NumberFormatter {
 
   public static enum DecimalMarkDisplay {
     AUTO,
-    ALWAYS_SHOWN,
+    ALWAYS,
   }
 
   public static enum SignDisplay {
     AUTO,
-    ALWAYS_SHOWN,
-    NEVER_SHOWN,
+    ALWAYS,
+    NEVER,
   }
 
   public static class UnlocalizedNumberFormatter {
@@ -398,7 +398,9 @@ public final class NumberFormatter {
 
   public static class Rounding implements IRounding {
 
-    protected static final int MAX_VALUE = 100;
+    // FIXME
+    /** @internal */
+    public static final int MAX_VALUE = 100;
 
     public static final Rounding NONE = new RoundingImplInfinity();
     public static final Rounding INTEGER = new RoundingImplFraction();
@@ -617,7 +619,7 @@ public final class NumberFormatter {
   public static class Grouping implements IGrouping {
 
     public static final Grouping DEFAULT = new GroupingImpl(GroupingImpl.TYPE_PLACEHOLDER);
-    public static final Grouping DEFAULT_MIN_2_DIGITS = new GroupingImpl(GroupingImpl.TYPE_MIN2);
+    public static final Grouping MIN_2_DIGITS = new GroupingImpl(GroupingImpl.TYPE_MIN2);
     public static final Grouping NONE = new GroupingImpl(GroupingImpl.TYPE_NONE);
 
     @Override
@@ -642,8 +644,13 @@ public final class NumberFormatter {
     public static final Padding NONE = new PaddingImpl();
 
     public static Padding codePoints(int cp, int targetWidth, PadPosition position) {
-      String paddingString = String.valueOf(Character.toChars(cp));
-      return PaddingImpl.getInstance(paddingString, targetWidth, 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
@@ -663,7 +670,12 @@ public final class NumberFormatter {
     public static final IntegerWidth DEFAULT = new IntegerWidthImpl();
 
     public static IntegerWidth zeroFillTo(int minInt) {
-      return new IntegerWidthImpl(minInt, Integer.MAX_VALUE);
+      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) {
index 6a4ae8eef79c82264ac3863773a79e4a496b90b2..6cc79fe1d0bdc782ad035d079cc0f4f4224fe347 100644 (file)
@@ -26,7 +26,7 @@ public class demo {
     UnlocalizedNumberFormatter formatter =
         NumberFormatter.with()
             .notation(Notation.COMPACT_SHORT)
-            .notation(Notation.SCIENTIFIC.withExponentSignDisplay(SignDisplay.ALWAYS_SHOWN))
+            .notation(Notation.SCIENTIFIC.withExponentSignDisplay(SignDisplay.ALWAYS))
             .notation(Notation.ENGINEERING.withMinExponentDigits(2))
             .notation(Notation.SIMPLE)
             .unit(Currency.getInstance("GBP"))
@@ -47,10 +47,10 @@ public class demo {
 //                })
             .grouping(Grouping.DEFAULT)
             .grouping(Grouping.NONE)
-            .grouping(Grouping.DEFAULT_MIN_2_DIGITS)
+            .grouping(Grouping.MIN_2_DIGITS)
             // .padding(Padding.codePoints(' ', 8, PadPosition.AFTER_PREFIX))
-            .sign(SignDisplay.ALWAYS_SHOWN)
-            .decimal(DecimalMarkDisplay.ALWAYS_SHOWN)
+            .sign(SignDisplay.ALWAYS)
+            .decimal(DecimalMarkDisplay.ALWAYS)
             .symbols(DecimalFormatSymbols.getInstance(new ULocale("fr@digits=ascii")))
             .symbols(NumberingSystem.getInstanceByName("arab"))
             .symbols(NumberingSystem.LATIN);
index b68d6d6cc2f5ca42ea0233be5042d9ab29abbaa8..fe7de892d936bbedd6a70130a707a2915f60e986 100644 (file)
@@ -22,38 +22,55 @@ public class CompactImpl implements QuantityChain {
 
   final PluralRules rules;
   final CompactData data;
-  /* final */ Map<String, CompactModInfo> precomputedMods;
-  /* final */ QuantityChain parent;
+  final Map<String, CompactModInfo> precomputedMods;
+  final QuantityChain parent;
 
   public static CompactImpl getInstance(
-      ULocale dataLocale, CompactType compactType, CompactStyle compactStyle, PluralRules rules) {
+      ULocale dataLocale,
+      CompactType compactType,
+      CompactStyle compactStyle,
+      PluralRules rules,
+      MurkyModifier buildReference,
+      QuantityChain parent) {
     CompactData data = CompactData.getInstance(dataLocale, compactType, compactStyle);
-    return new CompactImpl(data, rules);
+    return new CompactImpl(data, rules, buildReference, parent);
   }
 
   public static CompactImpl getInstance(
-      Map<String, Map<String, String>> compactCustomData, PluralRules rules) {
+      Map<String, Map<String, String>> compactCustomData,
+      PluralRules rules,
+      MurkyModifier buildReference,
+      QuantityChain parent) {
     CompactData data = CompactData.getInstance(compactCustomData);
-    return new CompactImpl(data, rules);
+    return new CompactImpl(data, rules, buildReference, parent);
   }
 
-  private CompactImpl(CompactData data, PluralRules rules) {
+  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 void precomputeAllModifiers(MurkyModifier reference) {
-    precomputedMods = new HashMap<String, CompactModInfo>();
+  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);
-      reference.setPatternInfo(patternInfo);
-      info.mod = reference.createImmutable();
+      buildReference.setPatternInfo(patternInfo);
+      info.mod = buildReference.createImmutable();
       info.numDigits = patternInfo.positive.totalIntegerDigits;
       precomputedMods.put(patternString, info);
     }
+    return precomputedMods;
   }
 
   private static class CompactModInfo {
@@ -61,12 +78,6 @@ public class CompactImpl implements QuantityChain {
     public int numDigits;
   }
 
-  @Override
-  public QuantityChain chain(QuantityChain parent) {
-    this.parent = parent;
-    return this;
-  }
-
   @Override
   public MicroProps withQuantity(FormatQuantity input) {
     MicroProps micros = parent.withQuantity(input);
index 9e9d2076a2b392abef4fc5a499a6388d758a46ad..8ad25cc62d4f778d5227e2637ed8c2284c93b64c 100644 (file)
@@ -13,8 +13,8 @@ import newapi.NumberFormatter.IGrouping;
 public class GroupingImpl extends Grouping.Internal {
 
   // Conveniences for Java handling of shorts
-  private static final short S2 = 2;
-  private static final short S3 = 3;
+  private static final byte B2 = 2;
+  private static final byte B3 = 3;
 
   // For the "placeholder constructor"
   public static final char TYPE_PLACEHOLDER = 0;
@@ -23,12 +23,12 @@ public class GroupingImpl extends Grouping.Internal {
 
   // 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(S3, S3, false);
-  static final GroupingImpl GROUPING_3_2 = new GroupingImpl(S3, S2, false);
-  static final GroupingImpl GROUPING_3_MIN2 = new GroupingImpl(S3, S3, true);
-  static final GroupingImpl GROUPING_3_2_MIN2 = new GroupingImpl(S3, S2, true);
+  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(short grouping1, short grouping2, boolean min2) {
+  static GroupingImpl getInstance(byte grouping1, byte grouping2, boolean min2) {
     if (grouping1 == -1) {
       return NONE;
     } else if (!min2 && grouping1 == 3 && grouping2 == 3) {
@@ -54,8 +54,8 @@ public class GroupingImpl extends Grouping.Internal {
   }
 
   final IGrouping lambda;
-  final short grouping1; // -2 means "needs locale data"; -1 means "no grouping"
-  final short grouping2;
+  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. */
@@ -82,7 +82,7 @@ public class GroupingImpl extends Grouping.Internal {
     }
   }
 
-  private GroupingImpl(short grouping1, short grouping2, boolean min2) {
+  private GroupingImpl(byte grouping1, byte grouping2, boolean min2) {
     this.lambda = null;
     this.grouping1 = grouping1;
     this.grouping2 = grouping2;
@@ -101,9 +101,10 @@ public class GroupingImpl extends Grouping.Internal {
       return this;
     }
     assert lambda == null;
-    short grouping1 = (short) (patternInfo.positive.groupingSizes & 0xffff);
-    short grouping2 = (short) ((patternInfo.positive.groupingSizes >>> 16) & 0xffff);
-    short grouping3 = (short) ((patternInfo.positive.groupingSizes >>> 32) & 0xffff);
+    // 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;
     }
index 795652fa0b2cf90e92d4277b3265706f8164196f..8c8695e1b17d1cc4d71e742de0c2fd98c8f908d7 100644 (file)
@@ -3,6 +3,7 @@
 package newapi.impl;
 
 import newapi.NumberFormatter.IntegerWidth;
+import newapi.NumberFormatter.Rounding;
 
 public final class IntegerWidthImpl extends IntegerWidth.Internal {
   public final int minInt;
@@ -21,7 +22,16 @@ public final class IntegerWidthImpl extends IntegerWidth.Internal {
   }
 
   @Override
-public IntegerWidthImpl truncateAt(int maxInt) {
-    return new IntegerWidthImpl(minInt, maxInt);
+  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 e9f8c3d620f438b31505ed8670a3ca396c1f1f25..16a11d709bce0ff27bbad90e38527601c5bebe96 100644 (file)
@@ -31,6 +31,7 @@ public class MacroProps implements Cloneable {
   public AffixPatternProvider affixProvider; // not in API; for JDK compatibility mode only
   public MultiplierImpl multiplier; // not in API; for JDK compatibility mode only
   public PluralRules rules; // not in API; could be made public in the future
+  public Long threshold; // not in API; controls internal self-regulation threshold
   public ULocale loc;
 
   /**
@@ -99,7 +100,7 @@ public class MacroProps implements Cloneable {
     try {
       return super.clone();
     } catch (CloneNotSupportedException e) {
-      throw new AssertionError();
+      throw new AssertionError(e);
     }
   }
 }
index 50c218fad2bbe6e387566275e98cb666e0ddbc46..6c57121f2722767478daf72a86a69df411a33fc8 100644 (file)
@@ -26,21 +26,19 @@ public class MicroProps implements Cloneable, QuantityChain {
   public int multiplier;
   public boolean useCurrency;
 
-  private boolean frozen = false;
-
-  public void enableCloneInChain() {
-    frozen = true;
-  }
-
-  @Override
-  public QuantityChain chain(QuantityChain parent) {
-    // The MicroProps instance should always be at the top of the chain!
-    throw new AssertionError();
+  private final boolean immutable;
+
+  /**
+   * @param immutable Whether this MicroProps should behave as an immutable after construction with
+   *     respect to the quantity chain.
+   */
+  public MicroProps(boolean immutable) {
+    this.immutable = immutable;
   }
 
   @Override
   public MicroProps withQuantity(FormatQuantity quantity) {
-    if (frozen) {
+    if (immutable) {
       return (MicroProps) this.clone();
     } else {
       return this;
@@ -52,7 +50,7 @@ public class MicroProps implements Cloneable, QuantityChain {
     try {
       return super.clone();
     } catch (CloneNotSupportedException e) {
-      throw new AssertionError();
+      throw new AssertionError(e);
     }
   }
 }
index 11448f4b0877bc2185a8636889a475dd2a5f824c..1c737717c6487d6755a9cfb60f07b644197ab431 100644 (file)
@@ -6,25 +6,31 @@ import java.math.BigDecimal;
 
 import com.ibm.icu.impl.number.FormatQuantity;
 
-public class MultiplierImpl implements QuantityChain {
+public class MultiplierImpl implements QuantityChain, Cloneable {
   final int magnitudeMultiplier;
   final BigDecimal bigDecimalMultiplier;
-  /* final */ QuantityChain parent;
+  final QuantityChain parent;
 
   public MultiplierImpl(int magnitudeMultiplier) {
     this.magnitudeMultiplier = magnitudeMultiplier;
     this.bigDecimalMultiplier = null;
+    parent = null;
   }
 
   public MultiplierImpl(BigDecimal bigDecimalMultiplier) {
     this.magnitudeMultiplier = 0;
     this.bigDecimalMultiplier = bigDecimalMultiplier;
+    parent = null;
   }
 
-  @Override
-  public QuantityChain chain(QuantityChain parent) {
+  private MultiplierImpl(MultiplierImpl base, QuantityChain parent) {
+    this.magnitudeMultiplier = base.magnitudeMultiplier;
+    this.bigDecimalMultiplier = base.bigDecimalMultiplier;
     this.parent = parent;
-    return this;
+  }
+
+  public QuantityChain copyAndChain(QuantityChain parent) {
+    return new MultiplierImpl(this, parent);
   }
 
   @Override
index 125bf197740ecad2ae17704ffcb2dca71cc61e23..27159e47e751bf404fa37618258978aa7f57ad12 100644 (file)
@@ -6,6 +6,7 @@ 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;
@@ -17,12 +18,21 @@ import com.ibm.icu.util.Currency;
 import newapi.NumberFormatter.SignDisplay;
 
 /**
- * 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!!!
+ * This class is a {@link Modifier} that wraps a decimal format pattern. It applies the pattern's
+ * affixes in {@link Modifier#apply}.
  *
- * <p>This class takes a parsed pattern and returns a Modifier, without creating any objects. When
- * the Modifier methods are called, symbols are substituted directly into the output
- * NumberStringBuilder, without creating any intermediate Strings.
+ * <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 {
 
@@ -56,19 +66,46 @@ public class MurkyModifier implements Modifier, SymbolProvider, CharSequence, Qu
   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();
@@ -89,6 +126,13 @@ public class MurkyModifier implements Modifier, SymbolProvider, CharSequence, Qu
     }
   }
 
+  /**
+   * 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;
@@ -98,41 +142,34 @@ public class MurkyModifier implements Modifier, SymbolProvider, CharSequence, Qu
   /**
    * 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.
+   * the pattern ("¤¤¤").
    */
   public boolean needsPlurals() {
     return patternInfo.containsSymbolType(AffixPatternUtils.TYPE_CURRENCY_TRIPLE);
   }
 
-  @Override
-  public QuantityChain chain(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;
+  /**
+   * 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 current instance is not changed by
-   * calling this method except for the number properties.
+   * 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 createImmutable() {
+  public ImmutableMurkyModifier createImmutableAndChain(QuantityChain parent) {
     NumberStringBuilder a = new NumberStringBuilder();
     NumberStringBuilder b = new NumberStringBuilder();
     if (needsPlurals()) {
@@ -146,14 +183,14 @@ public class MurkyModifier implements Modifier, SymbolProvider, CharSequence, Qu
         mods[ImmutableMurkyModifierWithPlurals.getModIndex(false, plural)] = positive;
         mods[ImmutableMurkyModifierWithPlurals.getModIndex(true, plural)] = negative;
       }
-      return new ImmutableMurkyModifierWithPlurals(mods, rules);
+      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);
+      return new ImmutableMurkyModifierWithoutPlurals(positive, negative, parent);
     }
   }
 
@@ -174,21 +211,18 @@ public class MurkyModifier implements Modifier, SymbolProvider, CharSequence, Qu
   public static class ImmutableMurkyModifierWithoutPlurals implements ImmutableMurkyModifier {
     final Modifier positive;
     final Modifier negative;
-    /* final */ QuantityChain parent;
+    final QuantityChain parent;
 
-    public ImmutableMurkyModifierWithoutPlurals(Modifier positive, Modifier negative) {
+    public ImmutableMurkyModifierWithoutPlurals(
+        Modifier positive, Modifier negative, QuantityChain parent) {
       this.positive = positive;
       this.negative = negative;
-    }
-
-    @Override
-    public QuantityChain chain(QuantityChain parent) {
       this.parent = parent;
-      return this;
     }
 
     @Override
     public MicroProps withQuantity(FormatQuantity quantity) {
+      assert parent != null;
       MicroProps micros = parent.withQuantity(quantity);
       applyToMicros(micros, quantity);
       return micros;
@@ -207,13 +241,15 @@ public class MurkyModifier implements Modifier, SymbolProvider, CharSequence, Qu
   public static class ImmutableMurkyModifierWithPlurals implements ImmutableMurkyModifier {
     final Modifier[] mods;
     final PluralRules rules;
-    /* final */ QuantityChain parent;
+    final QuantityChain parent;
 
-    public ImmutableMurkyModifierWithPlurals(Modifier[] mods, PluralRules rules) {
+    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() {
@@ -224,14 +260,9 @@ public class MurkyModifier implements Modifier, SymbolProvider, CharSequence, Qu
       return plural.ordinal() * 2 + (isNegative ? 1 : 0);
     }
 
-    @Override
-    public QuantityChain chain(QuantityChain parent) {
-      this.parent = parent;
-      return this;
-    }
-
     @Override
     public MicroProps withQuantity(FormatQuantity quantity) {
+      assert parent != null;
       MicroProps micros = parent.withQuantity(quantity);
       applyToMicros(micros, quantity);
       return micros;
@@ -248,6 +279,26 @@ public class MurkyModifier implements Modifier, SymbolProvider, CharSequence, Qu
     }
   }
 
+  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);
@@ -336,7 +387,7 @@ public class MurkyModifier implements Modifier, SymbolProvider, CharSequence, Qu
     // Should the output render '+' where '-' would normally appear in the pattern?
     plusReplacesMinusSign =
         !isNegative
-            && signDisplay == SignDisplay.ALWAYS_SHOWN
+            && signDisplay == SignDisplay.ALWAYS
             && patternInfo.positiveHasPlusSign() == false;
 
     // Should we use the negative affix pattern? (If not, we will use the positive one)
@@ -361,7 +412,7 @@ public class MurkyModifier implements Modifier, SymbolProvider, CharSequence, Qu
     if (!isPrefix || useNegativeAffixPattern) {
       prependSign = false;
     } else if (isNegative) {
-      prependSign = signDisplay != SignDisplay.NEVER_SHOWN;
+      prependSign = signDisplay != SignDisplay.NEVER;
     } else {
       prependSign = plusReplacesMinusSign;
     }
index dbe5bc713f9eb9f45c43298fd876295e1063d6d1..67ddc090dcad8a0ea37e633c6d9a41fe2c614bf3 100644 (file)
@@ -8,6 +8,7 @@ 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")
@@ -41,9 +42,14 @@ public class NotationImpl {
 
     @Override
     public NotationScientific withMinExponentDigits(int minExponentDigits) {
-      NotationScientificImpl other = (NotationScientificImpl) this.clone();
-      other.minExponentDigits = minExponentDigits;
-      return other;
+      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
index b7727eef036d70810014b6041bee507b9de84bb5..173031e884a4a4860722464587fa5804151b84ba 100644 (file)
@@ -4,7 +4,7 @@ package newapi.impl;
 
 import java.util.Locale;
 import java.util.Objects;
-import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLongFieldUpdater;
 
 import com.ibm.icu.impl.number.FormatQuantity4;
 import com.ibm.icu.impl.number.FormatQuantityBCD;
@@ -34,9 +34,6 @@ public class NumberFormatterImpl extends NumberFormatter.LocalizedNumberFormatte
 
   private static final NumberFormatterImpl BASE = new NumberFormatterImpl();
 
-  // TODO: Set a good value here.
-  static final int DEFAULT_THRESHOLD = 3;
-
   static final int KEY_MACROS = 0;
   static final int KEY_LOCALE = 1;
   static final int KEY_NOTATION = 2;
@@ -49,7 +46,8 @@ public class NumberFormatterImpl extends NumberFormatter.LocalizedNumberFormatte
   static final int KEY_UNIT_WIDTH = 9;
   static final int KEY_SIGN = 10;
   static final int KEY_DECIMAL = 11;
-  static final int KEY_MAX = 12;
+  static final int KEY_THRESHOLD = 12;
+  static final int KEY_MAX = 13;
 
   public static NumberFormatterImpl with() {
     return BASE;
@@ -72,20 +70,23 @@ public class NumberFormatterImpl extends NumberFormatter.LocalizedNumberFormatte
     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 AtomicInteger callCount;
+  volatile long callCountInternal; // do not access directly; use callCount instead
   volatile NumberFormatterImpl savedWithUnit;
   volatile Worker1 compiled;
 
-  /** Base constructor; called during startup only */
+  /** Base constructor; called during startup only. Sets the threshold to the default value of 3. */
   private NumberFormatterImpl() {
     parent = null;
-    key = -1;
-    value = null;
+    key = KEY_THRESHOLD;
+    value = new Long(3);
   }
 
   /** Primary constructor */
@@ -160,6 +161,15 @@ public class NumberFormatterImpl extends NumberFormatter.LocalizedNumberFormatte
     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());
@@ -167,44 +177,26 @@ public class NumberFormatterImpl extends NumberFormatter.LocalizedNumberFormatte
 
   @Override
   public NumberFormatterResult format(long input) {
-    return format(new FormatQuantity4(input), DEFAULT_THRESHOLD);
+    return format(new FormatQuantity4(input));
   }
 
   @Override
   public NumberFormatterResult format(double input) {
-    return format(new FormatQuantity4(input), DEFAULT_THRESHOLD);
+    return format(new FormatQuantity4(input));
   }
 
   @Override
   public NumberFormatterResult format(Number input) {
-    return format(new FormatQuantity4(input), DEFAULT_THRESHOLD);
+    return format(new FormatQuantity4(input));
   }
 
   @Override
   public NumberFormatterResult format(Measure input) {
-    return formatWithThreshold(input, DEFAULT_THRESHOLD);
-  }
-
-  /**
-   * Internal version of format with support for 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 NumberFormatterResult formatWithThreshold(Number number, int threshold) {
-    return format(new FormatQuantity4(number), threshold);
-  }
-
-  /**
-   * Internal version of format with support for 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 NumberFormatterResult formatWithThreshold(Measure input, int threshold) {
     MeasureUnit unit = input.getUnit();
     Number number = input.getNumber();
     // Use this formatter if possible
     if (Objects.equals(resolve().unit, unit)) {
-      return formatWithThreshold(number, threshold);
+      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.
@@ -213,24 +205,29 @@ public class NumberFormatterImpl extends NumberFormatter.LocalizedNumberFormatte
       withUnit = new NumberFormatterImpl(this, KEY_UNIT, unit);
       savedWithUnit = withUnit;
     }
-    return withUnit.formatWithThreshold(number, threshold);
+    return withUnit.format(number);
   }
 
-  private NumberFormatterResult format(FormatQuantityBCD fq, int threshold) {
+  /**
+   * 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.
+   */
+  private NumberFormatterResult format(FormatQuantityBCD fq) {
+    MacroProps macros = resolve();
     NumberStringBuilder string = new NumberStringBuilder();
-    // Lazily create the AtomicInteger
-    if (callCount == null) {
-      callCount = new AtomicInteger();
-    }
-    int currentCount = callCount.incrementAndGet();
+    long currentCount = callCount.incrementAndGet(this);
     MicroProps micros;
-    if (currentCount == threshold) {
-      compiled = Worker1.fromMacros(resolve());
+    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(resolve(), fq, string);
+      micros = Worker1.applyStatic(macros, fq, string);
     }
     return new NumberFormatterResult(string, fq, micros);
   }
@@ -257,7 +254,7 @@ public class NumberFormatterImpl extends NumberFormatter.LocalizedNumberFormatte
     // of a MacroProps object at each step.
     MacroProps macros = new MacroProps();
     NumberFormatterImpl current = this;
-    while (current != BASE) {
+    while (current != null) {
       switch (current.key) {
         case KEY_MACROS:
           macros.fallback((MacroProps) current.value);
@@ -317,8 +314,13 @@ public class NumberFormatterImpl extends NumberFormatter.LocalizedNumberFormatte
             macros.decimal = (DecimalMarkDisplay) current.value;
           }
           break;
+        case KEY_THRESHOLD:
+          if (macros.threshold == null) {
+            macros.threshold = (Long) current.value;
+          }
+          break;
         default:
-          throw new AssertionError();
+          throw new AssertionError("Unknown key: " + current.key);
       }
       current = current.parent;
     }
index ae28af0d65b2bef714f20ab2541341cd9a1ef9f1..747695325387bc858e00952edc78907d503ea9f8 100644 (file)
@@ -175,7 +175,7 @@ public final class NumberPropertyMapper {
     grouping2 = grouping2 > 0 ? grouping2 : grouping1;
     // TODO: Is it important to handle minGrouping > 2?
     macros.grouping =
-        GroupingImpl.getInstance((short) grouping1, (short) grouping2, minGrouping == 2);
+        GroupingImpl.getInstance((byte) grouping1, (byte) grouping2, minGrouping == 2);
 
     /////////////
     // PADDING //
@@ -193,14 +193,14 @@ public final class NumberPropertyMapper {
 
     macros.decimal =
         properties.getDecimalSeparatorAlwaysShown()
-            ? DecimalMarkDisplay.ALWAYS_SHOWN
+            ? DecimalMarkDisplay.ALWAYS
             : DecimalMarkDisplay.AUTO;
 
     ///////////////////////
     // SIGN ALWAYS SHOWN //
     ///////////////////////
 
-    macros.sign = properties.getSignAlwaysShown() ? SignDisplay.ALWAYS_SHOWN : SignDisplay.AUTO;
+    macros.sign = properties.getSignAlwaysShown() ? SignDisplay.ALWAYS : SignDisplay.AUTO;
 
     /////////////////////////
     // SCIENTIFIC NOTATION //
@@ -223,7 +223,7 @@ public final class NumberPropertyMapper {
               properties.getMinimumExponentDigits(),
               // Exponent sign always shown:
               properties.getExponentSignAlwaysShown()
-                  ? SignDisplay.ALWAYS_SHOWN
+                  ? SignDisplay.ALWAYS
                   : SignDisplay.AUTO);
       // Scientific notation also involves overriding the rounding mode.
       if (macros.rounding instanceof RoundingImplFraction) {
index 8c59a71802eb8edb2518dc41dcf823469161db27..65f3cb11668ca138776ece34ea019543a5212a08 100644 (file)
@@ -39,7 +39,7 @@ public class PositiveDecimalImpl implements Format.TargetFormat {
 
       // Add the decimal point
       if (input.getLowerDisplayMagnitude() < 0
-          || micros.decimal == DecimalMarkDisplay.ALWAYS_SHOWN) {
+          || micros.decimal == DecimalMarkDisplay.ALWAYS) {
         length +=
             string.insert(
                 length,
index 9efedec1b12ba2c92dcd9b98ae5be8dacae2c849..4f7a7965603e23a632db7c300fc7cce557b535b7 100644 (file)
@@ -5,6 +5,6 @@ package newapi.impl;
 import com.ibm.icu.impl.number.FormatQuantity;
 
 public interface QuantityChain {
-  QuantityChain chain(QuantityChain parent);
+  //QuantityChain addToChain(QuantityChain parent);
   MicroProps withQuantity(FormatQuantity quantity);
 }
\ No newline at end of file
index 45d1ad0661e813b360bc00aee996db8106ffff7f..1b544b90dfaf2ab6e0a97da895e9e1ecdeeb890f 100644 (file)
@@ -12,17 +12,12 @@ import com.ibm.icu.text.PluralRules;
 public class QuantityDependentModOuter implements QuantityChain {
   final Map<StandardPlural, Modifier> data;
   final PluralRules rules;
-  /* final */ QuantityChain parent;
+  final QuantityChain parent;
 
-  public QuantityDependentModOuter(Map<StandardPlural, Modifier> data, PluralRules rules) {
+  public QuantityDependentModOuter(Map<StandardPlural, Modifier> data, PluralRules rules, QuantityChain parent) {
     this.data = data;
     this.rules = rules;
-  }
-
-  @Override
-  public QuantityChain chain(QuantityChain parent) {
     this.parent = parent;
-    return this;
   }
 
   @Override
index 8e40ce8168d231608fc300410afaac646a24e6fd..526b50a42812210e03631871c999ed28b3cf18e8 100644 (file)
@@ -17,17 +17,24 @@ public class ScientificImpl implements QuantityChain, RoundingImpl.MultiplierPro
   final NotationImpl.NotationScientificImpl notation;
   final DecimalFormatSymbols symbols;
   final ScientificModifier[] precomputedMods;
-  /* final */ QuantityChain parent;
+  final QuantityChain parent;
 
   public static ScientificImpl getInstance(
-      NotationImpl.NotationScientificImpl notation, DecimalFormatSymbols symbols, boolean build) {
-    return new ScientificImpl(notation, symbols, build);
+      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) {
+      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
@@ -40,12 +47,6 @@ public class ScientificImpl implements QuantityChain, RoundingImpl.MultiplierPro
     }
   }
 
-  @Override
-  public QuantityChain chain(QuantityChain parent) {
-    this.parent = parent;
-    return this;
-  }
-
   @Override
   public MicroProps withQuantity(FormatQuantity quantity) {
     MicroProps micros = parent.withQuantity(quantity);
@@ -92,9 +93,9 @@ public class ScientificImpl implements QuantityChain, RoundingImpl.MultiplierPro
       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_SHOWN) {
+      if (exponent < 0 && notation.exponentSignDisplay != SignDisplay.NEVER) {
         i += output.insert(i, symbols.getMinusSignString(), NumberFormat.Field.EXPONENT_SIGN);
-      } else if (notation.exponentSignDisplay == SignDisplay.ALWAYS_SHOWN) {
+      } 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)
index ef1e0bace782b282d5e0c6978fcb4e94e072a99b..97160d5462d9ecd2b61d25fc33b4c8e0990a7be4 100644 (file)
@@ -6,6 +6,7 @@ import java.math.BigDecimal;
 import java.math.MathContext;
 import java.math.RoundingMode;
 
+import com.ibm.icu.impl.number.formatters.PaddingFormat.PadPosition;
 import com.ibm.icu.text.CompactDecimalFormat.CompactStyle;
 import com.ibm.icu.text.DecimalFormatSymbols;
 import com.ibm.icu.text.MeasureFormat.FormatWidth;
@@ -170,9 +171,9 @@ public final class SkeletonBuilder {
       if (notation.engineeringInterval != 1) {
         sb.append(notation.engineeringInterval);
       }
-      if (notation.exponentSignDisplay == SignDisplay.ALWAYS_SHOWN) {
+      if (notation.exponentSignDisplay == SignDisplay.ALWAYS) {
         sb.append('+');
-      } else if (notation.exponentSignDisplay == SignDisplay.NEVER_SHOWN) {
+      } else if (notation.exponentSignDisplay == SignDisplay.NEVER) {
         sb.append('!');
       } else {
         assert notation.exponentSignDisplay == SignDisplay.AUTO;
@@ -210,11 +211,11 @@ public final class SkeletonBuilder {
         c = safeCharAt(skeleton, offset++);
       }
       if (c == '+') {
-        sign = SignDisplay.ALWAYS_SHOWN;
+        sign = SignDisplay.ALWAYS;
         c = safeCharAt(skeleton, offset++);
       }
       if (c == '!') {
-        sign = SignDisplay.NEVER_SHOWN;
+        sign = SignDisplay.NEVER;
         c = safeCharAt(skeleton, offset++);
       }
       while (c == '0') {
@@ -251,7 +252,7 @@ public final class SkeletonBuilder {
       sb.append('$');
       sb.append(value.getSubtype());
     } else {
-      sb.append('U');
+      sb.append("U:");
       sb.append(value.getType());
       sb.append(':');
       sb.append(value.getSubtype());
@@ -430,7 +431,7 @@ public final class SkeletonBuilder {
     }
     if (value.equals(Grouping.DEFAULT)) {
       sb.append("DEFAULT");
-    } else if (value.equals(Grouping.DEFAULT_MIN_2_DIGITS)) {
+    } else if (value.equals(Grouping.MIN_2_DIGITS)) {
       sb.append("DEFAULT_MIN_2_DIGITS");
     } else if (value.equals(Grouping.NONE)) {
       sb.append("NONE");
@@ -471,7 +472,7 @@ public final class SkeletonBuilder {
       if (name.equals("DEFAULT")) {
         result = Grouping.DEFAULT;
       } else if (name.equals("DEFAULT_MIN_2_DIGITS")) {
-        result = Grouping.DEFAULT_MIN_2_DIGITS;
+        result = Grouping.MIN_2_DIGITS;
       } else if (name.equals("NONE")) {
         result = Grouping.NONE;
       }
@@ -486,13 +487,38 @@ public final class SkeletonBuilder {
       sb.append("NONE");
       return;
     }
-    sb.append("CP:");
-    // TODO: Handle padding strings that contain ':'
-    sb.append(padding.paddingString);
-    sb.append(':');
     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) {
@@ -506,6 +532,20 @@ public final class SkeletonBuilder {
     }
   }
 
+  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?
@@ -521,14 +561,38 @@ public final class SkeletonBuilder {
     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);
@@ -541,9 +605,20 @@ public final class SkeletonBuilder {
     int originalOffset = offset;
     char c = safeCharAt(skeleton, offset++);
     while (c != brk) {
-      sb.append(c);
+      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);
+  }
 }
index 376f9161454d1652412b66b53f7905404ac58396..99cdf87d177d22fb4b0fda114f8dab44f455106e 100644 (file)
@@ -69,7 +69,7 @@ public class Worker1 {
     boolean perMille = false;
     PluralRules rules = input.rules;
 
-    MicroProps micros = new MicroProps();
+    MicroProps micros = new MicroProps(build);
     QuantityChain chain = micros;
 
     // Copy over the simple settings
@@ -140,8 +140,7 @@ public class Worker1 {
     // An int magnitude multiplier is used when not in compatibility mode to
     // reduce object creations.
     if (input.multiplier != null) {
-      // TODO: Make sure this is thread safe.
-      chain = input.multiplier.chain(chain);
+      chain = input.multiplier.copyAndChain(chain);
     }
 
     // Rounding strategy
@@ -158,7 +157,7 @@ public class Worker1 {
       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.DEFAULT_MIN_2_DIGITS, patternInfo);
+      micros.grouping = GroupingImpl.normalizeType(Grouping.MIN_2_DIGITS, patternInfo);
     } else {
       micros.grouping = GroupingImpl.normalizeType(Grouping.DEFAULT, patternInfo);
     }
@@ -168,14 +167,14 @@ public class Worker1 {
       assert input.notation instanceof NotationImpl.NotationScientificImpl;
       chain =
           ScientificImpl.getInstance(
-                  (NotationImpl.NotationScientificImpl) input.notation, micros.symbols, build)
-              .chain(chain);
+              (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);
@@ -189,9 +188,9 @@ public class Worker1 {
       murkyMod.setSymbols(micros.symbols, currency, unitWidth, null);
     }
     if (build) {
-      chain = murkyMod.createImmutable().chain(chain);
+      chain = murkyMod.createImmutableAndChain(chain);
     } else {
-      chain = murkyMod.chain(chain);
+      chain = murkyMod.addToChain(chain);
     }
 
     // Outer modifier (CLDR units and currency long names)
@@ -200,7 +199,7 @@ public class Worker1 {
         // Lazily create PluralRules
         rules = PluralRules.forLocale(input.loc);
       }
-      chain = new QuantityDependentModOuter(outerMods, rules).chain(chain);
+      chain = new QuantityDependentModOuter(outerMods, ruleschain);
     } else {
       // No outer modifier required
       micros.modOuter = ConstantAffixModifier.EMPTY;
@@ -223,25 +222,21 @@ public class Worker1 {
         rules = PluralRules.forLocale(input.loc);
       }
       CompactStyle compactStyle = ((NotationImpl.NotationCompactImpl) input.notation).compactStyle;
-      CompactImpl worker;
       if (compactStyle == null) {
         // Use compact custom data
-        worker =
+        chain =
             CompactImpl.getInstance(
-                ((NotationImpl.NotationCompactImpl) input.notation).compactCustomData, rules);
+                ((NotationImpl.NotationCompactImpl) input.notation).compactCustomData,
+                rules,
+                build ? murkyMod : null,
+                chain);
       } else {
         CompactType compactType =
             (input.unit instanceof Currency) ? CompactType.CURRENCY : CompactType.DECIMAL;
-        worker = CompactImpl.getInstance(input.loc, compactType, compactStyle, rules);
-      }
-      if (build) {
-        worker.precomputeAllModifiers(murkyMod);
+        chain =
+            CompactImpl.getInstance(
+                input.loc, compactType, compactStyle, rules, build ? murkyMod : null, chain);
       }
-      chain = worker.chain(chain);
-    }
-
-    if (build) {
-      micros.enableCloneInChain();
     }
 
     return chain;
index aa8de0234921ac31d0df22a3af61b6d2568c9e94..3755a202ca3e204b43f1b97621c5fd6c1cd854ab 100644 (file)
@@ -6,7 +6,7 @@
        <classpathentry combineaccessrules="false" kind="src" path="/icu4j-test-framework"/>
        <classpathentry combineaccessrules="false" kind="src" path="/icu4j-langdata"/>
        <classpathentry combineaccessrules="false" kind="src" path="/icu4j-regiondata"/>
-       <classpathentry combineaccessrules="false" kind="src" path="/icu4j-currdata"/>
+       <classpathentry combineaccessrules="false" exported="true" kind="src" path="/icu4j-currdata"/>
        <classpathentry kind="lib" path="/external-libraries/hamcrest-core-1.3.jar"/>
        <classpathentry kind="lib" path="/external-libraries/junit-4.12.jar" sourcepath="/external-libraries/junit-4.12-sources.jar">
                <attributes>
index d6385cd1beeee80a7ba2837f60b873532e363096..38b5f3acbf5bd8c975ace10a453f13233b84ba3a 100644 (file)
@@ -331,7 +331,7 @@ set format 299792458.0
 begin
 minIntegerDigits       maxIntegerDigits        minFractionDigits       maxFractionDigits       output  breaks
 // JDK gives 2.99792458E8 (maxInt + maxFrac instead of minInt + maxFrac)
-1      1000    0       5       2.99792E8       K
+1      99      0       5       2.99792E8       K
 // JDK gives .3E9 instead of unlimited precision.
 0      1       0       0       2.99792458E8    K
 1      1       0       0       3E8
@@ -499,10 +499,10 @@ begin
 format multiplier      output  breaks
 23     -12     -276
 23     -1      -23
-// ICU4J and JDK throw exception on zero multiplier.
-// ICU4C and S print 23.
+// ICU4J throws exception on zero multiplier.
+// ICU4C prints 23.
 // Q multiplies by zero and prints 0.
-23     0       0       CJKS
+23     0       0       CJ
 23     1       23
 23     12      276
 -23    12      -276
@@ -547,15 +547,15 @@ set pattern 0.5
 begin
 format roundingMode    output  breaks
 1.24   halfUp  1.0     K
-1.25   halfUp  1.5     K
+1.25   halfUp  1.5
 1.25   halfDown        1.0     K
-1.26   halfDown        1.5     K
+1.26   halfDown        1.5
 1.25   halfEven        1.0     K
--1.01  up      -1.5    K
+-1.01  up      -1.5
 -1.49  down    -1.0    K
-1.01   up      1.5     K
+1.01   up      1.5
 1.49   down    1.0     K
--1.01  ceiling -1.0
+-1.01  ceiling -1.0    K
 -1.49  floor   -1.5
 
 test currency usage setters
index 4c8ac78ece0f299b36035c1c2edbc52402bfde5d..8772d332c76d8b50817d14013985a77d7de6d710 100644 (file)
@@ -7,7 +7,6 @@ import java.math.RoundingMode;
 import java.text.ParseException;
 import java.text.ParsePosition;
 
-import org.junit.Ignore;
 import org.junit.Test;
 
 import com.ibm.icu.dev.test.TestUtil;
@@ -820,7 +819,6 @@ public class NumberFormatDataDrivenTest {
       };
 
   @Test
-  @Ignore
   public void TestDataDrivenICU58() {
     // Android can't access DecimalFormat_ICU58 for testing (ticket #13283).
     if (TestUtil.getJavaVendor() == TestUtil.JavaVendor.Android) return;
@@ -830,7 +828,6 @@ public class NumberFormatDataDrivenTest {
   }
 
   @Test
-  @Ignore
   public void TestDataDrivenJDK() {
     // Android implements java.text.DecimalFormat with ICU4J (ticket #13322).
     if (TestUtil.getJavaVendor() == TestUtil.JavaVendor.Android) return;
@@ -840,7 +837,6 @@ public class NumberFormatDataDrivenTest {
   }
 
   @Test
-  @Ignore
   public void TestDataDrivenICU59() {
     DataDrivenNumberFormatTestUtility.runFormatSuiteIncludingKnownFailures(
         "numberformattestspecification.txt", ICU59);
index c4d9d5caf34e6854de04258c6d45a0e4cc8dced1..d544e6fb0b17eb7cfef3af15487732ba3d82f41e 100644 (file)
@@ -30,13 +30,13 @@ public class MurkyModifierTest {
     murky.setNumberProperties(false, null);
     assertEquals("a", murky.getPrefix());
     assertEquals("b", murky.getSuffix());
-    murky.setPatternAttributes(SignDisplay.ALWAYS_SHOWN, false);
+    murky.setPatternAttributes(SignDisplay.ALWAYS, false);
     assertEquals("+a", murky.getPrefix());
     assertEquals("b", murky.getSuffix());
     murky.setNumberProperties(true, null);
     assertEquals("-a", murky.getPrefix());
     assertEquals("b", murky.getSuffix());
-    murky.setPatternAttributes(SignDisplay.NEVER_SHOWN, false);
+    murky.setPatternAttributes(SignDisplay.NEVER, false);
     assertEquals("a", murky.getPrefix());
     assertEquals("b", murky.getSuffix());
 
@@ -45,13 +45,13 @@ public class MurkyModifierTest {
     murky.setNumberProperties(false, null);
     assertEquals("a", murky.getPrefix());
     assertEquals("b", murky.getSuffix());
-    murky.setPatternAttributes(SignDisplay.ALWAYS_SHOWN, false);
+    murky.setPatternAttributes(SignDisplay.ALWAYS, false);
     assertEquals("c+", murky.getPrefix());
     assertEquals("d", murky.getSuffix());
     murky.setNumberProperties(true, null);
     assertEquals("c-", murky.getPrefix());
     assertEquals("d", murky.getSuffix());
-    murky.setPatternAttributes(SignDisplay.NEVER_SHOWN, false);
+    murky.setPatternAttributes(SignDisplay.NEVER, false);
     assertEquals("c-", murky.getPrefix()); // TODO: What should this behavior be?
     assertEquals("d", murky.getSuffix());
   }
index 697be1c3dee60b57bc3368b5e097bf151a18089d..bcdd4d31d237acb9ba87441a641557344257f521 100644 (file)
@@ -106,7 +106,7 @@ public class NumberFormatterTest {
         "Scientific sign always shown",
         "E+",
         NumberFormatter.with()
-            .notation(Notation.SCIENTIFIC.withExponentSignDisplay(SignDisplay.ALWAYS_SHOWN)),
+            .notation(Notation.SCIENTIFIC.withExponentSignDisplay(SignDisplay.ALWAYS)),
         ULocale.ENGLISH,
         "8.765E+4",
         "8.765E+3",
@@ -234,7 +234,7 @@ public class NumberFormatterTest {
   public void unitMeasure() {
     assertFormatDescending(
         "Meters Short",
-        "Ulength:meter",
+        "U:length:meter",
         NumberFormatter.with().unit(MeasureUnit.METER),
         ULocale.ENGLISH,
         "87,650 m",
@@ -249,7 +249,7 @@ public class NumberFormatterTest {
 
     assertFormatDescending(
         "Meters Long",
-        "Ulength:meter unit-width=WIDE",
+        "U:length:meter unit-width=WIDE",
         NumberFormatter.with().unit(MeasureUnit.METER).unitWidth(FormatWidth.WIDE),
         ULocale.ENGLISH,
         "87,650 meters",
@@ -264,7 +264,7 @@ public class NumberFormatterTest {
 
     assertFormatDescending(
         "Compact Meters Long",
-        "CC Ulength:meter unit-width=WIDE",
+        "CC U:length:meter unit-width=WIDE",
         NumberFormatter.with()
             .notation(Notation.COMPACT_LONG)
             .unit(MeasureUnit.METER)
@@ -290,7 +290,7 @@ public class NumberFormatterTest {
 
     assertFormatSingle(
         "Meters with Negative Sign",
-        "Ulength:meter",
+        "U:length:meter",
         NumberFormatter.with().unit(MeasureUnit.METER),
         ULocale.ENGLISH,
         -9876543.21,
@@ -724,7 +724,7 @@ public class NumberFormatterTest {
     assertFormatDescending(
         "Western Grouping, Min 2",
         "%% grouping=DEFAULT_MIN_2_DIGITS",
-        NumberFormatter.with().unit(Dimensionless.PERMILLE).grouping(Grouping.DEFAULT_MIN_2_DIGITS),
+        NumberFormatter.with().unit(Dimensionless.PERMILLE).grouping(Grouping.MIN_2_DIGITS),
         ULocale.ENGLISH,
         "87,650,000‰",
         "8,765,000‰",
@@ -739,7 +739,7 @@ public class NumberFormatterTest {
     assertFormatDescending(
         "Indic Grouping, Min 2",
         "%% grouping=DEFAULT_MIN_2_DIGITS",
-        NumberFormatter.with().unit(Dimensionless.PERMILLE).grouping(Grouping.DEFAULT_MIN_2_DIGITS),
+        NumberFormatter.with().unit(Dimensionless.PERMILLE).grouping(Grouping.MIN_2_DIGITS),
         new ULocale("en-IN"),
         "8,76,50,000‰",
         "87,65,000‰",
@@ -786,7 +786,7 @@ public class NumberFormatterTest {
 
     assertFormatDescending(
         "Padding",
-        "padding=CP:*:8:AFTER_PREFIX",
+        "padding=8:AFTER_PREFIX:*",
         NumberFormatter.with().padding(Padding.codePoints('*', 8, PadPosition.AFTER_PREFIX)),
         ULocale.ENGLISH,
         "**87,650",
@@ -801,7 +801,7 @@ public class NumberFormatterTest {
 
     assertFormatDescending(
         "Padding with code points",
-        "padding=CP:𐇤:8:AFTER_PREFIX",
+        "padding=8:AFTER_PREFIX:𐇤",
         NumberFormatter.with().padding(Padding.codePoints(0x101E4, 8, PadPosition.AFTER_PREFIX)),
         ULocale.ENGLISH,
         "𐇤𐇤87,650",
@@ -816,7 +816,7 @@ public class NumberFormatterTest {
 
     assertFormatDescending(
         "Padding with wide digits",
-        "padding=CP:*:8:AFTER_PREFIX symbols=ns:mathsanb",
+        "padding=8:AFTER_PREFIX:* symbols=ns:mathsanb",
         NumberFormatter.with()
             .padding(Padding.codePoints('*', 8, PadPosition.AFTER_PREFIX))
             .symbols(NumberingSystem.getInstanceByName("mathsanb")),
@@ -833,7 +833,7 @@ public class NumberFormatterTest {
 
     assertFormatDescending(
         "Padding with currency spacing",
-        "$GBP padding=CP:*:10:AFTER_PREFIX unit-width=SHORT",
+        "$GBP padding=10:AFTER_PREFIX:* unit-width=SHORT",
         NumberFormatter.with()
             .padding(Padding.codePoints('*', 10, PadPosition.AFTER_PREFIX))
             .unit(GBP)
@@ -851,7 +851,7 @@ public class NumberFormatterTest {
 
     assertFormatSingle(
         "Pad Before Prefix",
-        "padding=CP:*:8:BEFORE_PREFIX",
+        "padding=8:BEFORE_PREFIX:*",
         NumberFormatter.with().padding(Padding.codePoints('*', 8, PadPosition.BEFORE_PREFIX)),
         ULocale.ENGLISH,
         -88.88,
@@ -859,7 +859,7 @@ public class NumberFormatterTest {
 
     assertFormatSingle(
         "Pad After Prefix",
-        "padding=CP:*:8:AFTER_PREFIX",
+        "padding=8:AFTER_PREFIX:*",
         NumberFormatter.with().padding(Padding.codePoints('*', 8, PadPosition.AFTER_PREFIX)),
         ULocale.ENGLISH,
         -88.88,
@@ -867,7 +867,7 @@ public class NumberFormatterTest {
 
     assertFormatSingle(
         "Pad Before Suffix",
-        "% padding=CP:*:8:BEFORE_SUFFIX",
+        "% padding=8:BEFORE_SUFFIX:*",
         NumberFormatter.with()
             .padding(Padding.codePoints('*', 8, PadPosition.BEFORE_SUFFIX))
             .unit(Dimensionless.PERCENT),
@@ -877,7 +877,7 @@ public class NumberFormatterTest {
 
     assertFormatSingle(
         "Pad After Suffix",
-        "% padding=CP:*:8:AFTER_SUFFIX",
+        "% padding=8:AFTER_SUFFIX:*",
         NumberFormatter.with()
             .padding(Padding.codePoints('*', 8, PadPosition.AFTER_SUFFIX))
             .unit(Dimensionless.PERCENT),
@@ -1057,32 +1057,32 @@ public class NumberFormatterTest {
 
     assertFormatSingle(
         "Sign Always Positive",
-        "sign=ALWAYS_SHOWN",
-        NumberFormatter.with().sign(SignDisplay.ALWAYS_SHOWN),
+        "sign=ALWAYS",
+        NumberFormatter.with().sign(SignDisplay.ALWAYS),
         ULocale.ENGLISH,
         444444,
         "+444,444");
 
     assertFormatSingle(
         "Sign Always Negative",
-        "sign=ALWAYS_SHOWN",
-        NumberFormatter.with().sign(SignDisplay.ALWAYS_SHOWN),
+        "sign=ALWAYS",
+        NumberFormatter.with().sign(SignDisplay.ALWAYS),
         ULocale.ENGLISH,
         -444444,
         "-444,444");
 
     assertFormatSingle(
         "Sign Never Positive",
-        "sign=NEVER_SHOWN",
-        NumberFormatter.with().sign(SignDisplay.NEVER_SHOWN),
+        "sign=NEVER",
+        NumberFormatter.with().sign(SignDisplay.NEVER),
         ULocale.ENGLISH,
         444444,
         "444,444");
 
     assertFormatSingle(
         "Sign Never Negative",
-        "sign=NEVER_SHOWN",
-        NumberFormatter.with().sign(SignDisplay.NEVER_SHOWN),
+        "sign=NEVER",
+        NumberFormatter.with().sign(SignDisplay.NEVER),
         ULocale.ENGLISH,
         -444444,
         "444,444");
@@ -1107,8 +1107,8 @@ public class NumberFormatterTest {
 
     assertFormatDescending(
         "Decimal Always Shown",
-        "decimal=ALWAYS_SHOWN",
-        NumberFormatter.with().decimal(DecimalMarkDisplay.ALWAYS_SHOWN),
+        "decimal=ALWAYS",
+        NumberFormatter.with().decimal(DecimalMarkDisplay.ALWAYS),
         ULocale.ENGLISH,
         "87,650.",
         "8,765.",
@@ -1203,13 +1203,13 @@ 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.locale(locale); // no self-regulation
-    NumberFormatterImpl l2 = (NumberFormatterImpl) f.locale(locale); // all self-regulation
+    NumberFormatterImpl l1 = ((NumberFormatterImpl) f).threshold(0L).locale(locale); // no self-regulation
+    NumberFormatterImpl l2 = ((NumberFormatterImpl) f).threshold(1L).locale(locale); // all self-regulation
     for (int i = 0; i < 9; i++) {
       double d = inputs[i];
-      String actual1 = l1.formatWithThreshold(d, 0).toString();
+      String actual1 = l1.format(d).toString();
       assertEquals(message + ": L1: " + d, expected[i], actual1);
-      String actual2 = l2.formatWithThreshold(d, 1).toString();
+      String actual2 = l2.format(d).toString();
       assertEquals(message + ": L2: " + d, expected[i], actual2);
     }
   }
@@ -1222,11 +1222,11 @@ public class NumberFormatterTest {
       Number input,
       String expected) {
     assertEquals(message + ": Skeleton:", skeleton, f.toSkeleton());
-    NumberFormatterImpl l1 = (NumberFormatterImpl) f.locale(locale); // no self-regulation
-    NumberFormatterImpl l2 = (NumberFormatterImpl) f.locale(locale); // all self-regulation
-    String actual1 = l1.formatWithThreshold(input, 0).toString();
+    NumberFormatterImpl l1 = ((NumberFormatterImpl) f).threshold(0L).locale(locale); // no self-regulation
+    NumberFormatterImpl l2 = ((NumberFormatterImpl) f).threshold(1L).locale(locale); // all self-regulation
+    String actual1 = l1.format(input).toString();
     assertEquals(message + ": L1: " + input, expected, actual1);
-    String actual2 = l2.formatWithThreshold(input, 1).toString();
+    String actual2 = l2.format(input).toString();
     assertEquals(message + ": L2: " + input, expected, actual2);
   }
 
@@ -1238,11 +1238,11 @@ public class NumberFormatterTest {
       Measure input,
       String expected) {
     assertEquals(message + ": Skeleton:", skeleton, f.toSkeleton());
-    NumberFormatterImpl l1 = (NumberFormatterImpl) f.locale(locale); // no self-regulation
-    NumberFormatterImpl l2 = (NumberFormatterImpl) f.locale(locale); // all self-regulation
-    String actual1 = l1.formatWithThreshold(input, 0).toString();
+    NumberFormatterImpl l1 = ((NumberFormatterImpl) f).threshold(0L).locale(locale); // no self-regulation
+    NumberFormatterImpl l2 = ((NumberFormatterImpl) f).threshold(1L).locale(locale); // all self-regulation
+    String actual1 = l1.format(input).toString();
     assertEquals(message + ": L1: " + input, expected, actual1);
-    String actual2 = l2.formatWithThreshold(input, 1).toString();
+    String actual2 = l2.format(input).toString();
     assertEquals(message + ": L2: " + input, expected, actual2);
   }
 }
index a9e9a26750cfaf918239f9b2a3393cd9fa29c5af..009b8b2383dfb0565f9e4faadf581ad50fac60ad 100644 (file)
@@ -3,6 +3,8 @@
 package com.ibm.icu.dev.test.number;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 
 import java.text.FieldPosition;
@@ -21,7 +23,8 @@ public class NumberStringBuilderTest {
     "The quick brown fox jumps over the lazy dog",
     "😁",
     "mixed 😇 and ASCII",
-    "with combining characters like 🇦🇧🇨🇩"
+    "with combining characters like 🇦🇧🇨🇩",
+    "A very very very very very very very very very very long string to force heap"
   };
 
   @Test
@@ -59,6 +62,10 @@ public class NumberStringBuilderTest {
       sb4.insert(4, str.toCharArray());
       sb5.insert(4, str.toCharArray(), null);
       assertCharSequenceEquals(sb4, sb5);
+
+      sb4.append(sb4.toString());
+      sb5.append(new NumberStringBuilder(sb5));
+      assertCharSequenceEquals(sb4, sb5);
     }
   }
 
@@ -87,6 +94,21 @@ public class NumberStringBuilderTest {
     }
   }
 
+  @Test
+  public void testCopy() {
+    for (String str : EXAMPLE_STRINGS) {
+      NumberStringBuilder sb1 = new NumberStringBuilder();
+      sb1.append(str, null);
+      NumberStringBuilder sb2 = new NumberStringBuilder(sb1);
+      assertCharSequenceEquals(sb1, sb2);
+      assertTrue(sb1.contentEquals(sb2));
+
+      sb1.append("12345", null);
+      assertNotEquals(sb1.length(), sb2.length());
+      assertFalse(sb1.contentEquals(sb2));
+    }
+  }
+
   @Test
   public void testFields() {
     for (String str : EXAMPLE_STRINGS) {
@@ -97,7 +119,9 @@ public class NumberStringBuilderTest {
       assertEquals(str.length() * 2, fields.length);
       for (int i = 0; i < str.length(); i++) {
         assertEquals(null, fields[i]);
+        assertEquals(null, sb.fieldAt(i));
         assertEquals(NumberFormat.Field.CURRENCY, fields[i + str.length()]);
+        assertEquals(NumberFormat.Field.CURRENCY, sb.fieldAt(i + str.length()));
       }
 
       // Very basic FieldPosition test. More robust tests happen in NumberFormatTest.
@@ -123,10 +147,15 @@ public class NumberStringBuilderTest {
       fields = sb.toFieldArray();
       for (int i = 0; i < sb.length(); i++) {
         assertEquals(oldFields[i % oldFields.length], fields[i]);
-        if (fields[i] == null) numNull++;
-        else if (fields[i] == NumberFormat.Field.CURRENCY) numCurr++;
-        else if (fields[i] == NumberFormat.Field.INTEGER) numInt++;
-        else throw new AssertionError("Encountered unknown field in " + str);
+        if (fields[i] == null) {
+          numNull++;
+        } else if (fields[i] == NumberFormat.Field.CURRENCY) {
+          numCurr++;
+        } else if (fields[i] == NumberFormat.Field.INTEGER) {
+          numInt++;
+        } else {
+          throw new AssertionError("Encountered unknown field in " + str);
+        }
       }
       assertEquals(str.length() * 4, numNull);
       assertEquals(numNull, numCurr);