com.ibm.icu.impl.number.CurrencySpacingEnabledModifier Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of icu4j Show documentation
Show all versions of icu4j Show documentation
International Component for Unicode for Java (ICU4J) is a mature, widely used Java library
providing Unicode and Globalization support
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
package com.ibm.icu.impl.number;
import com.ibm.icu.impl.FormattedStringBuilder;
import com.ibm.icu.text.DecimalFormatSymbols;
import com.ibm.icu.text.NumberFormat;
import com.ibm.icu.text.UnicodeSet;
/** Identical to {@link ConstantMultiFieldModifier}, but supports currency spacing. */
public class CurrencySpacingEnabledModifier extends ConstantMultiFieldModifier {
// These are the default currency spacing UnicodeSets in CLDR.
// Pre-compute them for performance.
// The unit test testCurrencySpacingPatternStability() will start failing if these change in CLDR.
private static final UnicodeSet UNISET_DIGIT = new UnicodeSet("[:digit:]").freeze();
private static final UnicodeSet UNISET_NOTSZ = new UnicodeSet("[[:^S:]&[:^Z:]]").freeze();
// Constants for better readability. Types are for compiler checking.
static final byte PREFIX = 0;
static final byte SUFFIX = 1;
static final short IN_CURRENCY = 0;
static final short IN_NUMBER = 1;
private final UnicodeSet afterPrefixUnicodeSet;
private final String afterPrefixInsert;
private final UnicodeSet beforeSuffixUnicodeSet;
private final String beforeSuffixInsert;
/** Safe code path */
public CurrencySpacingEnabledModifier(
FormattedStringBuilder prefix,
FormattedStringBuilder suffix,
boolean overwrite,
boolean strong,
DecimalFormatSymbols symbols) {
super(prefix, suffix, overwrite, strong);
// Check for currency spacing. Do not build the UnicodeSets unless there is
// a currency code point at a boundary.
if (prefix.length() > 0 && prefix.fieldAt(prefix.length() - 1) == NumberFormat.Field.CURRENCY) {
int prefixCp = prefix.getLastCodePoint();
UnicodeSet prefixUnicodeSet = getUnicodeSet(symbols, IN_CURRENCY, PREFIX);
if (prefixUnicodeSet.contains(prefixCp)) {
afterPrefixUnicodeSet = getUnicodeSet(symbols, IN_NUMBER, PREFIX);
afterPrefixUnicodeSet.freeze(); // no-op if set is already frozen
afterPrefixInsert = getInsertString(symbols, PREFIX);
} else {
afterPrefixUnicodeSet = null;
afterPrefixInsert = null;
}
} else {
afterPrefixUnicodeSet = null;
afterPrefixInsert = null;
}
if (suffix.length() > 0 && suffix.fieldAt(0) == NumberFormat.Field.CURRENCY) {
int suffixCp = suffix.getFirstCodePoint();
UnicodeSet suffixUnicodeSet = getUnicodeSet(symbols, IN_CURRENCY, SUFFIX);
if (suffixUnicodeSet.contains(suffixCp)) {
beforeSuffixUnicodeSet = getUnicodeSet(symbols, IN_NUMBER, SUFFIX);
beforeSuffixUnicodeSet.freeze(); // no-op if set is already frozen
beforeSuffixInsert = getInsertString(symbols, SUFFIX);
} else {
beforeSuffixUnicodeSet = null;
beforeSuffixInsert = null;
}
} else {
beforeSuffixUnicodeSet = null;
beforeSuffixInsert = null;
}
}
/** Safe code path */
@Override
public int apply(FormattedStringBuilder output, int leftIndex, int rightIndex) {
// Currency spacing logic
int length = 0;
if (rightIndex - leftIndex > 0
&& afterPrefixUnicodeSet != null
&& afterPrefixUnicodeSet.contains(output.codePointAt(leftIndex))) {
// TODO: Should we use the CURRENCY field here?
length += output.insert(leftIndex, afterPrefixInsert, null);
}
if (rightIndex - leftIndex > 0
&& beforeSuffixUnicodeSet != null
&& beforeSuffixUnicodeSet.contains(output.codePointBefore(rightIndex))) {
// TODO: Should we use the CURRENCY field here?
length += output.insert(rightIndex + length, beforeSuffixInsert, null);
}
// Call super for the remaining logic
length += super.apply(output, leftIndex, rightIndex + length);
return length;
}
/** Unsafe code path */
public static int applyCurrencySpacing(
FormattedStringBuilder output,
int prefixStart,
int prefixLen,
int suffixStart,
int suffixLen,
DecimalFormatSymbols symbols) {
int length = 0;
boolean hasPrefix = (prefixLen > 0);
boolean hasSuffix = (suffixLen > 0);
boolean hasNumber = (suffixStart - prefixStart - prefixLen > 0); // could be empty string
if (hasPrefix && hasNumber) {
length += applyCurrencySpacingAffix(output, prefixStart + prefixLen, PREFIX, symbols);
}
if (hasSuffix && hasNumber) {
length += applyCurrencySpacingAffix(output, suffixStart + length, SUFFIX, symbols);
}
return length;
}
/** Unsafe code path */
private static int applyCurrencySpacingAffix(
FormattedStringBuilder output,
int index,
byte affix,
DecimalFormatSymbols symbols) {
// NOTE: For prefix, output.fieldAt(index-1) gets the last field type in the prefix.
// This works even if the last code point in the prefix is 2 code units because the
// field value gets populated to both indices in the field array.
Object affixField = (affix == PREFIX) ? output.fieldAt(index - 1)
: output.fieldAt(index);
if (affixField != NumberFormat.Field.CURRENCY) {
return 0;
}
int affixCp = (affix == PREFIX) ? output.codePointBefore(index) : output.codePointAt(index);
UnicodeSet affixUniset = getUnicodeSet(symbols, IN_CURRENCY, affix);
if (!affixUniset.contains(affixCp)) {
return 0;
}
int numberCp = (affix == PREFIX) ? output.codePointAt(index) : output.codePointBefore(index);
UnicodeSet numberUniset = getUnicodeSet(symbols, IN_NUMBER, affix);
if (!numberUniset.contains(numberCp)) {
return 0;
}
String spacingString = getInsertString(symbols, affix);
// NOTE: This next line *inserts* the spacing string, triggering an arraycopy.
// It would be more efficient if this could be done before affixes were attached,
// so that it could be prepended/appended instead of inserted.
// However, the build code path is more efficient, and this is the most natural
// place to put currency spacing in the non-build code path.
// TODO: Should we use the CURRENCY field here?
return output.insert(index, spacingString, null);
}
private static UnicodeSet getUnicodeSet(DecimalFormatSymbols symbols, short position, byte affix) {
String pattern = symbols
.getPatternForCurrencySpacing(
position == IN_CURRENCY ? DecimalFormatSymbols.CURRENCY_SPC_CURRENCY_MATCH
: DecimalFormatSymbols.CURRENCY_SPC_SURROUNDING_MATCH,
affix == SUFFIX);
if (pattern.equals("[:digit:]")) {
return UNISET_DIGIT;
} else if (pattern.equals("[[:^S:]&[:^Z:]]")) {
return UNISET_NOTSZ;
} else {
return new UnicodeSet(pattern);
}
}
private static String getInsertString(DecimalFormatSymbols symbols, byte affix) {
return symbols.getPatternForCurrencySpacing(DecimalFormatSymbols.CURRENCY_SPC_INSERT,
affix == SUFFIX);
}
}