ICU-13117 More renaming and refactoring in Java with no behavior changes.

X-SVN-Rev: 40392
This commit is contained in:
Shane Carr 2017-09-13 09:57:11 +00:00
parent 1f1a485c3c
commit c444c0c561
32 changed files with 663 additions and 558 deletions

View File

@ -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}.

View File

@ -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. */

View File

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

View File

@ -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(
"<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();
}
}

View File

@ -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;

View File

@ -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

View File

@ -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));

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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<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;
/**

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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
* <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;
@ -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) {

View File

@ -364,6 +364,7 @@ public abstract class NumberFormatterSettings<T extends NumberFormatterSettings<
return create(KEY_THRESHOLD, threshold);
}
/** Non-public method */
public String toSkeleton() {
return SkeletonBuilder.macrosToSkeleton(resolve());
}

View File

@ -11,8 +11,8 @@ import com.ibm.icu.text.NumberFormat;
import newapi.NumberFormatter.SignDisplay;
import newapi.Rounder.SignificantRounderImpl;
import newapi.impl.MicroProps;
import newapi.impl.MultiplierProducer;
import newapi.impl.MicroPropsGenerator;
import newapi.impl.MultiplierProducer;
@SuppressWarnings("unused")
public class ScientificNotation extends Notation implements Cloneable {
@ -145,6 +145,12 @@ 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;
@ -193,6 +199,12 @@ 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;

View File

@ -6,37 +6,41 @@ import java.util.EnumMap;
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();
@ -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<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);
@ -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 <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
@ -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<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 ///
/////////////////////////////////////
}

View File

@ -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<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;
}
}

View File

@ -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;

View File

@ -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();
}
}

View File

@ -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);
public boolean isValid() {
return targetWidth > 0;
}
// No padding; apply the mods and leave.
if (targetWidth < 0) {
return applyMicroMods(micros, string, leftIndex, rightIndex + innerLength);
}
// Estimate the padding width needed.
// TODO: Make this more efficient (less copying)
// TODO: How to handle when padding is inserted between a currency sign and the number
// when currency spacing is in play?
NumberStringBuilder backup = new NumberStringBuilder(string);
int length = innerLength + applyMicroMods(micros, string, leftIndex, rightIndex + innerLength);
int requiredPadding = targetWidth - string.codePointCount();
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++) {

View File

@ -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();

View File

@ -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<StandardPluralCategories> 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<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) {
@ -927,6 +928,7 @@ public class PluralRulesTest extends TestFmwk {
}
private static final Comparator<PluralRules> PLURAL_RULE_COMPARATOR = new Comparator<PluralRules>() {
@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;

View File

@ -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();
}
}

View File

@ -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, "<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);
@ -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);
}
}

View File

@ -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("<NumberStringBuilder [" + expectedChars + "] [" + expectedFields + "]>", sb.toDebugString());
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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",

View File

@ -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;

View File

@ -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