From c444c0c561d1b60e971e16c6f83dfddb78b3536b Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Wed, 13 Sep 2017 09:57:11 +0000 Subject: [PATCH] ICU-13117 More renaming and refactoring in Java with no behavior changes. X-SVN-Rev: 40392 --- .../com/ibm/icu/impl/number/AffixUtils.java | 27 +++ .../ibm/icu/impl/number/DecimalQuantity.java | 40 +--- .../number/DecimalQuantity_AbstractBCD.java | 70 ++++--- .../DecimalQuantity_DualStorageBCD.java | 76 ++++---- .../number/DecimalQuantity_SimpleStorage.java | 14 ++ .../src/com/ibm/icu/impl/number/Modifier.java | 5 + .../icu/impl/number/PatternStringParser.java | 16 +- .../icu/impl/number/PatternStringUtils.java | 8 +- .../modifiers/ConstantAffixModifier.java | 5 + .../modifiers/ConstantMultiFieldModifier.java | 6 + .../impl/number/modifiers/SimpleModifier.java | 12 ++ .../src/com/ibm/icu/text/PluralRules.java | 67 ++----- .../core/src/newapi/CompactNotation.java | 4 +- .../core/src/newapi/NumberFormatter.java | 5 - .../core/src/newapi/NumberFormatterImpl.java | 52 ++--- .../src/newapi/NumberFormatterSettings.java | 1 + .../core/src/newapi/ScientificNotation.java | 14 +- .../core/src/newapi/impl/LongNameHandler.java | 118 ++++++++---- .../core/src/newapi/impl/MeasureData.java | 65 ------- .../core/src/newapi/impl/MicroProps.java | 1 + .../newapi/impl/MutablePatternModifier.java | 180 +++++++----------- .../classes/core/src/newapi/impl/Padder.java | 47 ++--- .../format/NumberFormatDataDrivenTest.java | 6 +- .../icu/dev/test/format/PluralRulesTest.java | 18 +- ...ternUtilsTest.java => AffixUtilsTest.java} | 8 +- ...tityTest.java => DecimalQuantityTest.java} | 110 +++++++---- .../ibm/icu/dev/test/number/ModifierTest.java | 31 +-- .../dev/test/number/MurkyModifierTest.java | 71 ------- .../number/MutablePatternModifierTest.java | 107 +++++++++++ .../dev/test/number/NumberFormatterTest.java | 30 ++- .../dev/test/number/PatternStringTest.java | 2 +- .../icu/dev/test/number/PropertiesTest.java | 5 +- 32 files changed, 663 insertions(+), 558 deletions(-) delete mode 100644 icu4j/main/classes/core/src/newapi/impl/MeasureData.java rename icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/{AffixPatternUtilsTest.java => AffixUtilsTest.java} (95%) rename icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/{FormatQuantityTest.java => DecimalQuantityTest.java} (80%) delete mode 100644 icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/MurkyModifierTest.java create mode 100644 icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/MutablePatternModifierTest.java diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/AffixUtils.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/AffixUtils.java index e51f9efd14d..a64c3d708cf 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/AffixUtils.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/AffixUtils.java @@ -270,6 +270,7 @@ public class AffixUtils { * @param output The NumberStringBuilder to mutate with the result. * @param position The index into the NumberStringBuilder to insert the the string. * @param provider An object to generate locale symbols. + * @return The length of the string added to affixPattern. */ public static int unescape( CharSequence affixPattern, @@ -294,6 +295,32 @@ public class AffixUtils { return length; } + /** + * Sames as {@link #unescape}, but only calculates the code point count. More efficient than {@link #unescape} + * if you only need the length but not the string itself. + * + * @param affixPattern The original string to be unescaped. + * @param provider An object to generate locale symbols. + * @return The number of code points in the unescaped string. + */ + public static int unescapedCodePointCount(CharSequence affixPattern, SymbolProvider provider) { + int length = 0; + long tag = 0L; + while (hasNext(tag, affixPattern)) { + tag = nextToken(tag, affixPattern); + int typeOrCp = getTypeOrCp(tag); + if (typeOrCp == TYPE_CURRENCY_OVERFLOW) { + length += 1; + } else if (typeOrCp < 0) { + CharSequence symbol = provider.getSymbol(typeOrCp); + length += Character.codePointCount(symbol, 0, symbol.length()); + } else { + length += 1; + } + } + return length; + } + /** * Checks whether the given affix pattern contains at least one token of the given type, which is * one of the constants "TYPE_" in {@link AffixUtils}. diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity.java index ef6c7b66da2..368dcfbec17 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity.java @@ -122,36 +122,6 @@ public interface DecimalQuantity extends PluralRules.IFixedDecimal { */ public StandardPlural getStandardPlural(PluralRules rules); - // /** - // * @return The number of fraction digits, always in the closed interval [minFrac, maxFrac]. - // * @see #setIntegerFractionLength(int, int, int, int) - // */ - // public int fractionCount(); - // - // /** - // * @return The number of integer digits, always in the closed interval [minInt, maxInt]. - // * @see #setIntegerFractionLength(int, int, int, int) - // */ - // public int integerCount(); - // - // /** - // * @param index The index of the fraction digit relative to the decimal place, or 1 minus the - // * digit's power of ten. - // * @return The digit at the specified index. Undefined if index is greater than maxInt or less - // * than 0. - // * @see #fractionCount() - // */ - // public byte getFractionDigit(int index); - // - // /** - // * @param index The index of the integer digit relative to the decimal place, or the digit's power - // * of ten. - // * @return The digit at the specified index. Undefined if index is greater than maxInt or less - // * than 0. - // * @see #integerCount() - // */ - // public byte getIntegerDigit(int index); - /** * Gets the digit at the specified magnitude. For example, if the represented number is 12.3, * getDigit(-1) returns 3, since 3 is the digit corresponding to 10^-1. @@ -177,6 +147,11 @@ public interface DecimalQuantity extends PluralRules.IFixedDecimal { */ public int getLowerDisplayMagnitude(); + /** + * Returns the string in "plain" format (no exponential notation) using ASCII digits. + */ + public String toPlainString(); + /** * Like clone, but without the restrictions of the Cloneable interface clone. * @@ -184,6 +159,11 @@ public interface DecimalQuantity extends PluralRules.IFixedDecimal { */ public DecimalQuantity createCopy(); + /** + * Sets this instance to be equal to another instance. + * + * @param other The instance to copy from. + */ public void copyFrom(DecimalQuantity other); /** This method is for internal testing only. */ diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_AbstractBCD.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_AbstractBCD.java index c814ee09a48..3be79a22d13 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_AbstractBCD.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_AbstractBCD.java @@ -44,7 +44,7 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity { * @see #INFINITY_FLAG * @see #NAN_FLAG */ - protected int flags; + protected byte flags; protected static final int NEGATIVE_FLAG = 1; protected static final int INFINITY_FLAG = 2; @@ -168,12 +168,12 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity { } @Override - public void roundToIncrement(BigDecimal roundingInterval, MathContext mathContext) { + public void roundToIncrement(BigDecimal roundingIncrement, MathContext mathContext) { // TODO: Avoid converting back and forth to BigDecimal. BigDecimal temp = toBigDecimal(); temp = - temp.divide(roundingInterval, 0, mathContext.getRoundingMode()) - .multiply(roundingInterval) + temp.divide(roundingIncrement, 0, mathContext.getRoundingMode()) + .multiply(roundingIncrement) .round(mathContext); if (temp.signum() == 0) { setBcdToZero(); // keeps negative flag for -0.0 @@ -467,34 +467,35 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity { int delta = origDelta; setBcdToZero(); - // Call the slow oracle function - String temp = Double.toString(n); + // Call the slow oracle function (Double.toString in Java, sprintf in C++). + String dstr = Double.toString(n); - if (temp.indexOf('E') != -1) { + if (dstr.indexOf('E') != -1) { // Case 1: Exponential notation. - assert temp.indexOf('.') == 1; - int expPos = temp.indexOf('E'); - _setToLong(Long.parseLong(temp.charAt(0) + temp.substring(2, expPos))); - scale += Integer.parseInt(temp.substring(expPos + 1)) - (expPos - 1) + 1; - } else if (temp.charAt(0) == '0') { + assert dstr.indexOf('.') == 1; + int expPos = dstr.indexOf('E'); + _setToLong(Long.parseLong(dstr.charAt(0) + dstr.substring(2, expPos))); + scale += Integer.parseInt(dstr.substring(expPos + 1)) - (expPos - 1) + 1; + } else if (dstr.charAt(0) == '0') { // Case 2: Fraction-only number. - assert temp.indexOf('.') == 1; - _setToLong(Long.parseLong(temp.substring(2))); - scale += 2 - temp.length(); - } else if (temp.charAt(temp.length() - 1) == '0') { + assert dstr.indexOf('.') == 1; + _setToLong(Long.parseLong(dstr.substring(2))); + scale += 2 - dstr.length(); + } else if (dstr.charAt(dstr.length() - 1) == '0') { // Case 3: Integer-only number. // Note: this path should not normally happen, because integer-only numbers are captured // before the approximate double logic is performed. - assert temp.indexOf('.') == temp.length() - 2; - assert temp.length() - 2 <= 18; - _setToLong(Long.parseLong(temp.substring(0, temp.length() - 2))); + assert dstr.indexOf('.') == dstr.length() - 2; + assert dstr.length() - 2 <= 18; + _setToLong(Long.parseLong(dstr.substring(0, dstr.length() - 2))); // no need to adjust scale } else { // Case 4: Number with both a fraction and an integer. - int decimalPos = temp.indexOf('.'); - _setToLong(Long.parseLong(temp.substring(0, decimalPos) + temp.substring(decimalPos + 1))); - scale += decimalPos - temp.length() + 1; + int decimalPos = dstr.indexOf('.'); + _setToLong(Long.parseLong(dstr.substring(0, decimalPos) + dstr.substring(decimalPos + 1))); + scale += decimalPos - dstr.length() + 1; } + scale += delta; compact(); explicitExactDouble = true; @@ -640,6 +641,9 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity { return diff; } + private static final int SECTION_LOWER_EDGE = -1; + private static final int SECTION_UPPER_EDGE = -2; + @Override public void roundToMagnitude(int magnitude, MathContext mathContext) { // The position in the BCD at which rounding will be performed; digits to the right of position @@ -689,7 +693,7 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity { int p = safeSubtract(position, 2); int minP = Math.max(0, precision - 14); if (leadingDigit == 0) { - section = -1; + section = SECTION_LOWER_EDGE; for (; p >= minP; p--) { if (getDigitPos(p) != 0) { section = RoundingUtils.SECTION_LOWER; @@ -711,7 +715,7 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity { } } } else if (leadingDigit == 9) { - section = -2; + section = SECTION_UPPER_EDGE; for (; p >= minP; p--) { if (getDigitPos(p) != 9) { section = RoundingUtils.SECTION_UPPER; @@ -747,8 +751,8 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity { } // Good to continue rounding. - if (section == -1) section = RoundingUtils.SECTION_LOWER; - if (section == -2) section = RoundingUtils.SECTION_UPPER; + if (section == SECTION_LOWER_EDGE) section = RoundingUtils.SECTION_LOWER; + if (section == SECTION_UPPER_EDGE) section = RoundingUtils.SECTION_UPPER; } boolean roundDown = @@ -841,6 +845,20 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity { } } + @Override + public String toPlainString() { + // NOTE: This logic is duplicated between here and DecimalQuantity_SimpleStorage. + StringBuilder sb = new StringBuilder(); + if (isNegative()) { + sb.append('-'); + } + for (int m = getUpperDisplayMagnitude(); m >= getLowerDisplayMagnitude(); m--) { + sb.append(getDigit(m)); + if (m == 0) sb.append('.'); + } + return sb.toString(); + } + /** * Returns a single digit from the BCD list. No internal state is changed by calling this method. * diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_DualStorageBCD.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_DualStorageBCD.java index a41f4761ba0..7774d2ac93f 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_DualStorageBCD.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_DualStorageBCD.java @@ -144,11 +144,9 @@ public final class DecimalQuantity_DualStorageBCD extends DecimalQuantity_Abstra @Override protected void setBcdToZero() { if (usingBytes) { - for (int i = 0; i < precision; i++) { - bcdBytes[i] = (byte) 0; - } + bcdBytes = null; + usingBytes = false; } - usingBytes = false; bcdLong = 0L; scale = 0; precision = 0; @@ -166,7 +164,7 @@ public final class DecimalQuantity_DualStorageBCD extends DecimalQuantity_Abstra for (; n != 0; n /= 10, i--) { result = (result >>> 4) + (((long) n % 10) << 60); } - usingBytes = false; + assert !usingBytes; bcdLong = result >>> (i * 4); scale = 0; precision = 16 - i; @@ -181,7 +179,7 @@ public final class DecimalQuantity_DualStorageBCD extends DecimalQuantity_Abstra for (; n != 0L; n /= 10L, i++) { bcdBytes[i] = (byte) (n % 10); } - usingBytes = true; + assert usingBytes; scale = 0; precision = i; } else { @@ -191,7 +189,7 @@ public final class DecimalQuantity_DualStorageBCD extends DecimalQuantity_Abstra result = (result >>> 4) + ((n % 10) << 60); } assert i >= 0; - usingBytes = false; + assert !usingBytes; bcdLong = result >>> (i * 4); scale = 0; precision = 16 - i; @@ -209,7 +207,6 @@ public final class DecimalQuantity_DualStorageBCD extends DecimalQuantity_Abstra bcdBytes[i] = temp[1].byteValue(); n = temp[0]; } - usingBytes = true; scale = 0; precision = i; } @@ -218,17 +215,11 @@ public final class DecimalQuantity_DualStorageBCD extends DecimalQuantity_Abstra protected BigDecimal bcdToBigDecimal() { if (usingBytes) { // Converting to a string here is faster than doing BigInteger/BigDecimal arithmetic. - StringBuilder sb = new StringBuilder(); - if (isNegative()) sb.append('-'); - assert precision > 0; - for (int i = precision - 1; i >= 0; i--) { - sb.append(getDigitPos(i)); + BigDecimal result = new BigDecimal(toNumberString()); + if (isNegative()) { + result = result.negate(); } - if (scale != 0) { - sb.append('E'); - sb.append(scale); - } - return new BigDecimal(sb.toString()); + return result; } else { long tempLong = 0L; for (int shift = (precision - 1); shift >= 0; shift--) { @@ -289,13 +280,15 @@ public final class DecimalQuantity_DualStorageBCD extends DecimalQuantity_Abstra private void ensureCapacity(int capacity) { if (capacity == 0) return; - if (bcdBytes == null) { + int oldCapacity = usingBytes ? bcdBytes.length : 0; + if (!usingBytes) { bcdBytes = new byte[capacity]; - } else if (bcdBytes.length < capacity) { + } else if (oldCapacity < capacity) { byte[] bcd1 = new byte[capacity * 2]; - System.arraycopy(bcdBytes, 0, bcd1, 0, bcdBytes.length); + System.arraycopy(bcdBytes, 0, bcd1, 0, oldCapacity); bcdBytes = bcd1; } + usingBytes = true; } /** Switches the internal storage mechanism between the 64-bit long and the byte array. */ @@ -306,8 +299,8 @@ public final class DecimalQuantity_DualStorageBCD extends DecimalQuantity_Abstra for (int i = precision - 1; i >= 0; i--) { bcdLong <<= 4; bcdLong |= bcdBytes[i]; - bcdBytes[i] = 0; } + bcdBytes = null; usingBytes = false; } else { // Change from long to bytes @@ -316,19 +309,18 @@ public final class DecimalQuantity_DualStorageBCD extends DecimalQuantity_Abstra bcdBytes[i] = (byte) (bcdLong & 0xf); bcdLong >>>= 4; } - usingBytes = true; + assert usingBytes; } } @Override protected void copyBcdFrom(DecimalQuantity _other) { DecimalQuantity_DualStorageBCD other = (DecimalQuantity_DualStorageBCD) _other; + setBcdToZero(); if (other.usingBytes) { - usingBytes = true; ensureCapacity(other.precision); System.arraycopy(other.bcdBytes, 0, bcdBytes, 0, other.precision); } else { - usingBytes = false; bcdLong = other.bcdLong; } } @@ -387,29 +379,33 @@ public final class DecimalQuantity_DualStorageBCD extends DecimalQuantity_Abstra * @deprecated This API is ICU internal only. */ @Deprecated - public boolean usingBytes() { + public boolean isUsingBytes() { return usingBytes; } @Override public String toString() { - StringBuilder sb = new StringBuilder(); - if (usingBytes) { - for (int i = precision - 1; i >= 0; i--) { - sb.append(bcdBytes[i]); - } - } else { - sb.append(Long.toHexString(bcdLong)); - } return String.format( - "", - (lOptPos > 1000 ? "max" : String.valueOf(lOptPos)), + "", + (lOptPos > 1000 ? "999" : String.valueOf(lOptPos)), lReqPos, rReqPos, - (rOptPos < -1000 ? "min" : String.valueOf(rOptPos)), + (rOptPos < -1000 ? "-999" : String.valueOf(rOptPos)), (usingBytes ? "bytes" : "long"), - sb, - "E", - scale); + toNumberString()); + } + + public String toNumberString() { + StringBuilder sb = new StringBuilder(); + if (usingBytes) { + for (int i = precision - 1; i >= 0; i--) { + sb.append(bcdBytes[i]); + } + } else { + sb.append(Long.toHexString(bcdLong)); + } + sb.append("E"); + sb.append(scale); + return sb.toString(); } } diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_SimpleStorage.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_SimpleStorage.java index cf7929df666..e078020c3fb 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_SimpleStorage.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_SimpleStorage.java @@ -855,6 +855,20 @@ public class DecimalQuantity_SimpleStorage implements DecimalQuantity { return sb.toString(); } + @Override + public String toPlainString() { + // NOTE: This logic is duplicated between here and DecimalQuantity_AbstractBCD. + StringBuilder sb = new StringBuilder(); + if (isNegative()) { + sb.append('-'); + } + for (int m = getUpperDisplayMagnitude(); m >= getLowerDisplayMagnitude(); m--) { + sb.append(getDigit(m)); + if (m == 0) sb.append('.'); + } + return sb.toString(); + } + private static int toRange(int i, int lo, int hi) { if (i < lo) { return lo; diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/Modifier.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/Modifier.java index 577cb1e82fe..bff553636c8 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/Modifier.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/Modifier.java @@ -36,6 +36,11 @@ public interface Modifier { */ public int getPrefixLength(); + /** + * Returns the number of code points in the modifier, prefix plus suffix. + */ + public int getCodePointCount(); + /** * Whether this modifier is strong. If a modifier is strong, it should always be applied immediately and not allowed * to bubble up. With regard to padding, strong modifiers are considered to be on the inside of the prefix and diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/PatternStringParser.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/PatternStringParser.java index 208ad2c5a94..1bf8d5e62c6 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/PatternStringParser.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/PatternStringParser.java @@ -78,10 +78,6 @@ public class PatternStringParser { parseToExistingProperties(pattern, properties, PatternStringParser.IGNORE_ROUNDING_NEVER); } - ///////////////////////////////////////////////////// - /// BEGIN RECURSIVE DESCENT PARSER IMPLEMENTATION /// - ///////////////////////////////////////////////////// - /** * Contains raw information about the parsed decimal format pattern string. */ @@ -198,6 +194,10 @@ public class PatternStringParser { public long paddingEndpoints = 0; } + ///////////////////////////////////////////////////// + /// BEGIN RECURSIVE DESCENT PARSER IMPLEMENTATION /// + ///////////////////////////////////////////////////// + /** An internal class used for tracking the cursor during parsing of a pattern string. */ private static class ParserState { final String pattern; @@ -267,6 +267,9 @@ public class PatternStringParser { if (state.peek() != '*') { return; } + if (result.paddingLocation != null) { + throw state.toParseException("Cannot have multiple pad specifiers"); + } result.paddingLocation = paddingLocation; state.next(); // consume the '*' result.paddingEndpoints |= state.offset; @@ -519,7 +522,6 @@ public class PatternStringParser { // Note that most data from "negative" is ignored per the specification of DecimalFormat. ParsedSubpatternInfo positive = patternInfo.positive; - ParsedSubpatternInfo negative = patternInfo.negative; boolean ignoreRounding; if (_ignoreRounding == PatternStringParser.IGNORE_ROUNDING_NEVER) { @@ -627,7 +629,7 @@ public class PatternStringParser { String posSuffix = patternInfo.getString(0); // Padding settings - if (positive.paddingEndpoints != 0) { + if (positive.paddingLocation != null) { // The width of the positive prefix and suffix templates are included in the padding int paddingWidth = positive.widthExceptAffixes + AffixUtils.estimateLength(posPrefix) + AffixUtils.estimateLength(posSuffix); @@ -657,7 +659,7 @@ public class PatternStringParser { // negative prefix pattern, to prevent default values from overriding the pattern. properties.setPositivePrefixPattern(posPrefix); properties.setPositiveSuffixPattern(posSuffix); - if (negative != null) { + if (patternInfo.negative != null) { properties.setNegativePrefixPattern(patternInfo .getString(AffixPatternProvider.Flags.NEGATIVE_SUBPATTERN | AffixPatternProvider.Flags.PREFIX)); properties.setNegativeSuffixPattern(patternInfo.getString(AffixPatternProvider.Flags.NEGATIVE_SUBPATTERN)); diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/PatternStringUtils.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/PatternStringUtils.java index d1420418736..cfd23d0d03b 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/PatternStringUtils.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/PatternStringUtils.java @@ -55,8 +55,9 @@ public class PatternStringUtils { String nsp = properties.getNegativeSuffixPattern(); // Prefixes - if (ppp != null) + if (ppp != null) { sb.append(ppp); + } AffixUtils.escape(pp, sb); int afterPrefixPos = sb.length(); @@ -99,7 +100,7 @@ public class PatternStringUtils { digitsStringScale = -roundingInterval.scale(); // TODO: Check for DoS here? String str = roundingInterval.scaleByPowerOfTen(roundingInterval.scale()).toPlainString(); - if (str.charAt(0) == '\'') { + if (str.charAt(0) == '-') { // TODO: Unsupported operation exception or fail silently? digitsString.append(str, 1, str.length()); } else { @@ -147,8 +148,9 @@ public class PatternStringUtils { // Suffixes int beforeSuffixPos = sb.length(); - if (psp != null) + if (psp != null) { sb.append(psp); + } AffixUtils.escape(ps, sb); // Resolve Padding diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/modifiers/ConstantAffixModifier.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/modifiers/ConstantAffixModifier.java index 876fa72b404..e629712cd02 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/modifiers/ConstantAffixModifier.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/modifiers/ConstantAffixModifier.java @@ -69,6 +69,11 @@ public class ConstantAffixModifier implements Modifier { return prefix.length(); } + @Override + public int getCodePointCount() { + return prefix.codePointCount(0, prefix.length()) + suffix.codePointCount(0, suffix.length()); + } + @Override public boolean isStrong() { return strong; diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/modifiers/ConstantMultiFieldModifier.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/modifiers/ConstantMultiFieldModifier.java index 26f57d7870f..b07422407ea 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/modifiers/ConstantMultiFieldModifier.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/modifiers/ConstantMultiFieldModifier.java @@ -41,6 +41,12 @@ public class ConstantMultiFieldModifier implements Modifier { return prefixChars.length; } + @Override + public int getCodePointCount() { + return Character.codePointCount(prefixChars, 0, prefixChars.length) + + Character.codePointCount(suffixChars, 0, suffixChars.length); + } + @Override public boolean isStrong() { return strong; diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/modifiers/SimpleModifier.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/modifiers/SimpleModifier.java index f49360b9195..a318a4fe2c2 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/modifiers/SimpleModifier.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/modifiers/SimpleModifier.java @@ -53,6 +53,18 @@ public class SimpleModifier implements Modifier { return prefixLength; } + @Override + public int getCodePointCount() { + int count = 0; + if (prefixLength > 0) { + count += Character.codePointCount(compiledPattern, 2, 2 + prefixLength); + } + if (suffixLength > 0) { + count += Character.codePointCount(compiledPattern, 1 + suffixOffset, 1 + suffixOffset + suffixLength); + } + return count; + } + @Override public boolean isStrong() { return strong; diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/PluralRules.java b/icu4j/main/classes/core/src/com/ibm/icu/text/PluralRules.java index 7c3aa236f7d..d384c89426d 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/text/PluralRules.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/text/PluralRules.java @@ -489,6 +489,8 @@ public class PluralRules implements Serializable { } /** + * An interface to FixedDecimal, allowing for other implementations. + * * @internal * @deprecated This API is ICU internal only. */ @@ -526,54 +528,23 @@ public class PluralRules implements Serializable { @Deprecated public static class FixedDecimal extends Number implements Comparable, IFixedDecimal { private static final long serialVersionUID = -4756200506571685661L; - /** - * @internal - * @deprecated This API is ICU internal only. - */ - @Deprecated - public final double source; - /** - * @internal - * @deprecated This API is ICU internal only. - */ - @Deprecated - public final int visibleDecimalDigitCount; - /** - * @internal - * @deprecated This API is ICU internal only. - */ - @Deprecated - public final int visibleDecimalDigitCountWithoutTrailingZeros; - /** - * @internal - * @deprecated This API is ICU internal only. - */ - @Deprecated - public final long decimalDigits; - /** - * @internal - * @deprecated This API is ICU internal only. - */ - @Deprecated - public final long decimalDigitsWithoutTrailingZeros; - /** - * @internal - * @deprecated This API is ICU internal only. - */ - @Deprecated - public final long integerValue; - /** - * @internal - * @deprecated This API is ICU internal only. - */ - @Deprecated - public final boolean hasIntegerValue; - /** - * @internal - * @deprecated This API is ICU internal only. - */ - @Deprecated - public final boolean isNegative; + + final double source; + + final int visibleDecimalDigitCount; + + final int visibleDecimalDigitCountWithoutTrailingZeros; + + final long decimalDigits; + + final long decimalDigitsWithoutTrailingZeros; + + final long integerValue; + + final boolean hasIntegerValue; + + final boolean isNegative; + private final int baseFactor; /** diff --git a/icu4j/main/classes/core/src/newapi/CompactNotation.java b/icu4j/main/classes/core/src/newapi/CompactNotation.java index 17aa57b369e..3df09789b7d 100644 --- a/icu4j/main/classes/core/src/newapi/CompactNotation.java +++ b/icu4j/main/classes/core/src/newapi/CompactNotation.java @@ -19,7 +19,7 @@ import newapi.impl.CompactData; import newapi.impl.MicroProps; import newapi.impl.MicroPropsGenerator; import newapi.impl.MutablePatternModifier; -import newapi.impl.MutablePatternModifier.ImmutableMurkyModifier; +import newapi.impl.MutablePatternModifier.ImmutablePatternModifier; public class CompactNotation extends Notation { @@ -50,7 +50,7 @@ public class CompactNotation extends Notation { private static class CompactImpl implements MicroPropsGenerator { private static class CompactModInfo { - public ImmutableMurkyModifier mod; + public ImmutablePatternModifier mod; public int numDigits; } diff --git a/icu4j/main/classes/core/src/newapi/NumberFormatter.java b/icu4j/main/classes/core/src/newapi/NumberFormatter.java index 749f8ca82fd..f08e5680257 100644 --- a/icu4j/main/classes/core/src/newapi/NumberFormatter.java +++ b/icu4j/main/classes/core/src/newapi/NumberFormatter.java @@ -31,11 +31,6 @@ public final class NumberFormatter { AUTO, ALWAYS, NEVER, ACCOUNTING, ACCOUNTING_ALWAYS, } - public static UnlocalizedNumberFormatter fromSkeleton(String skeleton) { - // FIXME - throw new UnsupportedOperationException(); - } - public static UnlocalizedNumberFormatter with() { return BASE; } diff --git a/icu4j/main/classes/core/src/newapi/NumberFormatterImpl.java b/icu4j/main/classes/core/src/newapi/NumberFormatterImpl.java index 18432b0451c..9e02cf697a6 100644 --- a/icu4j/main/classes/core/src/newapi/NumberFormatterImpl.java +++ b/icu4j/main/classes/core/src/newapi/NumberFormatterImpl.java @@ -37,14 +37,14 @@ import newapi.impl.Padder; */ class NumberFormatterImpl { + /** Builds a "safe" MicroPropsGenerator, which is thread-safe and can be used repeatedly. */ public static NumberFormatterImpl fromMacros(MacroProps macros) { - // Build a "safe" MicroPropsGenerator, which is thread-safe and can be used repeatedly. MicroPropsGenerator microPropsGenerator = macrosToMicroGenerator(macros, true); return new NumberFormatterImpl(microPropsGenerator); } + /** Builds and evaluates an "unsafe" MicroPropsGenerator, which is cheaper but can be used only once. */ public static MicroProps applyStatic(MacroProps macros, DecimalQuantity inValue, NumberStringBuilder outString) { - // Build an "unsafe" MicroPropsGenerator, which is cheaper but can be used only once. MicroPropsGenerator microPropsGenerator = macrosToMicroGenerator(macros, false); MicroProps micros = microPropsGenerator.processQuantity(inValue); microsToString(micros, inValue, outString); @@ -55,8 +55,8 @@ class NumberFormatterImpl { final MicroPropsGenerator microPropsGenerator; - private NumberFormatterImpl(MicroPropsGenerator microsGenerator) { - this.microPropsGenerator = microsGenerator; + private NumberFormatterImpl(MicroPropsGenerator microPropsGenerator) { + this.microPropsGenerator = microPropsGenerator; } public MicroProps apply(DecimalQuantity inValue, NumberStringBuilder outString) { @@ -79,15 +79,14 @@ class NumberFormatterImpl { * If true, the returned MicroPropsGenerator will be thread-safe. If false, the returned value will * not be thread-safe, intended for a single "one-shot" use only. Building the thread-safe * object is more expensive. - * @return */ private static MicroPropsGenerator macrosToMicroGenerator(MacroProps macros, boolean safe) { String innerPattern = null; LongNameHandler longNames = null; - Rounder defaultRounding = Rounder.unlimited(); + Rounder defaultRounder = Rounder.unlimited(); Currency currency = DEFAULT_CURRENCY; - UnitWidth unitWidth = null; + UnitWidth unitWidth = (macros.unitWidth == null) ? UnitWidth.SHORT : macros.unitWidth; boolean perMille = false; PluralRules rules = macros.rules; @@ -121,29 +120,27 @@ class NumberFormatterImpl { } else { innerPattern = NumberFormat.getPatternForStyle(macros.loc, NumberFormat.CURRENCYSTYLE); } - defaultRounding = Rounder.currency(CurrencyUsage.STANDARD); + defaultRounder = Rounder.currency(CurrencyUsage.STANDARD); currency = (Currency) macros.unit; micros.useCurrency = true; - unitWidth = (macros.unitWidth == null) ? UnitWidth.SHORT : macros.unitWidth; } else if (macros.unit instanceof Currency) { // Currency long name innerPattern = NumberFormat.getPatternForStyle(macros.loc, NumberFormat.NUMBERSTYLE); - longNames = LongNameHandler.getCurrencyLongNameModifiers(macros.loc, (Currency) macros.unit); - defaultRounding = Rounder.currency(CurrencyUsage.STANDARD); + longNames = LongNameHandler.forCurrencyLongNames(macros.loc, (Currency) macros.unit); + defaultRounder = Rounder.currency(CurrencyUsage.STANDARD); currency = (Currency) macros.unit; micros.useCurrency = true; - unitWidth = UnitWidth.FULL_NAME; } else { // MeasureUnit innerPattern = NumberFormat.getPatternForStyle(macros.loc, NumberFormat.NUMBERSTYLE); - unitWidth = (macros.unitWidth == null) ? UnitWidth.SHORT : macros.unitWidth; - longNames = LongNameHandler.getMeasureUnitModifiers(macros.loc, macros.unit, unitWidth); + longNames = LongNameHandler.forMeasureUnit(macros.loc, macros.unit, unitWidth); } // Parse the pattern, which is used for grouping and affixes only. ParsedPatternInfo patternInfo = PatternStringParser.parseToPatternInfo(innerPattern); // Symbols + // NOTE: C++ has a special class, SymbolsWrapper, in MacroProps. Java has all the resolution logic here directly. if (macros.symbols == null) { micros.symbols = DecimalFormatSymbols.getInstance(macros.loc); } else if (macros.symbols instanceof DecimalFormatSymbols) { @@ -173,7 +170,7 @@ class NumberFormatterImpl { } else if (macros.notation instanceof CompactNotation) { micros.rounding = Rounder.COMPACT_STRATEGY; } else { - micros.rounding = Rounder.normalizeType(defaultRounding, currency); + micros.rounding = Rounder.normalizeType(defaultRounder, currency); } // Grouping strategy @@ -186,6 +183,13 @@ class NumberFormatterImpl { micros.grouping = Grouper.normalizeType(Grouper.defaults(), patternInfo); } + // Padding strategy + if (macros.padder != null) { + micros.padding = macros.padder; + } else { + micros.padding = Padder.none(); + } + // Inner modifier (scientific notation) if (macros.notation instanceof ScientificNotation) { chain = ((ScientificNotation) macros.notation).withLocaleData(micros.symbols, safe, chain); @@ -220,19 +224,12 @@ class NumberFormatterImpl { // Lazily create PluralRules rules = PluralRules.forLocale(macros.loc); } - chain = longNames.withLocaleData(rules, safe, chain); + chain = longNames.withLocaleData(rules, chain); } else { // No outer modifier required micros.modOuter = ConstantAffixModifier.EMPTY; } - // Padding strategy - if (macros.padder != null) { - micros.padding = macros.padder; - } else { - micros.padding = Padder.none(); - } - // Compact notation // NOTE: Compact notation can (but might not) override the middle modifier and rounding. // It therefore needs to go at the end of the chain. @@ -272,7 +269,14 @@ class NumberFormatterImpl { int length = writeNumber(micros, quantity, string); // NOTE: When range formatting is added, these modifiers can bubble up. // For now, apply them all here at once. - length += micros.padding.applyModsAndMaybePad(micros, string, 0, length); + // Always apply the inner modifier (which is "strong"). + length += micros.modInner.apply(string, 0, length); + if (micros.padding.isValid()) { + micros.padding.padAndApply(micros.modMiddle, micros.modOuter, string, 0, length); + } else { + length += micros.modMiddle.apply(string, 0, length); + length += micros.modOuter.apply(string, 0, length); + } } private static int writeNumber(MicroProps micros, DecimalQuantity quantity, NumberStringBuilder string) { diff --git a/icu4j/main/classes/core/src/newapi/NumberFormatterSettings.java b/icu4j/main/classes/core/src/newapi/NumberFormatterSettings.java index c81e610eec2..694448b345c 100644 --- a/icu4j/main/classes/core/src/newapi/NumberFormatterSettings.java +++ b/icu4j/main/classes/core/src/newapi/NumberFormatterSettings.java @@ -364,6 +364,7 @@ public abstract class NumberFormatterSettings data; - /* unsafe */ PluralRules rules; - /* unsafe */ MicroPropsGenerator parent; + private final Map modifiers; + private PluralRules rules; + private MicroPropsGenerator parent; - private LongNameHandler(Map data) { - this.data = data; + private LongNameHandler(Map modifiers) { + this.modifiers = modifiers; } /** For use by the "safe" code path */ private LongNameHandler(LongNameHandler other) { - this.data = other.data; + this.modifiers = other.modifiers; } - public static LongNameHandler getCurrencyLongNameModifiers(ULocale loc, Currency currency) { + public static LongNameHandler forCurrencyLongNames(ULocale loc, Currency currency) { Map data = CurrencyData.provider.getInstance(loc, true).getUnitPatterns(); - Map result = new EnumMap(StandardPlural.class); + Map result = new EnumMap(StandardPlural.class); StringBuilder sb = new StringBuilder(); for (Map.Entry e : data.entrySet()) { String pluralKeyword = e.getKey(); @@ -45,23 +49,28 @@ public class LongNameHandler implements MicroPropsGenerator { String simpleFormat = e.getValue(); // e.g., "{0} {1}" simpleFormat = simpleFormat.replace("{1}", longName); String compiled = SimpleFormatterImpl.compileToStringMinMaxArguments(simpleFormat, sb, 1, 1); - Modifier mod = new SimpleModifier(compiled, Field.CURRENCY, false); + SimpleModifier mod = new SimpleModifier(compiled, Field.CURRENCY, false); result.put(plural, mod); } return new LongNameHandler(result); } - public static LongNameHandler getMeasureUnitModifiers(ULocale loc, MeasureUnit unit, UnitWidth width) { - Map simpleFormats = MeasureData.getMeasureData(loc, unit, width); - Map result = new EnumMap(StandardPlural.class); + public static LongNameHandler forMeasureUnit(ULocale loc, MeasureUnit unit, UnitWidth width) { + Map simpleFormats = getMeasureData(loc, unit, width); + Map result = new EnumMap(StandardPlural.class); StringBuilder sb = new StringBuilder(); for (StandardPlural plural : StandardPlural.VALUES) { - if (simpleFormats.get(plural) == null) { - plural = StandardPlural.OTHER; - } String simpleFormat = simpleFormats.get(plural); + if (simpleFormat == null) { + simpleFormat = simpleFormats.get(StandardPlural.OTHER); + } + if (simpleFormat == null) { + // There should always be data in the "other" plural variant. + throw new ICUException("Could not find data in 'other' plural variant for unit " + unit); + } String compiled = SimpleFormatterImpl.compileToStringMinMaxArguments(simpleFormat, sb, 1, 1); - Modifier mod = new SimpleModifier(compiled, Field.CURRENCY, false); + // TODO: What field to use for units? + SimpleModifier mod = new SimpleModifier(compiled, null, false); result.put(plural, mod); } return new LongNameHandler(result); @@ -72,26 +81,14 @@ public class LongNameHandler implements MicroPropsGenerator { * * @param rules * The PluralRules instance to reference. - * @param safe - * If true, creates a new object to insert into the quantity chain. If false, re-uses this - * object in the quantity chain. * @param parent * The old head of the quantity chain. * @return The new head of the quantity chain. */ - public MicroPropsGenerator withLocaleData(PluralRules rules, boolean safe, MicroPropsGenerator parent) { - if (safe) { - // Safe code path: return a new object - LongNameHandler copy = new LongNameHandler(this); - copy.rules = rules; - copy.parent = parent; - return copy; - } else { - // Unsafe code path: re-use this object! - this.rules = rules; - this.parent = parent; - return this; - } + public MicroPropsGenerator withLocaleData(PluralRules rules, MicroPropsGenerator parent) { + this.rules = rules; + this.parent = parent; + return this; } @Override @@ -100,7 +97,60 @@ public class LongNameHandler implements MicroPropsGenerator { // TODO: Avoid the copy here? DecimalQuantity copy = quantity.createCopy(); micros.rounding.apply(copy); - micros.modOuter = data.get(copy.getStandardPlural(rules)); + micros.modOuter = modifiers.get(copy.getStandardPlural(rules)); return micros; } + + /////////////////////////////////////// + /// BEGIN MEASURE UNIT DATA LOADING /// + /////////////////////////////////////// + + private static final class MeasureUnitSink extends UResource.Sink { + + Map output; + + public MeasureUnitSink(Map output) { + this.output = output; + } + + @Override + public void put(UResource.Key key, UResource.Value value, boolean noFallback) { + UResource.Table pluralsTable = value.getTable(); + for (int i = 0; pluralsTable.getKeyAndValue(i, key, value); ++i) { + if (key.contentEquals("dnam") || key.contentEquals("per")) { + continue; + } + StandardPlural plural = StandardPlural.fromString(key); + if (output.containsKey(plural)) { + continue; + } + String formatString = value.getString(); + output.put(plural, formatString); + } + } + } + + private static Map getMeasureData(ULocale locale, MeasureUnit unit, UnitWidth width) { + ICUResourceBundle resource = (ICUResourceBundle) UResourceBundle.getBundleInstance(ICUData.ICU_UNIT_BASE_NAME, + locale); + StringBuilder key = new StringBuilder(); + key.append("units"); + if (width == UnitWidth.NARROW) { + key.append("Narrow"); + } else if (width == UnitWidth.SHORT) { + key.append("Short"); + } + key.append("/"); + key.append(unit.getType()); + key.append("/"); + key.append(unit.getSubtype()); + Map output = new EnumMap(StandardPlural.class); + MeasureUnitSink sink = new MeasureUnitSink(output); + resource.getAllItemsWithFallback(key.toString(), sink); + return output; + } + + ///////////////////////////////////// + /// END MEASURE UNIT DATA LOADING /// + ///////////////////////////////////// } diff --git a/icu4j/main/classes/core/src/newapi/impl/MeasureData.java b/icu4j/main/classes/core/src/newapi/impl/MeasureData.java deleted file mode 100644 index c45220478be..00000000000 --- a/icu4j/main/classes/core/src/newapi/impl/MeasureData.java +++ /dev/null @@ -1,65 +0,0 @@ -// © 2017 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html#License -package newapi.impl; - -import java.util.EnumMap; -import java.util.Map; - -import com.ibm.icu.impl.ICUData; -import com.ibm.icu.impl.ICUResourceBundle; -import com.ibm.icu.impl.StandardPlural; -import com.ibm.icu.impl.UResource; -import com.ibm.icu.util.MeasureUnit; -import com.ibm.icu.util.ULocale; -import com.ibm.icu.util.UResourceBundle; - -import newapi.NumberFormatter.UnitWidth; - -public class MeasureData { - - private static final class ShanesMeasureUnitSink extends UResource.Sink { - - Map output; - - public ShanesMeasureUnitSink(Map output) { - this.output = output; - } - - @Override - public void put(UResource.Key key, UResource.Value value, boolean noFallback) { - UResource.Table pluralsTable = value.getTable(); - for (int i1 = 0; pluralsTable.getKeyAndValue(i1, key, value); ++i1) { - if (key.contentEquals("dnam") || key.contentEquals("per")) { - continue; - } - StandardPlural plural = StandardPlural.fromString(key); - if (output.containsKey(plural)) { - continue; - } - String formatString = value.getString(); - output.put(plural, formatString); - } - } - } - - public static Map getMeasureData( - ULocale locale, MeasureUnit unit, UnitWidth width) { - ICUResourceBundle resource = - (ICUResourceBundle) UResourceBundle.getBundleInstance(ICUData.ICU_UNIT_BASE_NAME, locale); - StringBuilder key = new StringBuilder(); - key.append("units"); - if (width == UnitWidth.NARROW) { - key.append("Narrow"); - } else if (width == UnitWidth.SHORT) { - key.append("Short"); - } - key.append("/"); - key.append(unit.getType()); - key.append("/"); - key.append(unit.getSubtype()); - Map output = new EnumMap(StandardPlural.class); - ShanesMeasureUnitSink sink = new ShanesMeasureUnitSink(output); - resource.getAllItemsWithFallback(key.toString(), sink); - return output; - } -} diff --git a/icu4j/main/classes/core/src/newapi/impl/MicroProps.java b/icu4j/main/classes/core/src/newapi/impl/MicroProps.java index 82c6541f442..03f52da2d63 100644 --- a/icu4j/main/classes/core/src/newapi/impl/MicroProps.java +++ b/icu4j/main/classes/core/src/newapi/impl/MicroProps.java @@ -29,6 +29,7 @@ public class MicroProps implements Cloneable, MicroPropsGenerator { public int multiplier; public boolean useCurrency; + // Internal fields: private final boolean immutable; private volatile boolean exhausted; diff --git a/icu4j/main/classes/core/src/newapi/impl/MutablePatternModifier.java b/icu4j/main/classes/core/src/newapi/impl/MutablePatternModifier.java index abd7cedf4db..497dddbc787 100644 --- a/icu4j/main/classes/core/src/newapi/impl/MutablePatternModifier.java +++ b/icu4j/main/classes/core/src/newapi/impl/MutablePatternModifier.java @@ -6,16 +6,16 @@ import com.ibm.icu.impl.StandardPlural; import com.ibm.icu.impl.number.AffixUtils; import com.ibm.icu.impl.number.AffixUtils.SymbolProvider; import com.ibm.icu.impl.number.DecimalQuantity; -import com.ibm.icu.impl.number.PatternStringParser; import com.ibm.icu.impl.number.Modifier; import com.ibm.icu.impl.number.NumberStringBuilder; +import com.ibm.icu.impl.number.ParameterizedModifier; +import com.ibm.icu.impl.number.PatternStringParser; import com.ibm.icu.impl.number.modifiers.ConstantMultiFieldModifier; import com.ibm.icu.impl.number.modifiers.CurrencySpacingEnabledModifier; import com.ibm.icu.text.DecimalFormatSymbols; import com.ibm.icu.text.PluralRules; import com.ibm.icu.util.Currency; -import newapi.NumberFormatter; import newapi.NumberFormatter.SignDisplay; import newapi.NumberFormatter.UnitWidth; @@ -53,9 +53,7 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, CharSeq // Symbol details DecimalFormatSymbols symbols; UnitWidth unitWidth; - String currency1; - String currency2; - String[] currency3; + Currency currency; PluralRules rules; // Number details @@ -84,7 +82,8 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, CharSeq /** * Sets a reference to the parsed decimal format pattern, usually obtained from - * {@link PatternStringParser#parseToPatternInfo(String)}, but any implementation of {@link AffixPatternProvider} is accepted. + * {@link PatternStringParser#parseToPatternInfo(String)}, but any implementation of {@link AffixPatternProvider} is + * accepted. */ public void setPatternInfo(AffixPatternProvider patternInfo) { this.patternInfo = patternInfo; @@ -109,8 +108,7 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, CharSeq * @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. + * The currency to be used when substituting currency values into the affixes. * @param unitWidth * The width used to render currencies. * @param rules @@ -120,19 +118,9 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, CharSeq public void setSymbols(DecimalFormatSymbols symbols, Currency currency, UnitWidth unitWidth, PluralRules rules) { assert (rules != null) == needsPlurals(); this.symbols = symbols; + this.currency = currency; this.unitWidth = unitWidth; this.rules = rules; - - currency1 = currency.getName(symbols.getULocale(), Currency.SYMBOL_NAME, null); - currency2 = currency.getCurrencyCode(); - - if (rules != null) { - currency3 = new String[StandardPlural.COUNT]; - for (StandardPlural plural : StandardPlural.VALUES) { - currency3[plural.ordinal()] = currency.getName(symbols.getULocale(), Currency.PLURAL_LONG_NAME, - plural.getKeyword(), null); - } - } } /** @@ -168,7 +156,7 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, CharSeq * * @return An immutable that supports both positive and negative numbers. */ - public ImmutableMurkyModifier createImmutable() { + public ImmutablePatternModifier createImmutable() { return createImmutableAndChain(null); } @@ -181,32 +169,43 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, CharSeq * The QuantityChain to which to chain this immutable. * @return An immutable that supports both positive and negative numbers. */ - public ImmutableMurkyModifier createImmutableAndChain(MicroPropsGenerator parent) { + public ImmutablePatternModifier createImmutableAndChain(MicroPropsGenerator parent) { NumberStringBuilder a = new NumberStringBuilder(); NumberStringBuilder b = new NumberStringBuilder(); if (needsPlurals()) { // Slower path when we require the plural keyword. - Modifier[] mods = new Modifier[ImmutableMurkyModifierWithPlurals.getModsLength()]; + ParameterizedModifier pm = new ParameterizedModifier(); for (StandardPlural plural : StandardPlural.VALUES) { setNumberProperties(false, plural); - Modifier positive = createConstantModifier(a, b); + pm.setModifier(false, plural, createConstantModifier(a, b)); setNumberProperties(true, plural); - Modifier negative = createConstantModifier(a, b); - mods[ImmutableMurkyModifierWithPlurals.getModIndex(false, plural)] = positive; - mods[ImmutableMurkyModifierWithPlurals.getModIndex(true, plural)] = negative; + pm.setModifier(true, plural, createConstantModifier(a, b)); } - return new ImmutableMurkyModifierWithPlurals(mods, rules, parent); + pm.freeze(); + return new ImmutablePatternModifier(pm, rules, parent); } else { // Faster path when plural keyword is not needed. setNumberProperties(false, null); Modifier positive = createConstantModifier(a, b); setNumberProperties(true, null); Modifier negative = createConstantModifier(a, b); - return new ImmutableMurkyModifierWithoutPlurals(positive, negative, parent); + ParameterizedModifier pm = new ParameterizedModifier(positive, negative); + return new ImmutablePatternModifier(pm, null, parent); } } - private Modifier createConstantModifier(NumberStringBuilder a, NumberStringBuilder b) { + /** + * Uses the current properties to create a single {@link ConstantMultiFieldModifier} with currency spacing support + * if required. + * + * @param a + * A working NumberStringBuilder object; passed from the outside to prevent the need to create many new + * instances if this method is called in a loop. + * @param b + * Another working NumberStringBuilder object. + * @return The constant modifier object. + */ + private ConstantMultiFieldModifier createConstantModifier(NumberStringBuilder a, NumberStringBuilder b) { insertPrefix(a.clear(), 0); insertSuffix(b.clear(), 0); if (patternInfo.hasCurrencySign()) { @@ -216,79 +215,38 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, CharSeq } } - public static interface ImmutableMurkyModifier extends MicroPropsGenerator { - public void applyToMicros(MicroProps micros, DecimalQuantity quantity); - } - - public static class ImmutableMurkyModifierWithoutPlurals implements ImmutableMurkyModifier { - final Modifier positive; - final Modifier negative; - final MicroPropsGenerator parent; - - public ImmutableMurkyModifierWithoutPlurals(Modifier positive, Modifier negative, MicroPropsGenerator parent) { - this.positive = positive; - this.negative = negative; - this.parent = parent; - } - - @Override - public MicroProps processQuantity(DecimalQuantity quantity) { - assert parent != null; - MicroProps micros = parent.processQuantity(quantity); - applyToMicros(micros, quantity); - return micros; - } - - @Override - public void applyToMicros(MicroProps micros, DecimalQuantity quantity) { - if (quantity.isNegative()) { - micros.modMiddle = negative; - } else { - micros.modMiddle = positive; - } - } - } - - public static class ImmutableMurkyModifierWithPlurals implements ImmutableMurkyModifier { - final Modifier[] mods; + public static class ImmutablePatternModifier implements MicroPropsGenerator { + final ParameterizedModifier pm; final PluralRules rules; final MicroPropsGenerator parent; - public ImmutableMurkyModifierWithPlurals(Modifier[] mods, PluralRules rules, MicroPropsGenerator parent) { - assert mods.length == getModsLength(); - assert rules != null; - this.mods = mods; + ImmutablePatternModifier(ParameterizedModifier pm, PluralRules rules, MicroPropsGenerator parent) { + this.pm = pm; this.rules = rules; this.parent = parent; } - public static int getModsLength() { - return 2 * StandardPlural.COUNT; - } - - public static int getModIndex(boolean isNegative, StandardPlural plural) { - return plural.ordinal() * 2 + (isNegative ? 1 : 0); - } - @Override public MicroProps processQuantity(DecimalQuantity quantity) { - assert parent != null; MicroProps micros = parent.processQuantity(quantity); applyToMicros(micros, quantity); return micros; } - @Override public void applyToMicros(MicroProps micros, DecimalQuantity quantity) { - // TODO: Fix this. Avoid the copy. - DecimalQuantity copy = quantity.createCopy(); - copy.roundToInfinity(); - StandardPlural plural = copy.getStandardPlural(rules); - Modifier mod = mods[getModIndex(quantity.isNegative(), plural)]; - micros.modMiddle = mod; + if (rules == null) { + micros.modMiddle = pm.getModifier(quantity.isNegative()); + } else { + // TODO: Fix this. Avoid the copy. + DecimalQuantity copy = quantity.createCopy(); + copy.roundToInfinity(); + StandardPlural plural = copy.getStandardPlural(rules); + micros.modMiddle = pm.getModifier(quantity.isNegative(), plural); + } } } + /** Used by the unsafe code path. */ public MicroPropsGenerator addToChain(MicroPropsGenerator parent) { this.parent = parent; return this; @@ -320,8 +278,23 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, CharSeq @Override public int getPrefixLength() { - NumberStringBuilder dummy = new NumberStringBuilder(); - return insertPrefix(dummy, 0); + // Enter and exit CharSequence Mode to get the length. + enterCharSequenceMode(true); + int result = AffixUtils.unescapedCodePointCount(this, this); // prefix length + exitCharSequenceMode(); + return result; + } + + @Override + public int getCodePointCount() { + // Enter and exit CharSequence Mode to get the length. + enterCharSequenceMode(true); + int result = AffixUtils.unescapedCodePointCount(this, this); // prefix length + exitCharSequenceMode(); + enterCharSequenceMode(false); + result += AffixUtils.unescapedCodePointCount(this, this); // suffix length + exitCharSequenceMode(); + return result; } @Override @@ -343,6 +316,9 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, CharSeq return length; } + /** + * Returns the string that substitutes a given symbol type in a pattern. + */ @Override public CharSequence getSymbol(int type) { switch (type) { @@ -355,23 +331,20 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, CharSeq case AffixUtils.TYPE_PERMILLE: return symbols.getPerMillString(); case AffixUtils.TYPE_CURRENCY_SINGLE: - // FormatWidth ISO overrides the singular currency symbol + // UnitWidth ISO overrides the singular currency symbol. if (unitWidth == UnitWidth.ISO_CODE) { - return currency2; + return currency.getCurrencyCode(); } else { - return currency1; + return currency.getName(symbols.getULocale(), Currency.SYMBOL_NAME, null); } case AffixUtils.TYPE_CURRENCY_DOUBLE: - return currency2; + return currency.getCurrencyCode(); case AffixUtils.TYPE_CURRENCY_TRIPLE: - // NOTE: This is the code path only for patterns containing "". - // Most plural currencies are formatted in DataUtils. + // NOTE: This is the code path only for patterns containing "¤¤¤". + // Plural currencies set via the API are formatted in LongNameHandler. + // This code path is used by DecimalFormat via CurrencyPluralInfo. assert plural != null; - if (currency3 == null) { - return currency2; - } else { - return currency3[plural.ordinal()]; - } + return currency.getName(symbols.getULocale(), Currency.PLURAL_LONG_NAME, plural.getKeyword(), null); case AffixUtils.TYPE_CURRENCY_QUAD: return "\uFFFD"; case AffixUtils.TYPE_CURRENCY_QUINT: @@ -391,7 +364,7 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, CharSeq && (signDisplay == SignDisplay.ALWAYS || signDisplay == SignDisplay.ACCOUNTING_ALWAYS) && patternInfo.positiveHasPlusSign() == false; - // Should we use the negative affix pattern? (If not, we will use the positive one) + // Should we use the affix from the negative subpattern? (If not, we will use the positive subpattern.) boolean useNegativeAffixPattern = patternInfo.hasNegativeSubpattern() && (isNegative || (patternInfo.negativeHasMinusSign() && plusReplacesMinusSign)); @@ -428,13 +401,8 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, CharSeq @Override public int length() { - if (inCharSequenceMode) { - return length; - } else { - NumberStringBuilder sb = new NumberStringBuilder(20); - apply(sb, 0, 0); - return sb.length(); - } + assert inCharSequenceMode; + return length; } @Override @@ -459,7 +427,7 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, CharSeq @Override public CharSequence subSequence(int start, int end) { - // Should never be called in normal circumstances + // Never called by AffixUtils throw new AssertionError(); } } diff --git a/icu4j/main/classes/core/src/newapi/impl/Padder.java b/icu4j/main/classes/core/src/newapi/impl/Padder.java index 91b2099903b..9f9a0a6cd92 100644 --- a/icu4j/main/classes/core/src/newapi/impl/Padder.java +++ b/icu4j/main/classes/core/src/newapi/impl/Padder.java @@ -2,6 +2,7 @@ // License & terms of use: http://www.unicode.org/copyright.html#License package newapi.impl; +import com.ibm.icu.impl.number.Modifier; import com.ibm.icu.impl.number.NumberStringBuilder; public class Padder { @@ -71,52 +72,46 @@ public class Padder { } } - public int applyModsAndMaybePad(MicroProps micros, NumberStringBuilder string, int leftIndex, int rightIndex) { - // Apply modInner (scientific notation) before padding - int innerLength = micros.modInner.apply(string, leftIndex, rightIndex); - - // No padding; apply the mods and leave. - if (targetWidth < 0) { - return applyMicroMods(micros, string, leftIndex, rightIndex + innerLength); - } + public boolean isValid() { + return targetWidth > 0; + } - // Estimate the padding width needed. - // TODO: Make this more efficient (less copying) - // TODO: How to handle when padding is inserted between a currency sign and the number - // when currency spacing is in play? - NumberStringBuilder backup = new NumberStringBuilder(string); - int length = innerLength + applyMicroMods(micros, string, leftIndex, rightIndex + innerLength); - int requiredPadding = targetWidth - string.codePointCount(); + public int padAndApply(Modifier mod1, Modifier mod2, NumberStringBuilder string, int leftIndex, int rightIndex) { + int modLength = mod1.getCodePointCount() + mod2.getCodePointCount(); + int requiredPadding = targetWidth - modLength - string.codePointCount(); + assert leftIndex == 0 && rightIndex == string.length(); // fix the previous line to remove this assertion + int length = 0; if (requiredPadding <= 0) { // Padding is not required. + length += mod1.apply(string, leftIndex, rightIndex); + length += mod2.apply(string, leftIndex, rightIndex + length); return length; } - length = innerLength; - string.copyFrom(backup); if (position == PadPosition.AFTER_PREFIX) { length += addPaddingHelper(paddingString, requiredPadding, string, leftIndex); } else if (position == PadPosition.BEFORE_SUFFIX) { length += addPaddingHelper(paddingString, requiredPadding, string, rightIndex + length); } - length += applyMicroMods(micros, string, leftIndex, rightIndex + length); + length += mod1.apply(string, leftIndex, rightIndex + length); + length += mod2.apply(string, leftIndex, rightIndex + length); if (position == PadPosition.BEFORE_PREFIX) { - length = addPaddingHelper(paddingString, requiredPadding, string, leftIndex); + length += addPaddingHelper(paddingString, requiredPadding, string, leftIndex); } else if (position == PadPosition.AFTER_SUFFIX) { - length = addPaddingHelper(paddingString, requiredPadding, string, rightIndex + length); + length += addPaddingHelper(paddingString, requiredPadding, string, rightIndex + length); } // The length might not be exactly right due to currency spacing. // Make an adjustment if needed. while (string.codePointCount() < targetWidth) { - int insertIndex; + int insertIndex = mod1.getPrefixLength() + mod2.getPrefixLength(); switch (position) { case AFTER_PREFIX: - insertIndex = leftIndex + length; + insertIndex += leftIndex; break; case BEFORE_SUFFIX: - insertIndex = rightIndex + length; + insertIndex += rightIndex; break; default: // Should not happen since currency spacing is always on the inside. @@ -128,12 +123,6 @@ public class Padder { return length; } - private static int applyMicroMods(MicroProps micros, NumberStringBuilder string, int leftIndex, int rightIndex) { - int length = micros.modMiddle.apply(string, leftIndex, rightIndex); - length += micros.modOuter.apply(string, leftIndex, rightIndex + length); - return length; - } - private static int addPaddingHelper(String paddingString, int requiredPadding, NumberStringBuilder string, int index) { for (int i = 0; i < requiredPadding; i++) { 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 2c51bf0dd34..4b7ea8b21d2 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 @@ -9,17 +9,17 @@ import java.text.ParsePosition; import org.junit.Test; import com.ibm.icu.dev.test.TestUtil; +import com.ibm.icu.impl.number.DecimalFormatProperties; import com.ibm.icu.impl.number.Parse.ParseMode; import com.ibm.icu.impl.number.PatternStringParser; import com.ibm.icu.impl.number.PatternStringUtils; -import com.ibm.icu.impl.number.DecimalFormatProperties; import com.ibm.icu.text.DecimalFormatSymbols; import com.ibm.icu.text.DecimalFormat_ICU58; import com.ibm.icu.util.CurrencyAmount; import com.ibm.icu.util.ULocale; import newapi.LocalizedNumberFormatter; -import newapi.NumberPropertyMapper; +import newapi.NumberFormatter; import newapi.impl.Padder.PadPosition; public class NumberFormatDataDrivenTest { @@ -556,7 +556,7 @@ public class NumberFormatDataDrivenTest { : PatternStringParser.IGNORE_ROUNDING_NEVER); propertiesFromTuple(tuple, properties); DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(locale); - LocalizedNumberFormatter fmt = NumberPropertyMapper.create(properties, symbols).locale(locale); + LocalizedNumberFormatter fmt = NumberFormatter.fromDecimalFormat(properties, symbols, null).locale(locale); Number number = toNumber(tuple.format); String expected = tuple.output; String actual = fmt.format(number).toString(); diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/PluralRulesTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/PluralRulesTest.java index 3de97b1476d..e262a741127 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/PluralRulesTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/PluralRulesTest.java @@ -72,13 +72,13 @@ public class PluralRulesTest extends TestFmwk { }) { FixedDecimal fd = new FixedDecimal(testDouble[0]); assertEquals(testDouble[0] + "=doubleValue()", testDouble[0], fd.doubleValue()); - assertEquals(testDouble[0] + " decimalDigits", (int) testDouble[1], fd.decimalDigits); - assertEquals(testDouble[0] + " visibleDecimalDigitCount", (int) testDouble[2], fd.visibleDecimalDigitCount); + assertEquals(testDouble[0] + " decimalDigits", (int) testDouble[1], fd.getDecimalDigits()); + assertEquals(testDouble[0] + " visibleDecimalDigitCount", (int) testDouble[2], fd.getVisibleDecimalDigitCount()); assertEquals(testDouble[0] + " decimalDigitsWithoutTrailingZeros", (int) testDouble[1], - fd.decimalDigitsWithoutTrailingZeros); + fd.getDecimalDigitsWithoutTrailingZeros()); assertEquals(testDouble[0] + " visibleDecimalDigitCountWithoutTrailingZeros", (int) testDouble[2], - fd.visibleDecimalDigitCountWithoutTrailingZeros); - assertEquals(testDouble[0] + " integerValue", (long) testDouble[3], fd.integerValue); + fd.getVisibleDecimalDigitCountWithoutTrailingZeros()); + assertEquals(testDouble[0] + " integerValue", (long) testDouble[3], fd.getIntegerValue()); } for (ULocale locale : new ULocale[] { ULocale.ENGLISH, new ULocale("cy"), new ULocale("ar") }) { @@ -862,14 +862,14 @@ public class PluralRulesTest extends TestFmwk { enum StandardPluralCategories { zero, one, two, few, many, other; /** - * + * */ private static final Set ALL = Collections.unmodifiableSet(EnumSet .allOf(StandardPluralCategories.class)); /** * Return a mutable set - * + * * @param source * @return */ @@ -882,6 +882,7 @@ public class PluralRulesTest extends TestFmwk { } static final Comparator> SHORTEST_FIRST = new Comparator>() { + @Override public int compare(Set arg0, Set arg1) { int diff = arg0.size() - arg1.size(); if (diff != 0) { @@ -927,6 +928,7 @@ public class PluralRulesTest extends TestFmwk { } private static final Comparator PLURAL_RULE_COMPARATOR = new Comparator() { + @Override public int compare(PluralRules o1, PluralRules o2) { return o1.compareTo(o2); } @@ -1066,12 +1068,14 @@ public class PluralRulesTest extends TestFmwk { } public static class FixedDecimalHandler implements SerializableTestUtility.Handler { + @Override public Object[] getTestObjects() { FixedDecimal items[] = { new FixedDecimal(3d), new FixedDecimal(3d, 2), new FixedDecimal(3.1d, 1), new FixedDecimal(3.1d, 2), }; return items; } + @Override public boolean hasSameBehavior(Object a, Object b) { FixedDecimal a1 = (FixedDecimal) a; FixedDecimal b1 = (FixedDecimal) b; diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/AffixPatternUtilsTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/AffixUtilsTest.java similarity index 95% rename from icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/AffixPatternUtilsTest.java rename to icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/AffixUtilsTest.java index 87a0ae06590..0af776c2205 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/AffixPatternUtilsTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/AffixUtilsTest.java @@ -13,7 +13,7 @@ import com.ibm.icu.impl.number.NumberStringBuilder; import com.ibm.icu.text.DecimalFormatSymbols; import com.ibm.icu.util.ULocale; -public class AffixPatternUtilsTest { +public class AffixUtilsTest { private static final SymbolProvider DEFAULT_SYMBOL_PROVIDER = new SymbolProvider() { @@ -127,6 +127,9 @@ public class AffixPatternUtilsTest { String actual = unescapeWithDefaults(input); assertEquals("Output on <" + input + ">", output, actual); + + int ulength = AffixUtils.unescapedCodePointCount(input, DEFAULT_SYMBOL_PROVIDER); + assertEquals("Unescaped length on <" + input + ">", output.length(), ulength); } } @@ -221,7 +224,8 @@ public class AffixPatternUtilsTest { private static String unescapeWithDefaults(String input) { NumberStringBuilder nsb = new NumberStringBuilder(); - AffixUtils.unescape(input, nsb, 0, DEFAULT_SYMBOL_PROVIDER); + int length = AffixUtils.unescape(input, nsb, 0, DEFAULT_SYMBOL_PROVIDER); + assertEquals("Return value of unescape", nsb.length(), length); return nsb.toString(); } } diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/FormatQuantityTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/DecimalQuantityTest.java similarity index 80% rename from icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/FormatQuantityTest.java rename to icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/DecimalQuantityTest.java index 0943a5fdfbc..b2485ed8a1c 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/FormatQuantityTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/DecimalQuantityTest.java @@ -10,25 +10,27 @@ import java.util.ArrayList; import java.util.List; import java.util.Random; +import org.junit.Ignore; import org.junit.Test; import com.ibm.icu.dev.test.TestFmwk; +import com.ibm.icu.impl.number.DecimalFormatProperties; import com.ibm.icu.impl.number.DecimalQuantity; -import com.ibm.icu.impl.number.DecimalQuantity_SimpleStorage; import com.ibm.icu.impl.number.DecimalQuantity_64BitBCD; import com.ibm.icu.impl.number.DecimalQuantity_ByteArrayBCD; import com.ibm.icu.impl.number.DecimalQuantity_DualStorageBCD; -import com.ibm.icu.impl.number.DecimalFormatProperties; +import com.ibm.icu.impl.number.DecimalQuantity_SimpleStorage; import com.ibm.icu.text.CompactDecimalFormat.CompactStyle; import com.ibm.icu.text.DecimalFormatSymbols; import com.ibm.icu.util.ULocale; import newapi.LocalizedNumberFormatter; -import newapi.NumberPropertyMapper; +import newapi.NumberFormatter; /** TODO: This is a temporary name for this class. Suggestions for a better name? */ public class DecimalQuantityTest extends TestFmwk { + @Ignore @Test public void testBehavior() throws ParseException { @@ -38,24 +40,24 @@ public class DecimalQuantityTest extends TestFmwk { DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(ULocale.ENGLISH); DecimalFormatProperties properties = new DecimalFormatProperties(); - formats.add(NumberPropertyMapper.create(properties, symbols).locale(ULocale.ENGLISH)); + formats.add(NumberFormatter.fromDecimalFormat(properties, symbols, null).locale(ULocale.ENGLISH)); properties = new DecimalFormatProperties() .setMinimumSignificantDigits(3) .setMaximumSignificantDigits(3) .setCompactStyle(CompactStyle.LONG); - formats.add(NumberPropertyMapper.create(properties, symbols).locale(ULocale.ENGLISH)); + formats.add(NumberFormatter.fromDecimalFormat(properties, symbols, null).locale(ULocale.ENGLISH)); properties = new DecimalFormatProperties() .setMinimumExponentDigits(1) .setMaximumIntegerDigits(3) .setMaximumFractionDigits(1); - formats.add(NumberPropertyMapper.create(properties, symbols).locale(ULocale.ENGLISH)); + formats.add(NumberFormatter.fromDecimalFormat(properties, symbols, null).locale(ULocale.ENGLISH)); properties = new DecimalFormatProperties().setRoundingIncrement(new BigDecimal("0.5")); - formats.add(NumberPropertyMapper.create(properties, symbols).locale(ULocale.ENGLISH)); + formats.add(NumberFormatter.fromDecimalFormat(properties, symbols, null).locale(ULocale.ENGLISH)); String[] cases = { "1.0", @@ -159,20 +161,12 @@ public class DecimalQuantityTest extends TestFmwk { } private static void testDecimalQuantityExpectedOutput(DecimalQuantity rq, String expected) { - StringBuilder sb = new StringBuilder(); DecimalQuantity q0 = rq.createCopy(); // Force an accurate double q0.roundToInfinity(); q0.setIntegerLength(1, Integer.MAX_VALUE); q0.setFractionLength(1, Integer.MAX_VALUE); - for (int m = q0.getUpperDisplayMagnitude(); m >= q0.getLowerDisplayMagnitude(); m--) { - sb.append(q0.getDigit(m)); - if (m == 0) sb.append('.'); - } - if (q0.isNegative()) { - sb.insert(0, '-'); - } - String actual = sb.toString(); + String actual = q0.toPlainString(); assertEquals("Unexpected output from simple string conversion (" + q0 + ")", expected, actual); } @@ -294,18 +288,18 @@ public class DecimalQuantityTest extends TestFmwk { DecimalQuantity_DualStorageBCD fq = new DecimalQuantity_DualStorageBCD(); fq.setToLong(1234123412341234L); - assertFalse("Should not be using byte array", fq.usingBytes()); - assertBigDecimalEquals("Failed on initialize", "1234123412341234", fq.toBigDecimal()); + assertFalse("Should not be using byte array", fq.isUsingBytes()); + assertEquals("Failed on initialize", "1234123412341234E0", fq.toNumberString()); assertNull("Failed health check", fq.checkHealth()); // Long -> Bytes fq.appendDigit((byte) 5, 0, true); - assertTrue("Should be using byte array", fq.usingBytes()); - assertBigDecimalEquals("Failed on multiply", "12341234123412345", fq.toBigDecimal()); + assertTrue("Should be using byte array", fq.isUsingBytes()); + assertEquals("Failed on multiply", "12341234123412345E0", fq.toNumberString()); assertNull("Failed health check", fq.checkHealth()); // Bytes -> Long fq.roundToMagnitude(5, MATH_CONTEXT_HALF_EVEN); - assertFalse("Should not be using byte array", fq.usingBytes()); - assertBigDecimalEquals("Failed on round", "12341234123400000", fq.toBigDecimal()); + assertFalse("Should not be using byte array", fq.isUsingBytes()); + assertEquals("Failed on round", "123412341234E5", fq.toNumberString()); assertNull("Failed health check", fq.checkHealth()); } @@ -313,45 +307,52 @@ public class DecimalQuantityTest extends TestFmwk { public void testAppend() { DecimalQuantity_DualStorageBCD fq = new DecimalQuantity_DualStorageBCD(); fq.appendDigit((byte) 1, 0, true); - assertBigDecimalEquals("Failed on append", "1.", fq.toBigDecimal()); + assertEquals("Failed on append", "1E0", fq.toNumberString()); assertNull("Failed health check", fq.checkHealth()); fq.appendDigit((byte) 2, 0, true); - assertBigDecimalEquals("Failed on append", "12.", fq.toBigDecimal()); + assertEquals("Failed on append", "12E0", fq.toNumberString()); assertNull("Failed health check", fq.checkHealth()); fq.appendDigit((byte) 3, 1, true); - assertBigDecimalEquals("Failed on append", "1203.", fq.toBigDecimal()); + assertEquals("Failed on append", "1203E0", fq.toNumberString()); assertNull("Failed health check", fq.checkHealth()); fq.appendDigit((byte) 0, 1, true); - assertBigDecimalEquals("Failed on append", "120300.", fq.toBigDecimal()); + assertEquals("Failed on append", "1203E2", fq.toNumberString()); assertNull("Failed health check", fq.checkHealth()); fq.appendDigit((byte) 4, 0, true); - assertBigDecimalEquals("Failed on append", "1203004.", fq.toBigDecimal()); + assertEquals("Failed on append", "1203004E0", fq.toNumberString()); assertNull("Failed health check", fq.checkHealth()); fq.appendDigit((byte) 0, 0, true); - assertBigDecimalEquals("Failed on append", "12030040.", fq.toBigDecimal()); + assertEquals("Failed on append", "1203004E1", fq.toNumberString()); assertNull("Failed health check", fq.checkHealth()); fq.appendDigit((byte) 5, 0, false); - assertBigDecimalEquals("Failed on append", "12030040.5", fq.toBigDecimal()); + assertEquals("Failed on append", "120300405E-1", fq.toNumberString()); assertNull("Failed health check", fq.checkHealth()); fq.appendDigit((byte) 6, 0, false); - assertBigDecimalEquals("Failed on append", "12030040.56", fq.toBigDecimal()); + assertEquals("Failed on append", "1203004056E-2", fq.toNumberString()); assertNull("Failed health check", fq.checkHealth()); fq.appendDigit((byte) 7, 3, false); - assertBigDecimalEquals("Failed on append", "12030040.560007", fq.toBigDecimal()); + assertEquals("Failed on append", "12030040560007E-6", fq.toNumberString()); assertNull("Failed health check", fq.checkHealth()); - StringBuilder expected = new StringBuilder("12030040.560007"); + StringBuilder baseExpected = new StringBuilder("12030040560007"); for (int i = 0; i < 10; i++) { fq.appendDigit((byte) 8, 0, false); - expected.append("8"); - assertBigDecimalEquals("Failed on append", expected.toString(), fq.toBigDecimal()); + baseExpected.append('8'); + StringBuilder expected = new StringBuilder(baseExpected); + expected.append("E"); + expected.append(-7 - i); + assertEquals("Failed on append", expected.toString(), fq.toNumberString()); assertNull("Failed health check", fq.checkHealth()); } fq.appendDigit((byte) 9, 2, false); - expected.append("009"); - assertBigDecimalEquals("Failed on append", expected.toString(), fq.toBigDecimal()); + baseExpected.append("009"); + StringBuilder expected = new StringBuilder(baseExpected); + expected.append('E'); + expected.append("-19"); + assertEquals("Failed on append", expected.toString(), fq.toNumberString()); assertNull("Failed health check", fq.checkHealth()); } + @Ignore @Test public void testConvertToAccurateDouble() { // based on https://github.com/google/double-conversion/issues/28 @@ -423,12 +424,14 @@ public class DecimalQuantityTest extends TestFmwk { private static void checkDoubleBehavior(double d, boolean explicitRequired, String alert) { DecimalQuantity_DualStorageBCD fq = new DecimalQuantity_DualStorageBCD(d); - if (explicitRequired) + if (explicitRequired) { assertTrue(alert + "Should be using approximate double", !fq.explicitExactDouble); + } assertEquals(alert + "Initial construction from hard double", d, fq.toDouble()); fq.roundToInfinity(); - if (explicitRequired) + if (explicitRequired) { assertTrue(alert + "Should not be using approximate double", fq.explicitExactDouble); + } assertDoubleEquals(alert + "After conversion to exact BCD (double)", d, fq.toDouble()); assertBigDecimalEquals( alert + "After conversion to exact BCD (BigDecimal)", @@ -469,6 +472,30 @@ public class DecimalQuantityTest extends TestFmwk { } } + @Test + public void testDecimalQuantityBehaviorStandalone() { + DecimalQuantity_DualStorageBCD fq = new DecimalQuantity_DualStorageBCD(); + assertToStringAndHealth(fq, ""); + fq.setToInt(51423); + assertToStringAndHealth(fq, ""); + fq.adjustMagnitude(-3); + assertToStringAndHealth(fq, ""); + fq.setToLong(999999999999000L); + assertToStringAndHealth(fq, ""); + fq.setIntegerLength(2, 5); + assertToStringAndHealth(fq, ""); + fq.setFractionLength(3, 6); + assertToStringAndHealth(fq, ""); + fq.setToDouble(987.654321); + assertToStringAndHealth(fq, ""); + fq.roundToInfinity(); + assertToStringAndHealth(fq, ""); + fq.roundToIncrement(new BigDecimal("0.005"), MATH_CONTEXT_HALF_EVEN); + assertToStringAndHealth(fq, ""); + fq.roundToMagnitude(-2, MATH_CONTEXT_HALF_EVEN); + assertToStringAndHealth(fq, ""); + } + static void assertDoubleEquals(String message, double d1, double d2) { boolean equal = (Math.abs(d1 - d2) < 1e-6) || (Math.abs((d1 - d2) / d1) < 1e-6); handleAssert(equal, message, d1, d2, null, false); @@ -482,4 +509,11 @@ public class DecimalQuantityTest extends TestFmwk { boolean equal = d1.compareTo(d2) == 0; handleAssert(equal, message, d1, d2, null, false); } + + static void assertToStringAndHealth(DecimalQuantity_DualStorageBCD fq, String expected) { + String actual = fq.toString(); + assertEquals("DecimalQuantity toString", expected, actual); + String health = fq.checkHealth(); + assertNull("DecimalQuantity health", health); + } } diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/ModifierTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/ModifierTest.java index 54b0e53cdb4..205660f27c7 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/ModifierTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/ModifierTest.java @@ -23,8 +23,8 @@ public class ModifierTest { public void testConstantAffixModifier() { assertModifierEquals(ConstantAffixModifier.EMPTY, 0, false, "|", "n"); - Modifier mod1 = new ConstantAffixModifier("a", "b", NumberFormat.Field.PERCENT, true); - assertModifierEquals(mod1, 1, true, "a|b", "%n%"); + Modifier mod1 = new ConstantAffixModifier("a📻", "b", NumberFormat.Field.PERCENT, true); + assertModifierEquals(mod1, 3, true, "a📻|b", "%%%n%"); } @Test @@ -34,10 +34,10 @@ public class ModifierTest { Modifier mod1 = new ConstantMultiFieldModifier(prefix, suffix, true); assertModifierEquals(mod1, 0, true, "|", "n"); - prefix.append("a", NumberFormat.Field.PERCENT); + prefix.append("a📻", NumberFormat.Field.PERCENT); suffix.append("b", NumberFormat.Field.CURRENCY); Modifier mod2 = new ConstantMultiFieldModifier(prefix, suffix, true); - assertModifierEquals(mod2, 1, true, "a|b", "%n$"); + assertModifierEquals(mod2, 3, true, "a📻|b", "%%%n$"); // Make sure the first modifier is still the same (that it stayed constant) assertModifierEquals(mod1, 0, true, "|", "n"); @@ -45,15 +45,15 @@ public class ModifierTest { @Test public void testSimpleModifier() { - String[] patterns = { "{0}", "X{0}Y", "XX{0}YYY", "{0}YY", "XXXX{0}" }; - Object[][] outputs = { { "", 0, 0 }, { "abcde", 0, 0 }, { "abcde", 2, 2 }, { "abcde", 1, 3 } }; - int[] prefixLens = { 0, 1, 2, 0, 4 }; + String[] patterns = { "{0}", "X{0}Y", "XX{0}YYY", "{0}YY", "XX📺XX{0}" }; + Object[][] outputs = { { "", 0, 0 }, { "a📻bcde", 0, 0 }, { "a📻bcde", 4, 4 }, { "a📻bcde", 3, 5 } }; + int[] prefixLens = { 0, 1, 2, 0, 6 }; String[][] expectedCharFields = { { "|", "n" }, { "X|Y", "%n%" }, { "XX|YYY", "%%n%%%" }, { "|YY", "n%%" }, - { "XXXX|", "%%%%n" } }; - String[][] expecteds = { { "", "XY", "XXYYY", "YY", "XXXX" }, - { "abcde", "XYabcde", "XXYYYabcde", "YYabcde", "XXXXabcde" }, - { "abcde", "abXYcde", "abXXYYYcde", "abYYcde", "abXXXXcde" }, - { "abcde", "aXbcYde", "aXXbcYYYde", "abcYYde", "aXXXXbcde" } }; + { "XX📺XX|", "%%%%%%n" } }; + String[][] expecteds = { { "", "XY", "XXYYY", "YY", "XX📺XX" }, + { "a📻bcde", "XYa📻bcde", "XXYYYa📻bcde", "YYa📻bcde", "XX📺XXa📻bcde" }, + { "a📻bcde", "a📻bXYcde", "a📻bXXYYYcde", "a📻bYYcde", "a📻bXX📺XXcde" }, + { "a📻bcde", "a📻XbcYde", "a📻XXbcYYYde", "a📻bcYYde", "a📻XX📺XXbcde" } }; for (int i = 0; i < patterns.length; i++) { String pattern = patterns[i]; String compiledPattern = SimpleFormatterImpl @@ -144,9 +144,14 @@ public class ModifierTest { boolean expectedStrong, String expectedChars, String expectedFields) { - mod.apply(sb, 0, sb.length()); + int oldCount = sb.codePointCount(); + mod.apply(sb, 0, oldCount); assertEquals("Prefix length on " + sb, expectedPrefixLength, mod.getPrefixLength()); assertEquals("Strong on " + sb, expectedStrong, mod.isStrong()); + if (!(mod instanceof CurrencySpacingEnabledModifier)) { + assertEquals("Code point count equals actual code point count", + sb.codePointCount() - oldCount, mod.getCodePointCount()); + } assertEquals("", sb.toDebugString()); } } 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 deleted file mode 100644 index e1bf13d1c9a..00000000000 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/MurkyModifierTest.java +++ /dev/null @@ -1,71 +0,0 @@ -// © 2017 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html#License -package com.ibm.icu.dev.test.number; - -import static org.junit.Assert.assertEquals; - -import org.junit.Test; - -import com.ibm.icu.impl.number.PatternStringParser; -import com.ibm.icu.impl.number.NumberStringBuilder; -import com.ibm.icu.text.DecimalFormatSymbols; -import com.ibm.icu.util.Currency; -import com.ibm.icu.util.ULocale; - -import newapi.NumberFormatter.SignDisplay; -import newapi.NumberFormatter.UnitWidth; -import newapi.impl.MutablePatternModifier; - -public class MurkyModifierTest { - - @Test - public void basic() { - MutablePatternModifier murky = new MutablePatternModifier(false); - murky.setPatternInfo(PatternStringParser.parseToPatternInfo("a0b")); - murky.setPatternAttributes(SignDisplay.AUTO, false); - murky.setSymbols( - DecimalFormatSymbols.getInstance(ULocale.ENGLISH), - Currency.getInstance("USD"), - UnitWidth.SHORT, - null); - murky.setNumberProperties(false, null); - assertEquals("a", getPrefix(murky)); - assertEquals("b", getSuffix(murky)); - murky.setPatternAttributes(SignDisplay.ALWAYS, false); - assertEquals("+a", getPrefix(murky)); - assertEquals("b", getSuffix(murky)); - murky.setNumberProperties(true, null); - assertEquals("-a", getPrefix(murky)); - assertEquals("b", getSuffix(murky)); - murky.setPatternAttributes(SignDisplay.NEVER, false); - assertEquals("a", getPrefix(murky)); - assertEquals("b", getSuffix(murky)); - - murky.setPatternInfo(PatternStringParser.parseToPatternInfo("a0b;c-0d")); - murky.setPatternAttributes(SignDisplay.AUTO, false); - murky.setNumberProperties(false, null); - assertEquals("a", getPrefix(murky)); - assertEquals("b", getSuffix(murky)); - murky.setPatternAttributes(SignDisplay.ALWAYS, false); - assertEquals("c+", getPrefix(murky)); - assertEquals("d", getSuffix(murky)); - murky.setNumberProperties(true, null); - assertEquals("c-", getPrefix(murky)); - assertEquals("d", getSuffix(murky)); - murky.setPatternAttributes(SignDisplay.NEVER, false); - assertEquals("c-", getPrefix(murky)); // TODO: What should this behavior be? - assertEquals("d", getSuffix(murky)); - } - - private static String getPrefix(MutablePatternModifier murky) { - NumberStringBuilder nsb = new NumberStringBuilder(); - murky.apply(nsb, 0, 0); - return nsb.subSequence(0, murky.getPrefixLength()).toString(); - } - - private static String getSuffix(MutablePatternModifier murky) { - NumberStringBuilder nsb = new NumberStringBuilder(); - murky.apply(nsb, 0, 0); - return nsb.subSequence(murky.getPrefixLength(), nsb.length()).toString(); - } -} diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/MutablePatternModifierTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/MutablePatternModifierTest.java new file mode 100644 index 00000000000..115d8229dab --- /dev/null +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/MutablePatternModifierTest.java @@ -0,0 +1,107 @@ +// © 2017 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html#License +package com.ibm.icu.dev.test.number; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import com.ibm.icu.impl.number.DecimalQuantity; +import com.ibm.icu.impl.number.DecimalQuantity_DualStorageBCD; +import com.ibm.icu.impl.number.NumberStringBuilder; +import com.ibm.icu.impl.number.PatternStringParser; +import com.ibm.icu.text.DecimalFormatSymbols; +import com.ibm.icu.util.Currency; +import com.ibm.icu.util.ULocale; + +import newapi.NumberFormatter.SignDisplay; +import newapi.NumberFormatter.UnitWidth; +import newapi.impl.MicroProps; +import newapi.impl.MutablePatternModifier; + +public class MutablePatternModifierTest { + + @Test + public void basic() { + MutablePatternModifier mod = new MutablePatternModifier(false); + mod.setPatternInfo(PatternStringParser.parseToPatternInfo("a0b")); + mod.setPatternAttributes(SignDisplay.AUTO, false); + mod.setSymbols( + DecimalFormatSymbols.getInstance(ULocale.ENGLISH), + Currency.getInstance("USD"), + UnitWidth.SHORT, + null); + + mod.setNumberProperties(false, null); + assertEquals("a", getPrefix(mod)); + assertEquals("b", getSuffix(mod)); + mod.setPatternAttributes(SignDisplay.ALWAYS, false); + assertEquals("+a", getPrefix(mod)); + assertEquals("b", getSuffix(mod)); + mod.setNumberProperties(true, null); + assertEquals("-a", getPrefix(mod)); + assertEquals("b", getSuffix(mod)); + mod.setPatternAttributes(SignDisplay.NEVER, false); + assertEquals("a", getPrefix(mod)); + assertEquals("b", getSuffix(mod)); + + mod.setPatternInfo(PatternStringParser.parseToPatternInfo("a0b;c-0d")); + mod.setPatternAttributes(SignDisplay.AUTO, false); + mod.setNumberProperties(false, null); + assertEquals("a", getPrefix(mod)); + assertEquals("b", getSuffix(mod)); + mod.setPatternAttributes(SignDisplay.ALWAYS, false); + assertEquals("c+", getPrefix(mod)); + assertEquals("d", getSuffix(mod)); + mod.setNumberProperties(true, null); + assertEquals("c-", getPrefix(mod)); + assertEquals("d", getSuffix(mod)); + mod.setPatternAttributes(SignDisplay.NEVER, false); + assertEquals("c-", getPrefix(mod)); // TODO: What should this behavior be? + assertEquals("d", getSuffix(mod)); + } + + @Test + public void mutableEqualsImmutable() { + MutablePatternModifier mod = new MutablePatternModifier(false); + mod.setPatternInfo(PatternStringParser.parseToPatternInfo("a0b;c-0d")); + mod.setPatternAttributes(SignDisplay.AUTO, false); + mod.setSymbols(DecimalFormatSymbols.getInstance(ULocale.ENGLISH), null, UnitWidth.SHORT, null); + DecimalQuantity fq = new DecimalQuantity_DualStorageBCD(1); + + NumberStringBuilder nsb1 = new NumberStringBuilder(); + MicroProps micros1 = new MicroProps(false); + mod.addToChain(micros1); + mod.processQuantity(fq); + micros1.modMiddle.apply(nsb1, 0, 0); + + NumberStringBuilder nsb2 = new NumberStringBuilder(); + MicroProps micros2 = new MicroProps(true); + mod.createImmutable().applyToMicros(micros2, fq); + micros2.modMiddle.apply(nsb2, 0, 0); + + NumberStringBuilder nsb3 = new NumberStringBuilder(); + MicroProps micros3 = new MicroProps(false); + mod.addToChain(micros3); + mod.setPatternAttributes(SignDisplay.ALWAYS, false); + mod.processQuantity(fq); + micros3.modMiddle.apply(nsb3, 0, 0); + + assertTrue(nsb1 + " vs. " + nsb2, nsb1.contentEquals(nsb2)); + assertFalse(nsb1 + " vs. " + nsb3, nsb1.contentEquals(nsb3)); + } + + private static String getPrefix(MutablePatternModifier mod) { + NumberStringBuilder nsb = new NumberStringBuilder(); + mod.apply(nsb, 0, 0); + return nsb.subSequence(0, mod.getPrefixLength()).toString(); + } + + private static String getSuffix(MutablePatternModifier mod) { + NumberStringBuilder nsb = new NumberStringBuilder(); + mod.apply(nsb, 0, 0); + return nsb.subSequence(mod.getPrefixLength(), nsb.length()).toString(); + } +} 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 dcb273cd69b..49d428e40a0 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 @@ -338,6 +338,34 @@ public class NumberFormatterTest { ULocale.ENGLISH, -9876543.21, "-9,876,543.21 m"); + + // The locale string "सान" appears only in brx.txt: + assertFormatSingle( + "Interesting Data Fallback 1", + "U:duration:day unit-width=FULL_NAME", + NumberFormatter.with().unit(MeasureUnit.DAY).unitWidth(UnitWidth.FULL_NAME), + ULocale.forLanguageTag("brx"), + 5.43, + "5.43 सान"); + + // Requires following the alias from unitsNarrow to unitsShort: + assertFormatSingle( + "Interesting Data Fallback 2", + "U:duration:day unit-width=NARROW", + NumberFormatter.with().unit(MeasureUnit.DAY).unitWidth(UnitWidth.NARROW), + ULocale.forLanguageTag("brx"), + 5.43, + "5.43 d"); + + // en_001.txt has a unitsNarrow/area/square-meter table, but table does not contain the OTHER unit, + // requiring fallback to the root. + assertFormatSingle( + "Interesting Data Fallback 3", + "U:area:square-meter unit-width=NARROW", + NumberFormatter.with().unit(MeasureUnit.SQUARE_METER).unitWidth(UnitWidth.NARROW), + ULocale.forLanguageTag("en-GB"), + 5.43, + "5.43 m²"); } @Test @@ -870,7 +898,7 @@ public class NumberFormatterTest { ULocale.ENGLISH, "GBP 87,650.00", "GBP 8,765.00", - "GBP 876.50", + "GBP*876.50", "GBP**87.65", "GBP***8.76", "GBP***0.88", diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/PatternStringTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/PatternStringTest.java index a5cd7a48c4d..aabf06adb0e 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/PatternStringTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/PatternStringTest.java @@ -7,9 +7,9 @@ import static org.junit.Assert.fail; import org.junit.Test; +import com.ibm.icu.impl.number.DecimalFormatProperties; import com.ibm.icu.impl.number.PatternStringParser; import com.ibm.icu.impl.number.PatternStringUtils; -import com.ibm.icu.impl.number.DecimalFormatProperties; import com.ibm.icu.text.DecimalFormatSymbols; import com.ibm.icu.util.ULocale; diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/PropertiesTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/PropertiesTest.java index 4d2df684299..64e034925c5 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/PropertiesTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/PropertiesTest.java @@ -29,11 +29,10 @@ import java.util.Set; import org.junit.Test; import com.ibm.icu.dev.test.serializable.SerializableTestUtility; +import com.ibm.icu.impl.number.DecimalFormatProperties; import com.ibm.icu.impl.number.Parse.GroupingMode; import com.ibm.icu.impl.number.Parse.ParseMode; import com.ibm.icu.impl.number.PatternStringParser; -import com.ibm.icu.impl.number.DecimalFormatProperties; -import com.ibm.icu.impl.number.ThingsNeedingNewHome.PadPosition; import com.ibm.icu.text.CompactDecimalFormat.CompactStyle; import com.ibm.icu.text.CurrencyPluralInfo; import com.ibm.icu.text.MeasureFormat.FormatWidth; @@ -43,6 +42,8 @@ import com.ibm.icu.util.Currency.CurrencyUsage; import com.ibm.icu.util.MeasureUnit; import com.ibm.icu.util.ULocale; +import newapi.impl.Padder.PadPosition; + public class PropertiesTest { @Test -- 2.40.0