From: Shane Carr Date: Thu, 24 Aug 2017 06:03:12 +0000 (+0000) Subject: ICU-13177 Assorted cleanup and minor changes. Preparation for C++. X-Git-Tag: release-60-rc~98^2~31 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=41d811b716728e654c827ff6193c4dd18c361557;p=icu ICU-13177 Assorted cleanup and minor changes. Preparation for C++. X-SVN-Rev: 40351 --- diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/NumberStringBuilder.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/NumberStringBuilder.java index ad4a52f6be6..2d4878fb5e6 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/NumberStringBuilder.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/NumberStringBuilder.java @@ -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(); } } diff --git a/icu4j/main/classes/core/src/newapi/NumberFormatter.java b/icu4j/main/classes/core/src/newapi/NumberFormatter.java index affce4410eb..8d6c975e26e 100644 --- a/icu4j/main/classes/core/src/newapi/NumberFormatter.java +++ b/icu4j/main/classes/core/src/newapi/NumberFormatter.java @@ -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) { diff --git a/icu4j/main/classes/core/src/newapi/demo.java b/icu4j/main/classes/core/src/newapi/demo.java index 6a4ae8eef79..6cc79fe1d0b 100644 --- a/icu4j/main/classes/core/src/newapi/demo.java +++ b/icu4j/main/classes/core/src/newapi/demo.java @@ -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); diff --git a/icu4j/main/classes/core/src/newapi/impl/CompactImpl.java b/icu4j/main/classes/core/src/newapi/impl/CompactImpl.java index b68d6d6cc2f..fe7de892d93 100644 --- a/icu4j/main/classes/core/src/newapi/impl/CompactImpl.java +++ b/icu4j/main/classes/core/src/newapi/impl/CompactImpl.java @@ -22,38 +22,55 @@ public class CompactImpl implements QuantityChain { final PluralRules rules; final CompactData data; - /* final */ Map precomputedMods; - /* final */ QuantityChain parent; + final Map 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> compactCustomData, PluralRules rules) { + Map> 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(); + public static Map precomputeAllModifiers( + CompactData data, MurkyModifier buildReference) { + Map precomputedMods = new HashMap(); Set 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); diff --git a/icu4j/main/classes/core/src/newapi/impl/GroupingImpl.java b/icu4j/main/classes/core/src/newapi/impl/GroupingImpl.java index 9e9d2076a2b..8ad25cc62d4 100644 --- a/icu4j/main/classes/core/src/newapi/impl/GroupingImpl.java +++ b/icu4j/main/classes/core/src/newapi/impl/GroupingImpl.java @@ -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; } diff --git a/icu4j/main/classes/core/src/newapi/impl/IntegerWidthImpl.java b/icu4j/main/classes/core/src/newapi/impl/IntegerWidthImpl.java index 795652fa0b2..8c8695e1b17 100644 --- a/icu4j/main/classes/core/src/newapi/impl/IntegerWidthImpl.java +++ b/icu4j/main/classes/core/src/newapi/impl/IntegerWidthImpl.java @@ -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); + } } } diff --git a/icu4j/main/classes/core/src/newapi/impl/MacroProps.java b/icu4j/main/classes/core/src/newapi/impl/MacroProps.java index e9f8c3d620f..16a11d709bc 100644 --- a/icu4j/main/classes/core/src/newapi/impl/MacroProps.java +++ b/icu4j/main/classes/core/src/newapi/impl/MacroProps.java @@ -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); } } } diff --git a/icu4j/main/classes/core/src/newapi/impl/MicroProps.java b/icu4j/main/classes/core/src/newapi/impl/MicroProps.java index 50c218fad2b..6c57121f272 100644 --- a/icu4j/main/classes/core/src/newapi/impl/MicroProps.java +++ b/icu4j/main/classes/core/src/newapi/impl/MicroProps.java @@ -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); } } } diff --git a/icu4j/main/classes/core/src/newapi/impl/MultiplierImpl.java b/icu4j/main/classes/core/src/newapi/impl/MultiplierImpl.java index 11448f4b087..1c737717c64 100644 --- a/icu4j/main/classes/core/src/newapi/impl/MultiplierImpl.java +++ b/icu4j/main/classes/core/src/newapi/impl/MultiplierImpl.java @@ -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 diff --git a/icu4j/main/classes/core/src/newapi/impl/MurkyModifier.java b/icu4j/main/classes/core/src/newapi/impl/MurkyModifier.java index 125bf197740..27159e47e75 100644 --- a/icu4j/main/classes/core/src/newapi/impl/MurkyModifier.java +++ b/icu4j/main/classes/core/src/newapi/impl/MurkyModifier.java @@ -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}. * - *

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. + *

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. + * + *

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. + * + *

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. + * + *

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; } diff --git a/icu4j/main/classes/core/src/newapi/impl/NotationImpl.java b/icu4j/main/classes/core/src/newapi/impl/NotationImpl.java index dbe5bc713f9..67ddc090dca 100644 --- a/icu4j/main/classes/core/src/newapi/impl/NotationImpl.java +++ b/icu4j/main/classes/core/src/newapi/impl/NotationImpl.java @@ -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 diff --git a/icu4j/main/classes/core/src/newapi/impl/NumberFormatterImpl.java b/icu4j/main/classes/core/src/newapi/impl/NumberFormatterImpl.java index b7727eef036..173031e884a 100644 --- a/icu4j/main/classes/core/src/newapi/impl/NumberFormatterImpl.java +++ b/icu4j/main/classes/core/src/newapi/impl/NumberFormatterImpl.java @@ -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 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; } diff --git a/icu4j/main/classes/core/src/newapi/impl/NumberPropertyMapper.java b/icu4j/main/classes/core/src/newapi/impl/NumberPropertyMapper.java index ae28af0d65b..74769532538 100644 --- a/icu4j/main/classes/core/src/newapi/impl/NumberPropertyMapper.java +++ b/icu4j/main/classes/core/src/newapi/impl/NumberPropertyMapper.java @@ -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) { diff --git a/icu4j/main/classes/core/src/newapi/impl/PositiveDecimalImpl.java b/icu4j/main/classes/core/src/newapi/impl/PositiveDecimalImpl.java index 8c59a71802e..65f3cb11668 100644 --- a/icu4j/main/classes/core/src/newapi/impl/PositiveDecimalImpl.java +++ b/icu4j/main/classes/core/src/newapi/impl/PositiveDecimalImpl.java @@ -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, diff --git a/icu4j/main/classes/core/src/newapi/impl/QuantityChain.java b/icu4j/main/classes/core/src/newapi/impl/QuantityChain.java index 9efedec1b12..4f7a7965603 100644 --- a/icu4j/main/classes/core/src/newapi/impl/QuantityChain.java +++ b/icu4j/main/classes/core/src/newapi/impl/QuantityChain.java @@ -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 diff --git a/icu4j/main/classes/core/src/newapi/impl/QuantityDependentModOuter.java b/icu4j/main/classes/core/src/newapi/impl/QuantityDependentModOuter.java index 45d1ad0661e..1b544b90dfa 100644 --- a/icu4j/main/classes/core/src/newapi/impl/QuantityDependentModOuter.java +++ b/icu4j/main/classes/core/src/newapi/impl/QuantityDependentModOuter.java @@ -12,17 +12,12 @@ import com.ibm.icu.text.PluralRules; public class QuantityDependentModOuter implements QuantityChain { final Map data; final PluralRules rules; - /* final */ QuantityChain parent; + final QuantityChain parent; - public QuantityDependentModOuter(Map data, PluralRules rules) { + public QuantityDependentModOuter(Map data, PluralRules rules, QuantityChain parent) { this.data = data; this.rules = rules; - } - - @Override - public QuantityChain chain(QuantityChain parent) { this.parent = parent; - return this; } @Override diff --git a/icu4j/main/classes/core/src/newapi/impl/ScientificImpl.java b/icu4j/main/classes/core/src/newapi/impl/ScientificImpl.java index 8e40ce8168d..526b50a4281 100644 --- a/icu4j/main/classes/core/src/newapi/impl/ScientificImpl.java +++ b/icu4j/main/classes/core/src/newapi/impl/ScientificImpl.java @@ -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) diff --git a/icu4j/main/classes/core/src/newapi/impl/SkeletonBuilder.java b/icu4j/main/classes/core/src/newapi/impl/SkeletonBuilder.java index ef1e0bace78..97160d5462d 100644 --- a/icu4j/main/classes/core/src/newapi/impl/SkeletonBuilder.java +++ b/icu4j/main/classes/core/src/newapi/impl/SkeletonBuilder.java @@ -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); + } } diff --git a/icu4j/main/classes/core/src/newapi/impl/Worker1.java b/icu4j/main/classes/core/src/newapi/impl/Worker1.java index 376f9161454..99cdf87d177 100644 --- a/icu4j/main/classes/core/src/newapi/impl/Worker1.java +++ b/icu4j/main/classes/core/src/newapi/impl/Worker1.java @@ -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, rules, chain); } 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; diff --git a/icu4j/main/tests/core/.classpath b/icu4j/main/tests/core/.classpath index aa8de023492..3755a202ca3 100644 --- a/icu4j/main/tests/core/.classpath +++ b/icu4j/main/tests/core/.classpath @@ -6,7 +6,7 @@ - + diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/data/numberformattestspecification.txt b/icu4j/main/tests/core/src/com/ibm/icu/dev/data/numberformattestspecification.txt index d6385cd1bee..38b5f3acbf5 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/data/numberformattestspecification.txt +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/data/numberformattestspecification.txt @@ -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 diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatDataDrivenTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatDataDrivenTest.java index 4c8ac78ece0..8772d332c76 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatDataDrivenTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatDataDrivenTest.java @@ -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); diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/MurkyModifierTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/MurkyModifierTest.java index c4d9d5caf34..d544e6fb0b1 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/MurkyModifierTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/MurkyModifierTest.java @@ -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()); } diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterTest.java index 697be1c3dee..bcdd4d31d23 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterTest.java @@ -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); } } diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberStringBuilderTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberStringBuilderTest.java index a9e9a26750c..009b8b2383d 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberStringBuilderTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberStringBuilderTest.java @@ -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);