}
public NumberStringBuilder(NumberStringBuilder source) {
- this(source.chars.length);
copyFrom(source);
}
public void copyFrom(NumberStringBuilder source) {
+ chars = Arrays.copyOf(source.chars, source.chars.length);
+ fields = Arrays.copyOf(source.fields, source.fields.length);
zero = source.zero;
length = source.length;
- System.arraycopy(source.chars, zero, chars, zero, length);
- System.arraycopy(source.fields, zero, fields, zero, length);
}
@Override
@Override
public char charAt(int index) {
- if (index < 0 || index > length) {
- throw new IndexOutOfBoundsException();
- }
+ assert index >= 0;
+ assert index < length;
return chars[zero + index];
}
public Field fieldAt(int index) {
- if (index < 0 || index > length) {
- throw new IndexOutOfBoundsException();
- }
+ assert index >= 0;
+ assert index < length;
return fields[zero + index];
}
+ public NumberStringBuilder clear() {
+ zero = chars.length / 2;
+ length = 0;
+ return this;
+ }
+
/**
* Appends the specified codePoint to the end of the string.
*
throw new IllegalArgumentException("Cannot call insert/append on myself");
}
int count = other.length;
- if (count == 0) return 0; // nothing to insert
+ if (count == 0) {
+ // Nothing to insert.
+ return 0;
+ }
int position = prepareForInsert(index, count);
for (int i = 0; i < count; i++) {
- this.chars[position + i] = other.chars[other.zero + i];
- this.fields[position + i] = other.fields[other.zero + i];
+ this.chars[position + i] = other.charAt(i);
+ this.fields[position + i] = other.fieldAt(i);
}
return count;
}
}
private int prepareForInsertHelper(int index, int count) {
- // Keeping this code out of prepareForInsert() increases the speed of append operations.
- if (length + count > chars.length) {
- char[] newChars = new char[(length + count) * 2];
- Field[] newFields = new Field[(length + count) * 2];
- int newZero = newChars.length / 2 - (length + count) / 2;
- System.arraycopy(chars, zero, newChars, newZero, index);
- System.arraycopy(chars, zero + index, newChars, newZero + index + count, length - index);
- System.arraycopy(fields, zero, newFields, newZero, index);
- System.arraycopy(fields, zero + index, newFields, newZero + index + count, length - index);
+ // Java note: Keeping this code out of prepareForInsert() increases the speed of append operations.
+ int oldCapacity = chars.length;
+ int oldZero = zero;
+ char[] oldChars = chars;
+ Field[] oldFields = fields;
+ if (length + count > oldCapacity) {
+ int newCapacity = (length + count) * 2;
+ int newZero = newCapacity / 2 - (length + count) / 2;
+
+ char[] newChars = new char[newCapacity];
+ Field[] newFields = new Field[newCapacity];
+
+ // First copy the prefix and then the suffix, leaving room for the new chars that the
+ // caller wants to insert.
+ System.arraycopy(oldChars, oldZero, newChars, newZero, index);
+ System.arraycopy(
+ oldChars, oldZero + index, newChars, newZero + index + count, length - index);
+ System.arraycopy(oldFields, oldZero, newFields, newZero, index);
+ System.arraycopy(
+ oldFields, oldZero + index, newFields, newZero + index + count, length - index);
+
chars = newChars;
fields = newFields;
zero = newZero;
length += count;
} else {
- int newZero = chars.length / 2 - (length + count) / 2;
- System.arraycopy(chars, zero, chars, newZero, length);
- System.arraycopy(chars, newZero + index, chars, newZero + index + count, length - index);
- System.arraycopy(fields, zero, fields, newZero, length);
- System.arraycopy(fields, newZero + index, fields, newZero + index + count, length - index);
+ int newZero = oldCapacity / 2 - (length + count) / 2;
+
+ // First copy the entire string to the location of the prefix, and then move the suffix
+ // to make room for the new chars that the caller wants to insert.
+ System.arraycopy(oldChars, oldZero, oldChars, newZero, length);
+ System.arraycopy(
+ oldChars, newZero + index, oldChars, newZero + index + count, length - index);
+ System.arraycopy(oldFields, oldZero, oldFields, newZero, length);
+ System.arraycopy(
+ oldFields, newZero + index, oldFields, newZero + index + count, length - index);
+
zero = newZero;
length += count;
}
public boolean contentEquals(NumberStringBuilder other) {
if (length != other.length) return false;
for (int i = 0; i < length; i++) {
- if (chars[zero + i] != other.chars[other.zero + i]) return false;
- if (fields[zero + i] != other.fields[other.zero + i]) return false;
+ if (charAt(i) != other.charAt(i) || fieldAt(i) != other.fieldAt(i)) {
+ return false;
+ }
}
return true;
}
"You must pass an instance of com.ibm.icu.text.NumberFormat.Field as your FieldPosition attribute. You passed: "
+ rawField.getClass().toString());
}
+
/* com.ibm.icu.text.NumberFormat. */ Field field = (Field) rawField;
boolean seenStart = false;
Field _field = (i < zero + length) ? fields[i] : null;
if (seenStart && field != _field) {
// Special case: GROUPING_SEPARATOR counts as an INTEGER.
- if (field == NumberFormat.Field.INTEGER && _field == NumberFormat.Field.GROUPING_SEPARATOR)
+ if (field == NumberFormat.Field.INTEGER && _field == NumberFormat.Field.GROUPING_SEPARATOR) {
continue;
+ }
fp.setEndIndex(i - zero + offset);
break;
} else if (!seenStart && field == _field) {
// Backwards compatibility: FRACTION needs to start after INTEGER if empty
if (field == NumberFormat.Field.FRACTION && !seenStart) {
- fp.setBeginIndex(fractionStart);
- fp.setEndIndex(fractionStart);
+ fp.setBeginIndex(fractionStart + offset);
+ fp.setEndIndex(fractionStart + offset);
}
}
if (current != null) {
as.addAttribute(current, current, currentStart, length);
}
- return as.getIterator();
- }
- public NumberStringBuilder clear() {
- zero = chars.length / 2;
- length = 0;
- return this;
+ return as.getIterator();
}
}
public static enum DecimalMarkDisplay {
AUTO,
- ALWAYS_SHOWN,
+ ALWAYS,
}
public static enum SignDisplay {
AUTO,
- ALWAYS_SHOWN,
- NEVER_SHOWN,
+ ALWAYS,
+ NEVER,
}
public static class UnlocalizedNumberFormatter {
public static class Rounding implements IRounding {
- protected static final int MAX_VALUE = 100;
+ // FIXME
+ /** @internal */
+ public static final int MAX_VALUE = 100;
public static final Rounding NONE = new RoundingImplInfinity();
public static final Rounding INTEGER = new RoundingImplFraction();
public static class Grouping implements IGrouping {
public static final Grouping DEFAULT = new GroupingImpl(GroupingImpl.TYPE_PLACEHOLDER);
- public static final Grouping DEFAULT_MIN_2_DIGITS = new GroupingImpl(GroupingImpl.TYPE_MIN2);
+ public static final Grouping MIN_2_DIGITS = new GroupingImpl(GroupingImpl.TYPE_MIN2);
public static final Grouping NONE = new GroupingImpl(GroupingImpl.TYPE_NONE);
@Override
public static final Padding NONE = new PaddingImpl();
public static Padding codePoints(int cp, int targetWidth, PadPosition position) {
- String paddingString = String.valueOf(Character.toChars(cp));
- return PaddingImpl.getInstance(paddingString, targetWidth, position);
+ // TODO: Validate the code point
+ if (targetWidth >= 0) {
+ String paddingString = String.valueOf(Character.toChars(cp));
+ return PaddingImpl.getInstance(paddingString, targetWidth, position);
+ } else {
+ throw new IllegalArgumentException("Padding width must not be negative");
+ }
}
// Prevent subclassing
public static final IntegerWidth DEFAULT = new IntegerWidthImpl();
public static IntegerWidth zeroFillTo(int minInt) {
- return new IntegerWidthImpl(minInt, Integer.MAX_VALUE);
+ if (minInt >= 0 && minInt < Rounding.MAX_VALUE) {
+ return new IntegerWidthImpl(minInt, Integer.MAX_VALUE);
+ } else {
+ throw new IllegalArgumentException(
+ "Integer digits must be between 0 and " + Rounding.MAX_VALUE);
+ }
}
public IntegerWidth truncateAt(int maxInt) {
UnlocalizedNumberFormatter formatter =
NumberFormatter.with()
.notation(Notation.COMPACT_SHORT)
- .notation(Notation.SCIENTIFIC.withExponentSignDisplay(SignDisplay.ALWAYS_SHOWN))
+ .notation(Notation.SCIENTIFIC.withExponentSignDisplay(SignDisplay.ALWAYS))
.notation(Notation.ENGINEERING.withMinExponentDigits(2))
.notation(Notation.SIMPLE)
.unit(Currency.getInstance("GBP"))
// })
.grouping(Grouping.DEFAULT)
.grouping(Grouping.NONE)
- .grouping(Grouping.DEFAULT_MIN_2_DIGITS)
+ .grouping(Grouping.MIN_2_DIGITS)
// .padding(Padding.codePoints(' ', 8, PadPosition.AFTER_PREFIX))
- .sign(SignDisplay.ALWAYS_SHOWN)
- .decimal(DecimalMarkDisplay.ALWAYS_SHOWN)
+ .sign(SignDisplay.ALWAYS)
+ .decimal(DecimalMarkDisplay.ALWAYS)
.symbols(DecimalFormatSymbols.getInstance(new ULocale("fr@digits=ascii")))
.symbols(NumberingSystem.getInstanceByName("arab"))
.symbols(NumberingSystem.LATIN);
final PluralRules rules;
final CompactData data;
- /* final */ Map<String, CompactModInfo> precomputedMods;
- /* final */ QuantityChain parent;
+ final Map<String, CompactModInfo> precomputedMods;
+ final QuantityChain parent;
public static CompactImpl getInstance(
- ULocale dataLocale, CompactType compactType, CompactStyle compactStyle, PluralRules rules) {
+ ULocale dataLocale,
+ CompactType compactType,
+ CompactStyle compactStyle,
+ PluralRules rules,
+ MurkyModifier buildReference,
+ QuantityChain parent) {
CompactData data = CompactData.getInstance(dataLocale, compactType, compactStyle);
- return new CompactImpl(data, rules);
+ return new CompactImpl(data, rules, buildReference, parent);
}
public static CompactImpl getInstance(
- Map<String, Map<String, String>> compactCustomData, PluralRules rules) {
+ Map<String, Map<String, String>> compactCustomData,
+ PluralRules rules,
+ MurkyModifier buildReference,
+ QuantityChain parent) {
CompactData data = CompactData.getInstance(compactCustomData);
- return new CompactImpl(data, rules);
+ return new CompactImpl(data, rules, buildReference, parent);
}
- private CompactImpl(CompactData data, PluralRules rules) {
+ private CompactImpl(
+ CompactData data, PluralRules rules, MurkyModifier buildReference, QuantityChain parent) {
this.data = data;
this.rules = rules;
+ if (buildReference != null) {
+ precomputedMods = precomputeAllModifiers(data, buildReference);
+ } else {
+ precomputedMods = null;
+ }
+ this.parent = parent;
}
/** To be used by the building code path */
- public void precomputeAllModifiers(MurkyModifier reference) {
- precomputedMods = new HashMap<String, CompactModInfo>();
+ public static Map<String, CompactModInfo> precomputeAllModifiers(
+ CompactData data, MurkyModifier buildReference) {
+ Map<String, CompactModInfo> precomputedMods = new HashMap<String, CompactModInfo>();
Set<String> allPatterns = data.getAllPatterns();
for (String patternString : allPatterns) {
CompactModInfo info = new CompactModInfo();
PatternParseResult patternInfo = LdmlPatternInfo.parse(patternString);
- reference.setPatternInfo(patternInfo);
- info.mod = reference.createImmutable();
+ buildReference.setPatternInfo(patternInfo);
+ info.mod = buildReference.createImmutable();
info.numDigits = patternInfo.positive.totalIntegerDigits;
precomputedMods.put(patternString, info);
}
+ return precomputedMods;
}
private static class CompactModInfo {
public int numDigits;
}
- @Override
- public QuantityChain chain(QuantityChain parent) {
- this.parent = parent;
- return this;
- }
-
@Override
public MicroProps withQuantity(FormatQuantity input) {
MicroProps micros = parent.withQuantity(input);
public class GroupingImpl extends Grouping.Internal {
// Conveniences for Java handling of shorts
- private static final short S2 = 2;
- private static final short S3 = 3;
+ private static final byte B2 = 2;
+ private static final byte B3 = 3;
// For the "placeholder constructor"
public static final char TYPE_PLACEHOLDER = 0;
// Statically initialized objects (cannot be used statically by other ICU classes)
static final GroupingImpl NONE = new GroupingImpl(TYPE_NONE);
- static final GroupingImpl GROUPING_3 = new GroupingImpl(S3, S3, false);
- static final GroupingImpl GROUPING_3_2 = new GroupingImpl(S3, S2, false);
- static final GroupingImpl GROUPING_3_MIN2 = new GroupingImpl(S3, S3, true);
- static final GroupingImpl GROUPING_3_2_MIN2 = new GroupingImpl(S3, S2, true);
+ static final GroupingImpl GROUPING_3 = new GroupingImpl(B3, B3, false);
+ static final GroupingImpl GROUPING_3_2 = new GroupingImpl(B3, B2, false);
+ static final GroupingImpl GROUPING_3_MIN2 = new GroupingImpl(B3, B3, true);
+ static final GroupingImpl GROUPING_3_2_MIN2 = new GroupingImpl(B3, B2, true);
- static GroupingImpl getInstance(short grouping1, short grouping2, boolean min2) {
+ static GroupingImpl getInstance(byte grouping1, byte grouping2, boolean min2) {
if (grouping1 == -1) {
return NONE;
} else if (!min2 && grouping1 == 3 && grouping2 == 3) {
}
final IGrouping lambda;
- final short grouping1; // -2 means "needs locale data"; -1 means "no grouping"
- final short grouping2;
+ final byte grouping1; // -2 means "needs locale data"; -1 means "no grouping"
+ final byte grouping2;
final boolean min2;
/** The "placeholder constructor". Pass in one of the GroupingImpl.TYPE_* variables. */
}
}
- private GroupingImpl(short grouping1, short grouping2, boolean min2) {
+ private GroupingImpl(byte grouping1, byte grouping2, boolean min2) {
this.lambda = null;
this.grouping1 = grouping1;
this.grouping2 = grouping2;
return this;
}
assert lambda == null;
- short grouping1 = (short) (patternInfo.positive.groupingSizes & 0xffff);
- short grouping2 = (short) ((patternInfo.positive.groupingSizes >>> 16) & 0xffff);
- short grouping3 = (short) ((patternInfo.positive.groupingSizes >>> 32) & 0xffff);
+ // TODO: short or byte?
+ byte grouping1 = (byte) (patternInfo.positive.groupingSizes & 0xffff);
+ byte grouping2 = (byte) ((patternInfo.positive.groupingSizes >>> 16) & 0xffff);
+ byte grouping3 = (byte) ((patternInfo.positive.groupingSizes >>> 32) & 0xffff);
if (grouping2 == -1) {
grouping1 = -1;
}
package newapi.impl;
import newapi.NumberFormatter.IntegerWidth;
+import newapi.NumberFormatter.Rounding;
public final class IntegerWidthImpl extends IntegerWidth.Internal {
public final int minInt;
}
@Override
-public IntegerWidthImpl truncateAt(int maxInt) {
- return new IntegerWidthImpl(minInt, maxInt);
+ public IntegerWidthImpl truncateAt(int maxInt) {
+ if (maxInt == this.maxInt) {
+ return this;
+ } else if (maxInt >= 0 && maxInt < Rounding.MAX_VALUE) {
+ return new IntegerWidthImpl(minInt, maxInt);
+ } else if (maxInt == Integer.MAX_VALUE) {
+ return new IntegerWidthImpl(minInt, maxInt);
+ } else {
+ throw new IllegalArgumentException(
+ "Integer digits must be between 0 and " + Rounding.MAX_VALUE);
+ }
}
}
public AffixPatternProvider affixProvider; // not in API; for JDK compatibility mode only
public MultiplierImpl multiplier; // not in API; for JDK compatibility mode only
public PluralRules rules; // not in API; could be made public in the future
+ public Long threshold; // not in API; controls internal self-regulation threshold
public ULocale loc;
/**
try {
return super.clone();
} catch (CloneNotSupportedException e) {
- throw new AssertionError();
+ throw new AssertionError(e);
}
}
}
public int multiplier;
public boolean useCurrency;
- private boolean frozen = false;
-
- public void enableCloneInChain() {
- frozen = true;
- }
-
- @Override
- public QuantityChain chain(QuantityChain parent) {
- // The MicroProps instance should always be at the top of the chain!
- throw new AssertionError();
+ private final boolean immutable;
+
+ /**
+ * @param immutable Whether this MicroProps should behave as an immutable after construction with
+ * respect to the quantity chain.
+ */
+ public MicroProps(boolean immutable) {
+ this.immutable = immutable;
}
@Override
public MicroProps withQuantity(FormatQuantity quantity) {
- if (frozen) {
+ if (immutable) {
return (MicroProps) this.clone();
} else {
return this;
try {
return super.clone();
} catch (CloneNotSupportedException e) {
- throw new AssertionError();
+ throw new AssertionError(e);
}
}
}
import com.ibm.icu.impl.number.FormatQuantity;
-public class MultiplierImpl implements QuantityChain {
+public class MultiplierImpl implements QuantityChain, Cloneable {
final int magnitudeMultiplier;
final BigDecimal bigDecimalMultiplier;
- /* final */ QuantityChain parent;
+ final QuantityChain parent;
public MultiplierImpl(int magnitudeMultiplier) {
this.magnitudeMultiplier = magnitudeMultiplier;
this.bigDecimalMultiplier = null;
+ parent = null;
}
public MultiplierImpl(BigDecimal bigDecimalMultiplier) {
this.magnitudeMultiplier = 0;
this.bigDecimalMultiplier = bigDecimalMultiplier;
+ parent = null;
}
- @Override
- public QuantityChain chain(QuantityChain parent) {
+ private MultiplierImpl(MultiplierImpl base, QuantityChain parent) {
+ this.magnitudeMultiplier = base.magnitudeMultiplier;
+ this.bigDecimalMultiplier = base.bigDecimalMultiplier;
this.parent = parent;
- return this;
+ }
+
+ public QuantityChain copyAndChain(QuantityChain parent) {
+ return new MultiplierImpl(this, parent);
}
@Override
import com.ibm.icu.impl.number.AffixPatternUtils;
import com.ibm.icu.impl.number.AffixPatternUtils.SymbolProvider;
import com.ibm.icu.impl.number.FormatQuantity;
+import com.ibm.icu.impl.number.LdmlPatternInfo;
import com.ibm.icu.impl.number.Modifier;
import com.ibm.icu.impl.number.NumberStringBuilder;
import com.ibm.icu.impl.number.modifiers.ConstantMultiFieldModifier;
import newapi.NumberFormatter.SignDisplay;
/**
- * This is a MUTABLE, NON-THREAD-SAFE class designed for performance. Do NOT save references to this
- * or attempt to use it from multiple threads!!!
+ * This class is a {@link Modifier} that wraps a decimal format pattern. It applies the pattern's
+ * affixes in {@link Modifier#apply}.
*
- * <p>This class takes a parsed pattern and returns a Modifier, without creating any objects. When
- * the Modifier methods are called, symbols are substituted directly into the output
- * NumberStringBuilder, without creating any intermediate Strings.
+ * <p>In addition to being a Modifier, this class contains the business logic for substituting the
+ * correct locale symbols into the affixes of the decimal format pattern.
+ *
+ * <p>In order to use this class, create a new instance and call the following four setters: {@link
+ * #setPatternInfo}, {@link #setPatternAttributes}, {@link #setSymbols}, and {@link
+ * #setNumberProperties}. After calling these four setters, the instance will be ready for use as a
+ * Modifier.
+ *
+ * <p>This is a MUTABLE, NON-THREAD-SAFE class designed for performance. Do NOT save references to
+ * this or attempt to use it from multiple threads! Instead, you can obtain a safe, immutable
+ * decimal format pattern modifier by calling {@link MurkyModifier#createImmutable}, in effect
+ * treating this instance as a builder for the immutable variant.
*/
public class MurkyModifier implements Modifier, SymbolProvider, CharSequence, QuantityChain {
boolean prependSign;
boolean plusReplacesMinusSign;
+ /**
+ * @param isStrong Whether the modifier should be considered strong. For more information, see
+ * {@link Modifier#isStrong()}. Most of the time, decimal format pattern modifiers should be
+ * considered as non-strong.
+ */
public MurkyModifier(boolean isStrong) {
this.isStrong = isStrong;
}
+ /**
+ * Sets a reference to the parsed decimal format pattern, usually obtained from {@link
+ * LdmlPatternInfo#parse(String)}, but any implementation of {@link AffixPatternProvider} is
+ * accepted.
+ */
public void setPatternInfo(AffixPatternProvider patternInfo) {
this.patternInfo = patternInfo;
}
+ /**
+ * Sets attributes that imply changes to the literal interpretation of the pattern string affixes.
+ *
+ * @param signDisplay Whether to force a plus sign on positive numbers.
+ * @param perMille Whether to substitute the percent sign in the pattern with a permille sign.
+ */
public void setPatternAttributes(SignDisplay signDisplay, boolean perMille) {
this.signDisplay = signDisplay;
this.perMilleReplacesPercent = perMille;
}
+ /**
+ * Sets locale-specific details that affect the symbols substituted into the pattern string
+ * affixes.
+ *
+ * @param symbols The desired instance of DecimalFormatSymbols.
+ * @param currency The currency to be used when substituting currency values into the affixes.
+ * Cannot be null, but a bogus currency like "XXX" can be used.
+ * @param unitWidth The width used to render currencies.
+ * @param rules Required if the triple currency sign, "¤¤¤", appears in the pattern, which can be
+ * determined from the convenience method {@link #needsPlurals()}.
+ */
public void setSymbols(
DecimalFormatSymbols symbols, Currency currency, FormatWidth unitWidth, PluralRules rules) {
assert (rules != null) == needsPlurals();
}
}
+ /**
+ * Sets attributes of the current number being processed.
+ *
+ * @param isNegative Whether the number is negative.
+ * @param plural The plural form of the number, required only if the pattern contains the triple
+ * currency sign, "¤¤¤" (and as indicated by {@link #needsPlurals()}).
+ */
public void setNumberProperties(boolean isNegative, StandardPlural plural) {
assert (plural != null) == needsPlurals();
this.isNegative = isNegative;
/**
* Returns true if the pattern represented by this MurkyModifier requires a plural keyword in
* order to localize. This is currently true only if there is a currency long name placeholder in
- * the pattern.
+ * the pattern ("¤¤¤").
*/
public boolean needsPlurals() {
return patternInfo.containsSymbolType(AffixPatternUtils.TYPE_CURRENCY_TRIPLE);
}
- @Override
- public QuantityChain chain(QuantityChain parent) {
- this.parent = parent;
- return this;
- }
-
- @Override
- public MicroProps withQuantity(FormatQuantity fq) {
- MicroProps micros = parent.withQuantity(fq);
- if (needsPlurals()) {
- // TODO: Fix this. Avoid the copy.
- FormatQuantity copy = fq.createCopy();
- micros.rounding.apply(copy);
- setNumberProperties(fq.isNegative(), copy.getStandardPlural(rules));
- } else {
- setNumberProperties(fq.isNegative(), null);
- }
- micros.modMiddle = this;
- return micros;
+ /**
+ * Creates a new quantity-dependent Modifier that behaves the same as the current instance, but
+ * which is immutable and can be saved for future use. The number properties in the current instance
+ * are mutated; all other properties are left untouched.
+ *
+ * <p>The resulting modifier cannot be used in a QuantityChain.
+ *
+ * @return An immutable that supports both positive and negative numbers.
+ */
+ public ImmutableMurkyModifier createImmutable() {
+ return createImmutableAndChain(null);
}
/**
* Creates a new quantity-dependent Modifier that behaves the same as the current instance, but
- * which is immutable and can be saved for future use. The current instance is not changed by
- * calling this method except for the number properties.
+ * which is immutable and can be saved for future use. The number properties in the current instance
+ * are mutated; all other properties are left untouched.
*
+ * @param parent The QuantityChain to which to chain this immutable.
* @return An immutable that supports both positive and negative numbers.
*/
- public ImmutableMurkyModifier createImmutable() {
+ public ImmutableMurkyModifier createImmutableAndChain(QuantityChain parent) {
NumberStringBuilder a = new NumberStringBuilder();
NumberStringBuilder b = new NumberStringBuilder();
if (needsPlurals()) {
mods[ImmutableMurkyModifierWithPlurals.getModIndex(false, plural)] = positive;
mods[ImmutableMurkyModifierWithPlurals.getModIndex(true, plural)] = negative;
}
- return new ImmutableMurkyModifierWithPlurals(mods, rules);
+ return new ImmutableMurkyModifierWithPlurals(mods, rules, parent);
} else {
// Faster path when plural keyword is not needed.
setNumberProperties(false, null);
Modifier positive = createConstantModifier(a, b);
setNumberProperties(true, null);
Modifier negative = createConstantModifier(a, b);
- return new ImmutableMurkyModifierWithoutPlurals(positive, negative);
+ return new ImmutableMurkyModifierWithoutPlurals(positive, negative, parent);
}
}
public static class ImmutableMurkyModifierWithoutPlurals implements ImmutableMurkyModifier {
final Modifier positive;
final Modifier negative;
- /* final */ QuantityChain parent;
+ final QuantityChain parent;
- public ImmutableMurkyModifierWithoutPlurals(Modifier positive, Modifier negative) {
+ public ImmutableMurkyModifierWithoutPlurals(
+ Modifier positive, Modifier negative, QuantityChain parent) {
this.positive = positive;
this.negative = negative;
- }
-
- @Override
- public QuantityChain chain(QuantityChain parent) {
this.parent = parent;
- return this;
}
@Override
public MicroProps withQuantity(FormatQuantity quantity) {
+ assert parent != null;
MicroProps micros = parent.withQuantity(quantity);
applyToMicros(micros, quantity);
return micros;
public static class ImmutableMurkyModifierWithPlurals implements ImmutableMurkyModifier {
final Modifier[] mods;
final PluralRules rules;
- /* final */ QuantityChain parent;
+ final QuantityChain parent;
- public ImmutableMurkyModifierWithPlurals(Modifier[] mods, PluralRules rules) {
+ public ImmutableMurkyModifierWithPlurals(
+ Modifier[] mods, PluralRules rules, QuantityChain parent) {
assert mods.length == getModsLength();
assert rules != null;
this.mods = mods;
this.rules = rules;
+ this.parent = parent;
}
public static int getModsLength() {
return plural.ordinal() * 2 + (isNegative ? 1 : 0);
}
- @Override
- public QuantityChain chain(QuantityChain parent) {
- this.parent = parent;
- return this;
- }
-
@Override
public MicroProps withQuantity(FormatQuantity quantity) {
+ assert parent != null;
MicroProps micros = parent.withQuantity(quantity);
applyToMicros(micros, quantity);
return micros;
}
}
+ public QuantityChain addToChain(QuantityChain parent) {
+ this.parent = parent;
+ return this;
+ }
+
+ @Override
+ public MicroProps withQuantity(FormatQuantity fq) {
+ MicroProps micros = parent.withQuantity(fq);
+ if (needsPlurals()) {
+ // TODO: Fix this. Avoid the copy.
+ FormatQuantity copy = fq.createCopy();
+ micros.rounding.apply(copy);
+ setNumberProperties(fq.isNegative(), copy.getStandardPlural(rules));
+ } else {
+ setNumberProperties(fq.isNegative(), null);
+ }
+ micros.modMiddle = this;
+ return micros;
+ }
+
@Override
public int apply(NumberStringBuilder output, int leftIndex, int rightIndex) {
int prefixLen = insertPrefix(output, leftIndex);
// Should the output render '+' where '-' would normally appear in the pattern?
plusReplacesMinusSign =
!isNegative
- && signDisplay == SignDisplay.ALWAYS_SHOWN
+ && signDisplay == SignDisplay.ALWAYS
&& patternInfo.positiveHasPlusSign() == false;
// Should we use the negative affix pattern? (If not, we will use the positive one)
if (!isPrefix || useNegativeAffixPattern) {
prependSign = false;
} else if (isNegative) {
- prependSign = signDisplay != SignDisplay.NEVER_SHOWN;
+ prependSign = signDisplay != SignDisplay.NEVER;
} else {
prependSign = plusReplacesMinusSign;
}
import newapi.NumberFormatter.NotationCompact;
import newapi.NumberFormatter.NotationScientific;
+import newapi.NumberFormatter.Rounding;
import newapi.NumberFormatter.SignDisplay;
@SuppressWarnings("deprecation")
@Override
public NotationScientific withMinExponentDigits(int minExponentDigits) {
- NotationScientificImpl other = (NotationScientificImpl) this.clone();
- other.minExponentDigits = minExponentDigits;
- return other;
+ if (minExponentDigits >= 0 && minExponentDigits < Rounding.MAX_VALUE) {
+ NotationScientificImpl other = (NotationScientificImpl) this.clone();
+ other.minExponentDigits = minExponentDigits;
+ return other;
+ } else {
+ throw new IllegalArgumentException(
+ "Integer digits must be between 0 and " + Rounding.MAX_VALUE);
+ }
}
@Override
import java.util.Locale;
import java.util.Objects;
-import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLongFieldUpdater;
import com.ibm.icu.impl.number.FormatQuantity4;
import com.ibm.icu.impl.number.FormatQuantityBCD;
private static final NumberFormatterImpl BASE = new NumberFormatterImpl();
- // TODO: Set a good value here.
- static final int DEFAULT_THRESHOLD = 3;
-
static final int KEY_MACROS = 0;
static final int KEY_LOCALE = 1;
static final int KEY_NOTATION = 2;
static final int KEY_UNIT_WIDTH = 9;
static final int KEY_SIGN = 10;
static final int KEY_DECIMAL = 11;
- static final int KEY_MAX = 12;
+ static final int KEY_THRESHOLD = 12;
+ static final int KEY_MAX = 13;
public static NumberFormatterImpl with() {
return BASE;
return fromMacros(macros);
}
+ static final AtomicLongFieldUpdater<NumberFormatterImpl> callCount =
+ AtomicLongFieldUpdater.newUpdater(NumberFormatterImpl.class, "callCountInternal");
+
// TODO: Reduce the number of fields.
final NumberFormatterImpl parent;
final int key;
final Object value;
volatile MacroProps resolvedMacros;
- volatile AtomicInteger callCount;
+ volatile long callCountInternal; // do not access directly; use callCount instead
volatile NumberFormatterImpl savedWithUnit;
volatile Worker1 compiled;
- /** Base constructor; called during startup only */
+ /** Base constructor; called during startup only. Sets the threshold to the default value of 3. */
private NumberFormatterImpl() {
parent = null;
- key = -1;
- value = null;
+ key = KEY_THRESHOLD;
+ value = new Long(3);
}
/** Primary constructor */
return new NumberFormatterImpl(this, KEY_LOCALE, locale);
}
+ /**
+ * Internal fluent setter to support a custom regulation threshold. A threshold of 1 causes the
+ * data structures to be built right away. A threshold of 0 prevents the data structures from
+ * being built.
+ */
+ public NumberFormatterImpl threshold(Long threshold) {
+ return new NumberFormatterImpl(this, KEY_THRESHOLD, threshold);
+ }
+
@Override
public String toSkeleton() {
return SkeletonBuilder.macrosToSkeleton(resolve());
@Override
public NumberFormatterResult format(long input) {
- return format(new FormatQuantity4(input), DEFAULT_THRESHOLD);
+ return format(new FormatQuantity4(input));
}
@Override
public NumberFormatterResult format(double input) {
- return format(new FormatQuantity4(input), DEFAULT_THRESHOLD);
+ return format(new FormatQuantity4(input));
}
@Override
public NumberFormatterResult format(Number input) {
- return format(new FormatQuantity4(input), DEFAULT_THRESHOLD);
+ return format(new FormatQuantity4(input));
}
@Override
public NumberFormatterResult format(Measure input) {
- return formatWithThreshold(input, DEFAULT_THRESHOLD);
- }
-
- /**
- * Internal version of format with support for a custom regulation threshold. A threshold of 1
- * causes the data structures to be built right away. A threshold of 0 prevents the data
- * structures from being built.
- */
- public NumberFormatterResult formatWithThreshold(Number number, int threshold) {
- return format(new FormatQuantity4(number), threshold);
- }
-
- /**
- * Internal version of format with support for a custom regulation threshold. A threshold of 1
- * causes the data structures to be built right away. A threshold of 0 prevents the data
- * structures from being built.
- */
- public NumberFormatterResult formatWithThreshold(Measure input, int threshold) {
MeasureUnit unit = input.getUnit();
Number number = input.getNumber();
// Use this formatter if possible
if (Objects.equals(resolve().unit, unit)) {
- return formatWithThreshold(number, threshold);
+ return format(number);
}
// This mechanism saves the previously used unit, so if the user calls this method with the
// same unit multiple times in a row, they get a more efficient code path.
withUnit = new NumberFormatterImpl(this, KEY_UNIT, unit);
savedWithUnit = withUnit;
}
- return withUnit.formatWithThreshold(number, threshold);
+ return withUnit.format(number);
}
- private NumberFormatterResult format(FormatQuantityBCD fq, int threshold) {
+ /**
+ * This is the core entrypoint to the number formatting pipeline. It performs self-regulation: a
+ * static code path for the first few calls, and compiling a more efficient data structure if
+ * called repeatedly.
+ *
+ * @param fq The quantity to be formatted.
+ * @return The formatted number result.
+ */
+ private NumberFormatterResult format(FormatQuantityBCD fq) {
+ MacroProps macros = resolve();
NumberStringBuilder string = new NumberStringBuilder();
- // Lazily create the AtomicInteger
- if (callCount == null) {
- callCount = new AtomicInteger();
- }
- int currentCount = callCount.incrementAndGet();
+ long currentCount = callCount.incrementAndGet(this);
MicroProps micros;
- if (currentCount == threshold) {
- compiled = Worker1.fromMacros(resolve());
+ if (currentCount == macros.threshold.longValue()) {
+ compiled = Worker1.fromMacros(macros);
micros = compiled.apply(fq, string);
} else if (compiled != null) {
micros = compiled.apply(fq, string);
} else {
- micros = Worker1.applyStatic(resolve(), fq, string);
+ micros = Worker1.applyStatic(macros, fq, string);
}
return new NumberFormatterResult(string, fq, micros);
}
// of a MacroProps object at each step.
MacroProps macros = new MacroProps();
NumberFormatterImpl current = this;
- while (current != BASE) {
+ while (current != null) {
switch (current.key) {
case KEY_MACROS:
macros.fallback((MacroProps) current.value);
macros.decimal = (DecimalMarkDisplay) current.value;
}
break;
+ case KEY_THRESHOLD:
+ if (macros.threshold == null) {
+ macros.threshold = (Long) current.value;
+ }
+ break;
default:
- throw new AssertionError();
+ throw new AssertionError("Unknown key: " + current.key);
}
current = current.parent;
}
grouping2 = grouping2 > 0 ? grouping2 : grouping1;
// TODO: Is it important to handle minGrouping > 2?
macros.grouping =
- GroupingImpl.getInstance((short) grouping1, (short) grouping2, minGrouping == 2);
+ GroupingImpl.getInstance((byte) grouping1, (byte) grouping2, minGrouping == 2);
/////////////
// PADDING //
macros.decimal =
properties.getDecimalSeparatorAlwaysShown()
- ? DecimalMarkDisplay.ALWAYS_SHOWN
+ ? DecimalMarkDisplay.ALWAYS
: DecimalMarkDisplay.AUTO;
///////////////////////
// SIGN ALWAYS SHOWN //
///////////////////////
- macros.sign = properties.getSignAlwaysShown() ? SignDisplay.ALWAYS_SHOWN : SignDisplay.AUTO;
+ macros.sign = properties.getSignAlwaysShown() ? SignDisplay.ALWAYS : SignDisplay.AUTO;
/////////////////////////
// SCIENTIFIC NOTATION //
properties.getMinimumExponentDigits(),
// Exponent sign always shown:
properties.getExponentSignAlwaysShown()
- ? SignDisplay.ALWAYS_SHOWN
+ ? SignDisplay.ALWAYS
: SignDisplay.AUTO);
// Scientific notation also involves overriding the rounding mode.
if (macros.rounding instanceof RoundingImplFraction) {
// Add the decimal point
if (input.getLowerDisplayMagnitude() < 0
- || micros.decimal == DecimalMarkDisplay.ALWAYS_SHOWN) {
+ || micros.decimal == DecimalMarkDisplay.ALWAYS) {
length +=
string.insert(
length,
import com.ibm.icu.impl.number.FormatQuantity;
public interface QuantityChain {
- QuantityChain chain(QuantityChain parent);
+ //QuantityChain addToChain(QuantityChain parent);
MicroProps withQuantity(FormatQuantity quantity);
}
\ No newline at end of file
public class QuantityDependentModOuter implements QuantityChain {
final Map<StandardPlural, Modifier> data;
final PluralRules rules;
- /* final */ QuantityChain parent;
+ final QuantityChain parent;
- public QuantityDependentModOuter(Map<StandardPlural, Modifier> data, PluralRules rules) {
+ public QuantityDependentModOuter(Map<StandardPlural, Modifier> data, PluralRules rules, QuantityChain parent) {
this.data = data;
this.rules = rules;
- }
-
- @Override
- public QuantityChain chain(QuantityChain parent) {
this.parent = parent;
- return this;
}
@Override
final NotationImpl.NotationScientificImpl notation;
final DecimalFormatSymbols symbols;
final ScientificModifier[] precomputedMods;
- /* final */ QuantityChain parent;
+ final QuantityChain parent;
public static ScientificImpl getInstance(
- NotationImpl.NotationScientificImpl notation, DecimalFormatSymbols symbols, boolean build) {
- return new ScientificImpl(notation, symbols, build);
+ NotationImpl.NotationScientificImpl notation,
+ DecimalFormatSymbols symbols,
+ boolean build,
+ QuantityChain parent) {
+ return new ScientificImpl(notation, symbols, build, parent);
}
private ScientificImpl(
- NotationImpl.NotationScientificImpl notation, DecimalFormatSymbols symbols, boolean build) {
+ NotationImpl.NotationScientificImpl notation,
+ DecimalFormatSymbols symbols,
+ boolean build,
+ QuantityChain parent) {
this.notation = notation;
this.symbols = symbols;
+ this.parent = parent;
if (build) {
// Pre-build the modifiers for exponents -12 through 12
}
}
- @Override
- public QuantityChain chain(QuantityChain parent) {
- this.parent = parent;
- return this;
- }
-
@Override
public MicroProps withQuantity(FormatQuantity quantity) {
MicroProps micros = parent.withQuantity(quantity);
int i = rightIndex;
// Append the exponent separator and sign
i += output.insert(i, symbols.getExponentSeparator(), NumberFormat.Field.EXPONENT_SYMBOL);
- if (exponent < 0 && notation.exponentSignDisplay != SignDisplay.NEVER_SHOWN) {
+ if (exponent < 0 && notation.exponentSignDisplay != SignDisplay.NEVER) {
i += output.insert(i, symbols.getMinusSignString(), NumberFormat.Field.EXPONENT_SIGN);
- } else if (notation.exponentSignDisplay == SignDisplay.ALWAYS_SHOWN) {
+ } else if (notation.exponentSignDisplay == SignDisplay.ALWAYS) {
i += output.insert(i, symbols.getPlusSignString(), NumberFormat.Field.EXPONENT_SIGN);
}
// Append the exponent digits (using a simple inline algorithm)
import java.math.MathContext;
import java.math.RoundingMode;
+import com.ibm.icu.impl.number.formatters.PaddingFormat.PadPosition;
import com.ibm.icu.text.CompactDecimalFormat.CompactStyle;
import com.ibm.icu.text.DecimalFormatSymbols;
import com.ibm.icu.text.MeasureFormat.FormatWidth;
if (notation.engineeringInterval != 1) {
sb.append(notation.engineeringInterval);
}
- if (notation.exponentSignDisplay == SignDisplay.ALWAYS_SHOWN) {
+ if (notation.exponentSignDisplay == SignDisplay.ALWAYS) {
sb.append('+');
- } else if (notation.exponentSignDisplay == SignDisplay.NEVER_SHOWN) {
+ } else if (notation.exponentSignDisplay == SignDisplay.NEVER) {
sb.append('!');
} else {
assert notation.exponentSignDisplay == SignDisplay.AUTO;
c = safeCharAt(skeleton, offset++);
}
if (c == '+') {
- sign = SignDisplay.ALWAYS_SHOWN;
+ sign = SignDisplay.ALWAYS;
c = safeCharAt(skeleton, offset++);
}
if (c == '!') {
- sign = SignDisplay.NEVER_SHOWN;
+ sign = SignDisplay.NEVER;
c = safeCharAt(skeleton, offset++);
}
while (c == '0') {
sb.append('$');
sb.append(value.getSubtype());
} else {
- sb.append('U');
+ sb.append("U:");
sb.append(value.getType());
sb.append(':');
sb.append(value.getSubtype());
}
if (value.equals(Grouping.DEFAULT)) {
sb.append("DEFAULT");
- } else if (value.equals(Grouping.DEFAULT_MIN_2_DIGITS)) {
+ } else if (value.equals(Grouping.MIN_2_DIGITS)) {
sb.append("DEFAULT_MIN_2_DIGITS");
} else if (value.equals(Grouping.NONE)) {
sb.append("NONE");
if (name.equals("DEFAULT")) {
result = Grouping.DEFAULT;
} else if (name.equals("DEFAULT_MIN_2_DIGITS")) {
- result = Grouping.DEFAULT_MIN_2_DIGITS;
+ result = Grouping.MIN_2_DIGITS;
} else if (name.equals("NONE")) {
result = Grouping.NONE;
}
sb.append("NONE");
return;
}
- sb.append("CP:");
- // TODO: Handle padding strings that contain ':'
- sb.append(padding.paddingString);
- sb.append(':');
sb.append(padding.targetWidth);
sb.append(':');
sb.append(padding.position.name());
+ sb.append(':');
+ if (!padding.paddingString.equals(" ")) {
+ sb.append(padding.paddingString);
+ }
+ }
+
+ private static int skeletonToPadding(String skeleton, int offset, MacroProps output) {
+ int originalOffset = offset;
+ char c0 = skeleton.charAt(offset++);
+ if (c0 == 'N') {
+ offset += consumeUntil(skeleton, --offset, ' ', null);
+ } else if ('0' <= c0 && c0 <= '9') {
+ long intResult = consumeInt(skeleton, --offset);
+ offset += intResult & 0xffffffff;
+ int width = (int) (intResult >>> 32);
+ char c1 = safeCharAt(skeleton, offset++);
+ if (c1 != ':') {
+ return offset - originalOffset - 1;
+ }
+ StringBuilder sb = new StringBuilder();
+ offset += consumeUntil(skeleton, offset, ':', sb);
+ String padPositionString = sb.toString();
+ sb.setLength(0);
+ offset += consumeUntil(skeleton, offset, ' ', sb);
+ String string = (sb.length() == 0) ? " " : sb.toString();
+ PadPosition position = Enum.valueOf(PadPosition.class, padPositionString);
+ output.padding = PaddingImpl.getInstance(string, width, position);
+ }
+ return offset - originalOffset;
}
private static void integerWidthToSkeleton(IntegerWidth value, StringBuilder sb) {
}
}
+ private static int skeletonToIntegerWidth(String skeleton, int offset, MacroProps output) {
+ int originalOffset = offset;
+ long intResult = consumeInt(skeleton, offset);
+ offset += intResult & 0xffffffff;
+ int minInt = (int) (intResult >>> 32);
+ char c1 = safeCharAt(skeleton, --offset);
+ int maxInt;
+ if (c1 == '-') {
+ intResult = consumeInt(skeleton, offset);
+ offset += intResult & 0xffffffff;
+ maxInt = (int) (intResult >>> 32);
+ }
+ }
+
private static void symbolsToSkeleton(Object value, StringBuilder sb) {
if (value instanceof DecimalFormatSymbols) {
// TODO: Check to see if any of the symbols are not default?
sb.append(value.name());
}
+ private static int skeletonToUnitWidth(String skeleton, int offset, MacroProps output) {
+ int originalOffset = offset;
+ StringBuilder sb = new StringBuilder();
+ offset += consumeUntil(skeleton, offset, ' ', sb);
+ output.unitWidth = Enum.valueOf(FormatWidth.class, sb.toString());
+ return offset - originalOffset;
+ }
+
private static void signToSkeleton(SignDisplay value, StringBuilder sb) {
sb.append(value.name());
}
+ private static int skeletonToSign(String skeleton, int offset, MacroProps output) {
+ int originalOffset = offset;
+ StringBuilder sb = new StringBuilder();
+ offset += consumeUntil(skeleton, offset, ' ', sb);
+ output.sign = Enum.valueOf(SignDisplay.class, sb.toString());
+ return offset - originalOffset;
+ }
+
private static void decimalToSkeleton(DecimalMarkDisplay value, StringBuilder sb) {
sb.append(value.name());
}
+ private static int skeletonToDecimal(String skeleton, int offset, MacroProps output) {
+ int originalOffset = offset;
+ StringBuilder sb = new StringBuilder();
+ offset += consumeUntil(skeleton, offset, ' ', sb);
+ output.decimal = Enum.valueOf(DecimalMarkDisplay.class, sb.toString());
+ return offset - originalOffset;
+ }
+
private static char safeCharAt(String str, int offset) {
if (offset < str.length()) {
return str.charAt(offset);
int originalOffset = offset;
char c = safeCharAt(skeleton, offset++);
while (c != brk) {
- sb.append(c);
+ if (sb != null) sb.append(c);
c = safeCharAt(skeleton, offset++);
}
return offset - originalOffset;
}
+
+ private static long consumeInt(String skeleton, int offset) {
+ int originalOffset = offset;
+ char c = safeCharAt(skeleton, offset++);
+ int result = 0;
+ while ('0' <= c && c <= '9') {
+ result = (result * 10) + (c - '0');
+ c = safeCharAt(skeleton, offset++);
+ }
+ return (offset - originalOffset) | (((long) result) << 32);
+ }
}
boolean perMille = false;
PluralRules rules = input.rules;
- MicroProps micros = new MicroProps();
+ MicroProps micros = new MicroProps(build);
QuantityChain chain = micros;
// Copy over the simple settings
// An int magnitude multiplier is used when not in compatibility mode to
// reduce object creations.
if (input.multiplier != null) {
- // TODO: Make sure this is thread safe.
- chain = input.multiplier.chain(chain);
+ chain = input.multiplier.copyAndChain(chain);
}
// Rounding strategy
micros.grouping = GroupingImpl.normalizeType(input.grouping, patternInfo);
} else if (input.notation instanceof NotationCompact) {
// Compact notation uses minGrouping by default since ICU 59
- micros.grouping = GroupingImpl.normalizeType(Grouping.DEFAULT_MIN_2_DIGITS, patternInfo);
+ micros.grouping = GroupingImpl.normalizeType(Grouping.MIN_2_DIGITS, patternInfo);
} else {
micros.grouping = GroupingImpl.normalizeType(Grouping.DEFAULT, patternInfo);
}
assert input.notation instanceof NotationImpl.NotationScientificImpl;
chain =
ScientificImpl.getInstance(
- (NotationImpl.NotationScientificImpl) input.notation, micros.symbols, build)
- .chain(chain);
+ (NotationImpl.NotationScientificImpl) input.notation, micros.symbols, build, chain);
} else {
// No inner modifier required
micros.modInner = ConstantAffixModifier.EMPTY;
}
// Middle modifier (patterns, positive/negative, currency symbols, percent)
+ // The default middle modifier is weak (thus the false argument).
MurkyModifier murkyMod = new MurkyModifier(false);
murkyMod.setPatternInfo((input.affixProvider != null) ? input.affixProvider : patternInfo);
murkyMod.setPatternAttributes(micros.sign, perMille);
murkyMod.setSymbols(micros.symbols, currency, unitWidth, null);
}
if (build) {
- chain = murkyMod.createImmutable().chain(chain);
+ chain = murkyMod.createImmutableAndChain(chain);
} else {
- chain = murkyMod.chain(chain);
+ chain = murkyMod.addToChain(chain);
}
// Outer modifier (CLDR units and currency long names)
// Lazily create PluralRules
rules = PluralRules.forLocale(input.loc);
}
- chain = new QuantityDependentModOuter(outerMods, rules).chain(chain);
+ chain = new QuantityDependentModOuter(outerMods, rules, chain);
} else {
// No outer modifier required
micros.modOuter = ConstantAffixModifier.EMPTY;
rules = PluralRules.forLocale(input.loc);
}
CompactStyle compactStyle = ((NotationImpl.NotationCompactImpl) input.notation).compactStyle;
- CompactImpl worker;
if (compactStyle == null) {
// Use compact custom data
- worker =
+ chain =
CompactImpl.getInstance(
- ((NotationImpl.NotationCompactImpl) input.notation).compactCustomData, rules);
+ ((NotationImpl.NotationCompactImpl) input.notation).compactCustomData,
+ rules,
+ build ? murkyMod : null,
+ chain);
} else {
CompactType compactType =
(input.unit instanceof Currency) ? CompactType.CURRENCY : CompactType.DECIMAL;
- worker = CompactImpl.getInstance(input.loc, compactType, compactStyle, rules);
- }
- if (build) {
- worker.precomputeAllModifiers(murkyMod);
+ chain =
+ CompactImpl.getInstance(
+ input.loc, compactType, compactStyle, rules, build ? murkyMod : null, chain);
}
- chain = worker.chain(chain);
- }
-
- if (build) {
- micros.enableCloneInChain();
}
return chain;
<classpathentry combineaccessrules="false" kind="src" path="/icu4j-test-framework"/>
<classpathentry combineaccessrules="false" kind="src" path="/icu4j-langdata"/>
<classpathentry combineaccessrules="false" kind="src" path="/icu4j-regiondata"/>
- <classpathentry combineaccessrules="false" kind="src" path="/icu4j-currdata"/>
+ <classpathentry combineaccessrules="false" exported="true" kind="src" path="/icu4j-currdata"/>
<classpathentry kind="lib" path="/external-libraries/hamcrest-core-1.3.jar"/>
<classpathentry kind="lib" path="/external-libraries/junit-4.12.jar" sourcepath="/external-libraries/junit-4.12-sources.jar">
<attributes>
begin
minIntegerDigits maxIntegerDigits minFractionDigits maxFractionDigits output breaks
// JDK gives 2.99792458E8 (maxInt + maxFrac instead of minInt + maxFrac)
-1 1000 0 5 2.99792E8 K
+1 99 0 5 2.99792E8 K
// JDK gives .3E9 instead of unlimited precision.
0 1 0 0 2.99792458E8 K
1 1 0 0 3E8
format multiplier output breaks
23 -12 -276
23 -1 -23
-// ICU4J and JDK throw exception on zero multiplier.
-// ICU4C and S print 23.
+// ICU4J throws exception on zero multiplier.
+// ICU4C prints 23.
// Q multiplies by zero and prints 0.
-23 0 0 CJKS
+23 0 0 CJ
23 1 23
23 12 276
-23 12 -276
begin
format roundingMode output breaks
1.24 halfUp 1.0 K
-1.25 halfUp 1.5 K
+1.25 halfUp 1.5
1.25 halfDown 1.0 K
-1.26 halfDown 1.5 K
+1.26 halfDown 1.5
1.25 halfEven 1.0 K
--1.01 up -1.5 K
+-1.01 up -1.5
-1.49 down -1.0 K
-1.01 up 1.5 K
+1.01 up 1.5
1.49 down 1.0 K
--1.01 ceiling -1.0
+-1.01 ceiling -1.0 K
-1.49 floor -1.5
test currency usage setters
import java.text.ParseException;
import java.text.ParsePosition;
-import org.junit.Ignore;
import org.junit.Test;
import com.ibm.icu.dev.test.TestUtil;
};
@Test
- @Ignore
public void TestDataDrivenICU58() {
// Android can't access DecimalFormat_ICU58 for testing (ticket #13283).
if (TestUtil.getJavaVendor() == TestUtil.JavaVendor.Android) return;
}
@Test
- @Ignore
public void TestDataDrivenJDK() {
// Android implements java.text.DecimalFormat with ICU4J (ticket #13322).
if (TestUtil.getJavaVendor() == TestUtil.JavaVendor.Android) return;
}
@Test
- @Ignore
public void TestDataDrivenICU59() {
DataDrivenNumberFormatTestUtility.runFormatSuiteIncludingKnownFailures(
"numberformattestspecification.txt", ICU59);
murky.setNumberProperties(false, null);
assertEquals("a", murky.getPrefix());
assertEquals("b", murky.getSuffix());
- murky.setPatternAttributes(SignDisplay.ALWAYS_SHOWN, false);
+ murky.setPatternAttributes(SignDisplay.ALWAYS, false);
assertEquals("+a", murky.getPrefix());
assertEquals("b", murky.getSuffix());
murky.setNumberProperties(true, null);
assertEquals("-a", murky.getPrefix());
assertEquals("b", murky.getSuffix());
- murky.setPatternAttributes(SignDisplay.NEVER_SHOWN, false);
+ murky.setPatternAttributes(SignDisplay.NEVER, false);
assertEquals("a", murky.getPrefix());
assertEquals("b", murky.getSuffix());
murky.setNumberProperties(false, null);
assertEquals("a", murky.getPrefix());
assertEquals("b", murky.getSuffix());
- murky.setPatternAttributes(SignDisplay.ALWAYS_SHOWN, false);
+ murky.setPatternAttributes(SignDisplay.ALWAYS, false);
assertEquals("c+", murky.getPrefix());
assertEquals("d", murky.getSuffix());
murky.setNumberProperties(true, null);
assertEquals("c-", murky.getPrefix());
assertEquals("d", murky.getSuffix());
- murky.setPatternAttributes(SignDisplay.NEVER_SHOWN, false);
+ murky.setPatternAttributes(SignDisplay.NEVER, false);
assertEquals("c-", murky.getPrefix()); // TODO: What should this behavior be?
assertEquals("d", murky.getSuffix());
}
"Scientific sign always shown",
"E+",
NumberFormatter.with()
- .notation(Notation.SCIENTIFIC.withExponentSignDisplay(SignDisplay.ALWAYS_SHOWN)),
+ .notation(Notation.SCIENTIFIC.withExponentSignDisplay(SignDisplay.ALWAYS)),
ULocale.ENGLISH,
"8.765E+4",
"8.765E+3",
public void unitMeasure() {
assertFormatDescending(
"Meters Short",
- "Ulength:meter",
+ "U:length:meter",
NumberFormatter.with().unit(MeasureUnit.METER),
ULocale.ENGLISH,
"87,650 m",
assertFormatDescending(
"Meters Long",
- "Ulength:meter unit-width=WIDE",
+ "U:length:meter unit-width=WIDE",
NumberFormatter.with().unit(MeasureUnit.METER).unitWidth(FormatWidth.WIDE),
ULocale.ENGLISH,
"87,650 meters",
assertFormatDescending(
"Compact Meters Long",
- "CC Ulength:meter unit-width=WIDE",
+ "CC U:length:meter unit-width=WIDE",
NumberFormatter.with()
.notation(Notation.COMPACT_LONG)
.unit(MeasureUnit.METER)
assertFormatSingle(
"Meters with Negative Sign",
- "Ulength:meter",
+ "U:length:meter",
NumberFormatter.with().unit(MeasureUnit.METER),
ULocale.ENGLISH,
-9876543.21,
assertFormatDescending(
"Western Grouping, Min 2",
"%% grouping=DEFAULT_MIN_2_DIGITS",
- NumberFormatter.with().unit(Dimensionless.PERMILLE).grouping(Grouping.DEFAULT_MIN_2_DIGITS),
+ NumberFormatter.with().unit(Dimensionless.PERMILLE).grouping(Grouping.MIN_2_DIGITS),
ULocale.ENGLISH,
"87,650,000‰",
"8,765,000‰",
assertFormatDescending(
"Indic Grouping, Min 2",
"%% grouping=DEFAULT_MIN_2_DIGITS",
- NumberFormatter.with().unit(Dimensionless.PERMILLE).grouping(Grouping.DEFAULT_MIN_2_DIGITS),
+ NumberFormatter.with().unit(Dimensionless.PERMILLE).grouping(Grouping.MIN_2_DIGITS),
new ULocale("en-IN"),
"8,76,50,000‰",
"87,65,000‰",
assertFormatDescending(
"Padding",
- "padding=CP:*:8:AFTER_PREFIX",
+ "padding=8:AFTER_PREFIX:*",
NumberFormatter.with().padding(Padding.codePoints('*', 8, PadPosition.AFTER_PREFIX)),
ULocale.ENGLISH,
"**87,650",
assertFormatDescending(
"Padding with code points",
- "padding=CP:𐇤:8:AFTER_PREFIX",
+ "padding=8:AFTER_PREFIX:𐇤",
NumberFormatter.with().padding(Padding.codePoints(0x101E4, 8, PadPosition.AFTER_PREFIX)),
ULocale.ENGLISH,
"𐇤𐇤87,650",
assertFormatDescending(
"Padding with wide digits",
- "padding=CP:*:8:AFTER_PREFIX symbols=ns:mathsanb",
+ "padding=8:AFTER_PREFIX:* symbols=ns:mathsanb",
NumberFormatter.with()
.padding(Padding.codePoints('*', 8, PadPosition.AFTER_PREFIX))
.symbols(NumberingSystem.getInstanceByName("mathsanb")),
assertFormatDescending(
"Padding with currency spacing",
- "$GBP padding=CP:*:10:AFTER_PREFIX unit-width=SHORT",
+ "$GBP padding=10:AFTER_PREFIX:* unit-width=SHORT",
NumberFormatter.with()
.padding(Padding.codePoints('*', 10, PadPosition.AFTER_PREFIX))
.unit(GBP)
assertFormatSingle(
"Pad Before Prefix",
- "padding=CP:*:8:BEFORE_PREFIX",
+ "padding=8:BEFORE_PREFIX:*",
NumberFormatter.with().padding(Padding.codePoints('*', 8, PadPosition.BEFORE_PREFIX)),
ULocale.ENGLISH,
-88.88,
assertFormatSingle(
"Pad After Prefix",
- "padding=CP:*:8:AFTER_PREFIX",
+ "padding=8:AFTER_PREFIX:*",
NumberFormatter.with().padding(Padding.codePoints('*', 8, PadPosition.AFTER_PREFIX)),
ULocale.ENGLISH,
-88.88,
assertFormatSingle(
"Pad Before Suffix",
- "% padding=CP:*:8:BEFORE_SUFFIX",
+ "% padding=8:BEFORE_SUFFIX:*",
NumberFormatter.with()
.padding(Padding.codePoints('*', 8, PadPosition.BEFORE_SUFFIX))
.unit(Dimensionless.PERCENT),
assertFormatSingle(
"Pad After Suffix",
- "% padding=CP:*:8:AFTER_SUFFIX",
+ "% padding=8:AFTER_SUFFIX:*",
NumberFormatter.with()
.padding(Padding.codePoints('*', 8, PadPosition.AFTER_SUFFIX))
.unit(Dimensionless.PERCENT),
assertFormatSingle(
"Sign Always Positive",
- "sign=ALWAYS_SHOWN",
- NumberFormatter.with().sign(SignDisplay.ALWAYS_SHOWN),
+ "sign=ALWAYS",
+ NumberFormatter.with().sign(SignDisplay.ALWAYS),
ULocale.ENGLISH,
444444,
"+444,444");
assertFormatSingle(
"Sign Always Negative",
- "sign=ALWAYS_SHOWN",
- NumberFormatter.with().sign(SignDisplay.ALWAYS_SHOWN),
+ "sign=ALWAYS",
+ NumberFormatter.with().sign(SignDisplay.ALWAYS),
ULocale.ENGLISH,
-444444,
"-444,444");
assertFormatSingle(
"Sign Never Positive",
- "sign=NEVER_SHOWN",
- NumberFormatter.with().sign(SignDisplay.NEVER_SHOWN),
+ "sign=NEVER",
+ NumberFormatter.with().sign(SignDisplay.NEVER),
ULocale.ENGLISH,
444444,
"444,444");
assertFormatSingle(
"Sign Never Negative",
- "sign=NEVER_SHOWN",
- NumberFormatter.with().sign(SignDisplay.NEVER_SHOWN),
+ "sign=NEVER",
+ NumberFormatter.with().sign(SignDisplay.NEVER),
ULocale.ENGLISH,
-444444,
"444,444");
assertFormatDescending(
"Decimal Always Shown",
- "decimal=ALWAYS_SHOWN",
- NumberFormatter.with().decimal(DecimalMarkDisplay.ALWAYS_SHOWN),
+ "decimal=ALWAYS",
+ NumberFormatter.with().decimal(DecimalMarkDisplay.ALWAYS),
ULocale.ENGLISH,
"87,650.",
"8,765.",
assertEquals(message + ": Skeleton:", skeleton, f.toSkeleton());
final double[] inputs =
new double[] {87650, 8765, 876.5, 87.65, 8.765, 0.8765, 0.08765, 0.008765, 0};
- NumberFormatterImpl l1 = (NumberFormatterImpl) f.locale(locale); // no self-regulation
- NumberFormatterImpl l2 = (NumberFormatterImpl) f.locale(locale); // all self-regulation
+ NumberFormatterImpl l1 = ((NumberFormatterImpl) f).threshold(0L).locale(locale); // no self-regulation
+ NumberFormatterImpl l2 = ((NumberFormatterImpl) f).threshold(1L).locale(locale); // all self-regulation
for (int i = 0; i < 9; i++) {
double d = inputs[i];
- String actual1 = l1.formatWithThreshold(d, 0).toString();
+ String actual1 = l1.format(d).toString();
assertEquals(message + ": L1: " + d, expected[i], actual1);
- String actual2 = l2.formatWithThreshold(d, 1).toString();
+ String actual2 = l2.format(d).toString();
assertEquals(message + ": L2: " + d, expected[i], actual2);
}
}
Number input,
String expected) {
assertEquals(message + ": Skeleton:", skeleton, f.toSkeleton());
- NumberFormatterImpl l1 = (NumberFormatterImpl) f.locale(locale); // no self-regulation
- NumberFormatterImpl l2 = (NumberFormatterImpl) f.locale(locale); // all self-regulation
- String actual1 = l1.formatWithThreshold(input, 0).toString();
+ NumberFormatterImpl l1 = ((NumberFormatterImpl) f).threshold(0L).locale(locale); // no self-regulation
+ NumberFormatterImpl l2 = ((NumberFormatterImpl) f).threshold(1L).locale(locale); // all self-regulation
+ String actual1 = l1.format(input).toString();
assertEquals(message + ": L1: " + input, expected, actual1);
- String actual2 = l2.formatWithThreshold(input, 1).toString();
+ String actual2 = l2.format(input).toString();
assertEquals(message + ": L2: " + input, expected, actual2);
}
Measure input,
String expected) {
assertEquals(message + ": Skeleton:", skeleton, f.toSkeleton());
- NumberFormatterImpl l1 = (NumberFormatterImpl) f.locale(locale); // no self-regulation
- NumberFormatterImpl l2 = (NumberFormatterImpl) f.locale(locale); // all self-regulation
- String actual1 = l1.formatWithThreshold(input, 0).toString();
+ NumberFormatterImpl l1 = ((NumberFormatterImpl) f).threshold(0L).locale(locale); // no self-regulation
+ NumberFormatterImpl l2 = ((NumberFormatterImpl) f).threshold(1L).locale(locale); // all self-regulation
+ String actual1 = l1.format(input).toString();
assertEquals(message + ": L1: " + input, expected, actual1);
- String actual2 = l2.formatWithThreshold(input, 1).toString();
+ String actual2 = l2.format(input).toString();
assertEquals(message + ": L2: " + input, expected, actual2);
}
}
package com.ibm.icu.dev.test.number;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import java.text.FieldPosition;
"The quick brown fox jumps over the lazy dog",
"😁",
"mixed 😇 and ASCII",
- "with combining characters like 🇦🇧🇨🇩"
+ "with combining characters like 🇦🇧🇨🇩",
+ "A very very very very very very very very very very long string to force heap"
};
@Test
sb4.insert(4, str.toCharArray());
sb5.insert(4, str.toCharArray(), null);
assertCharSequenceEquals(sb4, sb5);
+
+ sb4.append(sb4.toString());
+ sb5.append(new NumberStringBuilder(sb5));
+ assertCharSequenceEquals(sb4, sb5);
}
}
}
}
+ @Test
+ public void testCopy() {
+ for (String str : EXAMPLE_STRINGS) {
+ NumberStringBuilder sb1 = new NumberStringBuilder();
+ sb1.append(str, null);
+ NumberStringBuilder sb2 = new NumberStringBuilder(sb1);
+ assertCharSequenceEquals(sb1, sb2);
+ assertTrue(sb1.contentEquals(sb2));
+
+ sb1.append("12345", null);
+ assertNotEquals(sb1.length(), sb2.length());
+ assertFalse(sb1.contentEquals(sb2));
+ }
+ }
+
@Test
public void testFields() {
for (String str : EXAMPLE_STRINGS) {
assertEquals(str.length() * 2, fields.length);
for (int i = 0; i < str.length(); i++) {
assertEquals(null, fields[i]);
+ assertEquals(null, sb.fieldAt(i));
assertEquals(NumberFormat.Field.CURRENCY, fields[i + str.length()]);
+ assertEquals(NumberFormat.Field.CURRENCY, sb.fieldAt(i + str.length()));
}
// Very basic FieldPosition test. More robust tests happen in NumberFormatTest.
fields = sb.toFieldArray();
for (int i = 0; i < sb.length(); i++) {
assertEquals(oldFields[i % oldFields.length], fields[i]);
- if (fields[i] == null) numNull++;
- else if (fields[i] == NumberFormat.Field.CURRENCY) numCurr++;
- else if (fields[i] == NumberFormat.Field.INTEGER) numInt++;
- else throw new AssertionError("Encountered unknown field in " + str);
+ if (fields[i] == null) {
+ numNull++;
+ } else if (fields[i] == NumberFormat.Field.CURRENCY) {
+ numCurr++;
+ } else if (fields[i] == NumberFormat.Field.INTEGER) {
+ numInt++;
+ } else {
+ throw new AssertionError("Encountered unknown field in " + str);
+ }
}
assertEquals(str.length() * 4, numNull);
assertEquals(numNull, numCurr);