package com.ibm.icu.impl.number;
import com.ibm.icu.text.DecimalFormatSymbols;
-import com.ibm.icu.text.NumberFormat.Field;
+import com.ibm.icu.text.NumberFormat;
/**
* Performs manipulations on affix patterns: the prefix and suffix strings associated with a decimal
while (hasNext(tag, affixPattern)) {
tag = nextToken(tag, affixPattern);
int typeOrCp = getTypeOrCp(tag);
+ NumberFormat.Field field = (typeOrCp < 0) ? getFieldForType(typeOrCp) : null;
switch (typeOrCp) {
case TYPE_MINUS_SIGN:
- output.append(minusSign, Field.SIGN);
+ output.append(minusSign, field);
break;
case TYPE_PLUS_SIGN:
- output.append(symbols.getPlusSignString(), Field.SIGN);
+ output.append(symbols.getPlusSignString(), field);
break;
case TYPE_PERCENT:
- output.append(symbols.getPercentString(), Field.PERCENT);
+ output.append(symbols.getPercentString(), field);
break;
case TYPE_PERMILLE:
- output.append(symbols.getPerMillString(), Field.PERMILLE);
+ output.append(symbols.getPerMillString(), field);
break;
case TYPE_CURRENCY_SINGLE:
- output.append(currency1, Field.CURRENCY);
+ output.append(currency1, field);
break;
case TYPE_CURRENCY_DOUBLE:
- output.append(currency2, Field.CURRENCY);
+ output.append(currency2, field);
break;
case TYPE_CURRENCY_TRIPLE:
- output.append(currency3, Field.CURRENCY);
+ output.append(currency3, field);
break;
case TYPE_CURRENCY_QUAD:
- output.appendCodePoint('\uFFFD', Field.CURRENCY);
+ output.appendCodePoint('\uFFFD', field);
break;
case TYPE_CURRENCY_QUINT:
// TODO: Add support for narrow currency symbols here.
- output.appendCodePoint('\uFFFD', Field.CURRENCY);
+ output.appendCodePoint('\uFFFD', field);
break;
case TYPE_CURRENCY_OVERFLOW:
- output.appendCodePoint('\uFFFD', Field.CURRENCY);
+ output.appendCodePoint('\uFFFD', field);
break;
default:
output.appendCodePoint(typeOrCp, null);
}
}
- private static final Field getFieldForType(int type) {
+ public static final NumberFormat.Field getFieldForType(int type) {
switch (type) {
case TYPE_MINUS_SIGN:
- return Field.SIGN;
+ return NumberFormat.Field.SIGN;
case TYPE_PLUS_SIGN:
- return Field.SIGN;
+ return NumberFormat.Field.SIGN;
case TYPE_PERCENT:
- return Field.PERCENT;
+ return NumberFormat.Field.PERCENT;
case TYPE_PERMILLE:
- return Field.PERMILLE;
+ return NumberFormat.Field.PERMILLE;
case TYPE_CURRENCY_SINGLE:
- return Field.CURRENCY;
+ return NumberFormat.Field.CURRENCY;
case TYPE_CURRENCY_DOUBLE:
- return Field.CURRENCY;
+ return NumberFormat.Field.CURRENCY;
case TYPE_CURRENCY_TRIPLE:
- return Field.CURRENCY;
+ return NumberFormat.Field.CURRENCY;
case TYPE_CURRENCY_QUAD:
- return Field.CURRENCY;
+ return NumberFormat.Field.CURRENCY;
case TYPE_CURRENCY_QUINT:
- return Field.CURRENCY;
+ return NumberFormat.Field.CURRENCY;
case TYPE_CURRENCY_OVERFLOW:
- return Field.CURRENCY;
+ return NumberFormat.Field.CURRENCY;
default:
throw new AssertionError();
}
int typeOrCp = getTypeOrCp(tag);
if (typeOrCp == TYPE_CURRENCY_OVERFLOW) {
// Don't go to the provider for this special case
- local.appendCodePoint(0xFFFD, Field.CURRENCY);
+ local.appendCodePoint(0xFFFD, NumberFormat.Field.CURRENCY);
} else if (typeOrCp < 0) {
local.append(provider.getSymbol(typeOrCp), getFieldForType(typeOrCp));
} else {
while (hasNext(tag, affixPattern)) {
tag = nextToken(tag, affixPattern);
int typeOrCp = getTypeOrCp(tag);
- if (typeOrCp == AffixPatternUtils.TYPE_CURRENCY_SINGLE
- || typeOrCp == AffixPatternUtils.TYPE_CURRENCY_DOUBLE
- || typeOrCp == AffixPatternUtils.TYPE_CURRENCY_TRIPLE
- || typeOrCp == AffixPatternUtils.TYPE_CURRENCY_QUAD
- || typeOrCp == AffixPatternUtils.TYPE_CURRENCY_QUINT
- || typeOrCp == AffixPatternUtils.TYPE_CURRENCY_OVERFLOW) {
+ if (typeOrCp < 0 && getFieldForType(typeOrCp) == NumberFormat.Field.CURRENCY) {
return true;
}
}
*/
public int apply(NumberStringBuilder output, int leftIndex, int rightIndex);
- /**
- * The number of characters that {@link #apply} would add to the string builder.
- *
- * @return The number of characters (UTF-16 code units) that would be added to a string builder.
- */
- public int length();
-
/**
* 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
// private Modifier mod4 = null;
// private Modifier mod5 = null;
+ public ModifierHolder createCopy() {
+ ModifierHolder copy = new ModifierHolder();
+ copy.mods.addAll(mods);
+ return copy;
+ }
+
public ModifierHolder clear() {
// mod1 = null;
// mod2 = null;
}
return addedLength;
}
-
- public int totalLength() {
- int length = 0;
- // if (mod1 != null) length += mod1.length();
- // if (mod2 != null) length += mod2.length();
- // if (mod3 != null) length += mod3.length();
- // if (mod4 != null) length += mod4.length();
- // if (mod5 != null) length += mod5.length();
- for (Modifier mod : mods) {
- if (mod == null) continue;
- length += mod.length();
- }
- return length;
- }
}
import com.ibm.icu.text.NumberFormat;
import com.ibm.icu.text.NumberFormat.Field;
+/**
+ * A StringBuilder optimized for number formatting. It implements the following key features beyond
+ * a normal JDK StringBuilder:
+ *
+ * <ol>
+ * <li>Efficient prepend as well as append.
+ * <li>Keeps tracks of Fields in an efficient manner.
+ * <li>String operations are fast-pathed to code point operations when possible.
+ * </ol>
+ */
public class NumberStringBuilder implements CharSequence {
+
+ /** A constant, empty NumberStringBuilder. Do NOT call mutative operations on this. */
+ public static final NumberStringBuilder EMPTY = new NumberStringBuilder();
+
private char[] chars;
private Field[] fields;
private int zero;
public NumberStringBuilder(NumberStringBuilder source) {
this(source.chars.length);
+ copyFrom(source);
+ }
+
+ public void copyFrom(NumberStringBuilder source) {
zero = source.zero;
length = source.length;
System.arraycopy(source.chars, zero, chars, zero, length);
return length;
}
+ public int codePointCount() {
+ return Character.codePointCount(this, 0, length());
+ }
+
@Override
public char charAt(int index) {
if (index < 0 || index > length) {
return chars[zero + index];
}
+ public Field fieldAt(int index) {
+ if (index < 0 || index > length) {
+ throw new IndexOutOfBoundsException();
+ }
+ return fields[zero + index];
+ }
+
/**
* Appends the specified codePoint to the end of the string.
*
return true;
}
+ @Override
+ public int hashCode() {
+ throw new UnsupportedOperationException("Don't call #hashCode() or #equals() on a mutable.");
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ throw new UnsupportedOperationException("Don't call #hashCode() or #equals() on a mutable.");
+ }
+
/**
* Populates the given {@link FieldPosition} based on this string builder.
*
public static AfterFormat getInstance(IProperties properties) {
return new PaddingFormat(
- properties.getFormatWidth(),
- properties.getPadString(),
- properties.getPadPosition());
+ properties.getFormatWidth(), properties.getPadString(), properties.getPadPosition());
}
// Properties
private final String paddingString;
private final PadPosition paddingLocation;
- private PaddingFormat(
- int paddingWidth, String paddingString, PadPosition paddingLocation) {
+ private PaddingFormat(int paddingWidth, String paddingString, PadPosition paddingLocation) {
this.paddingWidth = paddingWidth > 0 ? paddingWidth : 10; // TODO: Is this a sensible default?
this.paddingString = paddingString != null ? paddingString : FALLBACK_PADDING_STRING;
- this.paddingLocation =
- paddingLocation != null ? paddingLocation : PadPosition.BEFORE_PREFIX;
+ this.paddingLocation = paddingLocation != null ? paddingLocation : PadPosition.BEFORE_PREFIX;
}
@Override
public int after(ModifierHolder mods, NumberStringBuilder string, int leftIndex, int rightIndex) {
// TODO: Count code points instead of code units?
- int requiredPadding = paddingWidth - (rightIndex - leftIndex) - mods.totalLength();
+ // TODO: Make this more efficient (less copying)
+ NumberStringBuilder copy1 = new NumberStringBuilder(string);
+ ModifierHolder copy2 = mods.createCopy();
+ copy2.applyAll(copy1, leftIndex, rightIndex);
+ int requiredPadding = paddingWidth - copy1.length();
if (requiredPadding <= 0) {
// Skip padding, but still apply modifiers to be consistent
this.strong = strong;
}
- /**
- * Constructs a new instance with an empty prefix, suffix, and field.
- */
+ /** Constructs a new instance with an empty prefix, suffix, and field. */
public ConstantAffixModifier() {
prefix = "";
suffix = "";
return length;
}
- @Override
- public int length() {
- return prefix.length() + suffix.length();
- }
-
@Override
public boolean isStrong() {
return strong;
@Override
public String toString() {
- return String.format(
- "<ConstantAffixModifier(%d) prefix:'%s' suffix:'%s'>", length(), prefix, suffix);
+ return String.format("<ConstantAffixModifier prefix:'%s' suffix:'%s'>", prefix, suffix);
}
@Override
// TODO: Avoid making a new instance by default if prefix and suffix are empty
public static final ConstantMultiFieldModifier EMPTY = new ConstantMultiFieldModifier();
- private final char[] prefixChars;
- private final char[] suffixChars;
- private final Field[] prefixFields;
- private final Field[] suffixFields;
+ protected final char[] prefixChars;
+ protected final char[] suffixChars;
+ protected final Field[] prefixFields;
+ protected final Field[] suffixFields;
private final String prefix;
private final String suffix;
private final boolean strong;
return length;
}
- @Override
- public int length() {
- return prefixChars.length + suffixChars.length;
- }
-
@Override
public boolean isStrong() {
return strong;
@Override
public String toString() {
- return String.format(
- "<ConstantMultiFieldModifier(%d) prefix:'%s' suffix:'%s'>", length(), prefix, suffix);
+ return String.format("<ConstantMultiFieldModifier prefix:'%s' suffix:'%s'>", prefix, suffix);
}
@Override
return formatAsPrefixSuffix(compiledPattern, output, leftIndex, rightIndex, field);
}
- @Override
- public int length() {
- // TODO: Make a separate method for computing the length only?
- return formatAsPrefixSuffix(compiledPattern, null, -1, -1, field);
- }
-
@Override
public boolean isStrong() {
return strong;