* @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,
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}.
*/
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.
*/
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.
*
*/
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. */
* @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;
}
@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
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;
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
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;
}
}
} else if (leadingDigit == 9) {
- section = -2;
+ section = SECTION_UPPER_EDGE;
for (; p >= minP; p--) {
if (getDigitPos(p) != 9) {
section = RoundingUtils.SECTION_UPPER;
}
// 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 =
}
}
+ @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.
*
@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;
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;
for (; n != 0L; n /= 10L, i++) {
bcdBytes[i] = (byte) (n % 10);
}
- usingBytes = true;
+ assert usingBytes;
scale = 0;
precision = i;
} else {
result = (result >>> 4) + ((n % 10) << 60);
}
assert i >= 0;
- usingBytes = false;
+ assert !usingBytes;
bcdLong = result >>> (i * 4);
scale = 0;
precision = 16 - i;
bcdBytes[i] = temp[1].byteValue();
n = temp[0];
}
- usingBytes = true;
scale = 0;
precision = i;
}
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--) {
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. */
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
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;
}
}
* @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(
- "<DecimalQuantity4 %s:%d:%d:%s %s %s%s%d>",
- (lOptPos > 1000 ? "max" : String.valueOf(lOptPos)),
+ "<DecimalQuantity %s:%d:%d:%s %s %s>",
+ (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();
}
}
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;
*/
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
parseToExistingProperties(pattern, properties, PatternStringParser.IGNORE_ROUNDING_NEVER);
}
- /////////////////////////////////////////////////////
- /// BEGIN RECURSIVE DESCENT PARSER IMPLEMENTATION ///
- /////////////////////////////////////////////////////
-
/**
* Contains raw information about the parsed decimal format pattern string.
*/
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;
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;
// 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) {
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);
// 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));
String nsp = properties.getNegativeSuffixPattern();
// Prefixes
- if (ppp != null)
+ if (ppp != null) {
sb.append(ppp);
+ }
AffixUtils.escape(pp, sb);
int afterPrefixPos = sb.length();
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 {
// Suffixes
int beforeSuffixPos = sb.length();
- if (psp != null)
+ if (psp != null) {
sb.append(psp);
+ }
AffixUtils.escape(ps, sb);
// Resolve Padding
return prefix.length();
}
+ @Override
+ public int getCodePointCount() {
+ return prefix.codePointCount(0, prefix.length()) + suffix.codePointCount(0, suffix.length());
+ }
+
@Override
public boolean isStrong() {
return strong;
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;
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;
}
/**
+ * An interface to FixedDecimal, allowing for other implementations.
+ *
* @internal
* @deprecated This API is ICU internal only.
*/
@Deprecated
public static class FixedDecimal extends Number implements Comparable<FixedDecimal>, 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;
/**
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 {
private static class CompactImpl implements MicroPropsGenerator {
private static class CompactModInfo {
- public ImmutableMurkyModifier mod;
+ public ImmutablePatternModifier mod;
public int numDigits;
}
AUTO, ALWAYS, NEVER, ACCOUNTING, ACCOUNTING_ALWAYS,
}
- public static UnlocalizedNumberFormatter fromSkeleton(String skeleton) {
- // FIXME
- throw new UnsupportedOperationException();
- }
-
public static UnlocalizedNumberFormatter with() {
return BASE;
}
*/
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);
final MicroPropsGenerator microPropsGenerator;
- private NumberFormatterImpl(MicroPropsGenerator microsGenerator) {
- this.microPropsGenerator = microsGenerator;
+ private NumberFormatterImpl(MicroPropsGenerator microPropsGenerator) {
+ this.microPropsGenerator = microPropsGenerator;
}
public MicroProps apply(DecimalQuantity inValue, NumberStringBuilder outString) {
* If true, the returned MicroPropsGenerator will be thread-safe. If false, the returned value will
* <em>not</em> 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;
} 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) {
} 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
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);
// 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.
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) {
return create(KEY_THRESHOLD, threshold);
}
+ /** Non-public method */
public String toSkeleton() {
return SkeletonBuilder.macrosToSkeleton(resolve());
}
import newapi.NumberFormatter.SignDisplay;
import newapi.Rounder.SignificantRounderImpl;
import newapi.impl.MicroProps;
-import newapi.impl.MultiplierProducer;
import newapi.impl.MicroPropsGenerator;
+import newapi.impl.MultiplierProducer;
@SuppressWarnings("unused")
public class ScientificNotation extends Notation implements Cloneable {
return 0;
}
+ @Override
+ public int getCodePointCount() {
+ // This method is not used for strong modifiers.
+ throw new AssertionError();
+ }
+
@Override
public boolean isStrong() {
return true;
return 0;
}
+ @Override
+ public int getCodePointCount() {
+ // This method is not used for strong modifiers.
+ throw new AssertionError();
+ }
+
@Override
public boolean isStrong() {
return true;
import java.util.Map;
import com.ibm.icu.impl.CurrencyData;
+import com.ibm.icu.impl.ICUData;
+import com.ibm.icu.impl.ICUResourceBundle;
import com.ibm.icu.impl.SimpleFormatterImpl;
import com.ibm.icu.impl.StandardPlural;
+import com.ibm.icu.impl.UResource;
import com.ibm.icu.impl.number.DecimalQuantity;
-import com.ibm.icu.impl.number.Modifier;
import com.ibm.icu.impl.number.modifiers.SimpleModifier;
import com.ibm.icu.text.NumberFormat.Field;
import com.ibm.icu.text.PluralRules;
import com.ibm.icu.util.Currency;
+import com.ibm.icu.util.ICUException;
import com.ibm.icu.util.MeasureUnit;
import com.ibm.icu.util.ULocale;
+import com.ibm.icu.util.UResourceBundle;
import newapi.NumberFormatter.UnitWidth;
public class LongNameHandler implements MicroPropsGenerator {
- private final Map<StandardPlural, Modifier> data;
- /* unsafe */ PluralRules rules;
- /* unsafe */ MicroPropsGenerator parent;
+ private final Map<StandardPlural, SimpleModifier> modifiers;
+ private PluralRules rules;
+ private MicroPropsGenerator parent;
- private LongNameHandler(Map<StandardPlural, Modifier> data) {
- this.data = data;
+ private LongNameHandler(Map<StandardPlural, SimpleModifier> 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<String, String> data = CurrencyData.provider.getInstance(loc, true).getUnitPatterns();
- Map<StandardPlural, Modifier> result = new EnumMap<StandardPlural, Modifier>(StandardPlural.class);
+ Map<StandardPlural, SimpleModifier> result = new EnumMap<StandardPlural, SimpleModifier>(StandardPlural.class);
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, String> e : data.entrySet()) {
String pluralKeyword = e.getKey();
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<StandardPlural, String> simpleFormats = MeasureData.getMeasureData(loc, unit, width);
- Map<StandardPlural, Modifier> result = new EnumMap<StandardPlural, Modifier>(StandardPlural.class);
+ public static LongNameHandler forMeasureUnit(ULocale loc, MeasureUnit unit, UnitWidth width) {
+ Map<StandardPlural, String> simpleFormats = getMeasureData(loc, unit, width);
+ Map<StandardPlural, SimpleModifier> result = new EnumMap<StandardPlural, SimpleModifier>(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);
*
* @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 <em>this</em>
- * 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
// 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<StandardPlural, String> output;
+
+ public MeasureUnitSink(Map<StandardPlural, String> 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<StandardPlural, String> 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<StandardPlural, String> output = new EnumMap<StandardPlural, String>(StandardPlural.class);
+ MeasureUnitSink sink = new MeasureUnitSink(output);
+ resource.getAllItemsWithFallback(key.toString(), sink);
+ return output;
+ }
+
+ /////////////////////////////////////
+ /// END MEASURE UNIT DATA LOADING ///
+ /////////////////////////////////////
}
+++ /dev/null
-// © 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<StandardPlural, String> output;
-
- public ShanesMeasureUnitSink(Map<StandardPlural, String> 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<StandardPlural, String> 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<StandardPlural, String> output = new EnumMap<StandardPlural, String>(StandardPlural.class);
- ShanesMeasureUnitSink sink = new ShanesMeasureUnitSink(output);
- resource.getAllItemsWithFallback(key.toString(), sink);
- return output;
- }
-}
public int multiplier;
public boolean useCurrency;
+ // Internal fields:
private final boolean immutable;
private volatile boolean exhausted;
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;
// Symbol details
DecimalFormatSymbols symbols;
UnitWidth unitWidth;
- String currency1;
- String currency2;
- String[] currency3;
+ Currency currency;
PluralRules rules;
// Number details
/**
* 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;
* @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
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);
- }
- }
}
/**
*
* @return An immutable that supports both positive and negative numbers.
*/
- public ImmutableMurkyModifier createImmutable() {
+ public ImmutablePatternModifier createImmutable() {
return createImmutableAndChain(null);
}
* 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()) {
}
}
- 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;
@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
return length;
}
+ /**
+ * Returns the string that substitutes a given symbol type in a pattern.
+ */
@Override
public CharSequence getSymbol(int type) {
switch (type) {
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:
&& (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));
@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
@Override
public CharSequence subSequence(int start, int end) {
- // Should never be called in normal circumstances
+ // Never called by AffixUtils
throw new AssertionError();
}
}
// 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 {
}
}
- 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.
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++) {
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 {
: 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();
}) {
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") }) {
enum StandardPluralCategories {
zero, one, two, few, many, other;
/**
- *
+ *
*/
private static final Set<StandardPluralCategories> ALL = Collections.unmodifiableSet(EnumSet
.allOf(StandardPluralCategories.class));
/**
* Return a mutable set
- *
+ *
* @param source
* @return
*/
}
static final Comparator<Set<StandardPluralCategories>> SHORTEST_FIRST = new Comparator<Set<StandardPluralCategories>>() {
+ @Override
public int compare(Set<StandardPluralCategories> arg0, Set<StandardPluralCategories> arg1) {
int diff = arg0.size() - arg1.size();
if (diff != 0) {
}
private static final Comparator<PluralRules> PLURAL_RULE_COMPARATOR = new Comparator<PluralRules>() {
+ @Override
public int compare(PluralRules o1, PluralRules o2) {
return o1.compareTo(o2);
}
}
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;
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() {
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);
}
}
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();
}
}
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 {
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",
}
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);
}
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());
}
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
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)",
}
}
+ @Test
+ public void testDecimalQuantityBehaviorStandalone() {
+ DecimalQuantity_DualStorageBCD fq = new DecimalQuantity_DualStorageBCD();
+ assertToStringAndHealth(fq, "<DecimalQuantity 999:0:0:-999 long 0E0>");
+ fq.setToInt(51423);
+ assertToStringAndHealth(fq, "<DecimalQuantity 999:0:0:-999 long 51423E0>");
+ fq.adjustMagnitude(-3);
+ assertToStringAndHealth(fq, "<DecimalQuantity 999:0:0:-999 long 51423E-3>");
+ fq.setToLong(999999999999000L);
+ assertToStringAndHealth(fq, "<DecimalQuantity 999:0:0:-999 long 999999999999E3>");
+ fq.setIntegerLength(2, 5);
+ assertToStringAndHealth(fq, "<DecimalQuantity 5:2:0:-999 long 999999999999E3>");
+ fq.setFractionLength(3, 6);
+ assertToStringAndHealth(fq, "<DecimalQuantity 5:2:-3:-6 long 999999999999E3>");
+ fq.setToDouble(987.654321);
+ assertToStringAndHealth(fq, "<DecimalQuantity 5:2:-3:-6 long 987654321E-6>");
+ fq.roundToInfinity();
+ assertToStringAndHealth(fq, "<DecimalQuantity 5:2:-3:-6 long 987654321E-6>");
+ fq.roundToIncrement(new BigDecimal("0.005"), MATH_CONTEXT_HALF_EVEN);
+ assertToStringAndHealth(fq, "<DecimalQuantity 5:2:-3:-6 long 987655E-3>");
+ fq.roundToMagnitude(-2, MATH_CONTEXT_HALF_EVEN);
+ assertToStringAndHealth(fq, "<DecimalQuantity 5:2:-3:-6 long 98766E-2>");
+ }
+
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);
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);
+ }
}
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
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");
@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
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("<NumberStringBuilder [" + expectedChars + "] [" + expectedFields + "]>", sb.toDebugString());
}
}
+++ /dev/null
-// © 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();
- }
-}
--- /dev/null
+// © 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();
+ }
+}
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
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",
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;
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;
import com.ibm.icu.util.MeasureUnit;
import com.ibm.icu.util.ULocale;
+import newapi.impl.Padder.PadPosition;
+
public class PropertiesTest {
@Test