/* GENERATED SOURCE. DO NOT MODIFY. */ // © 2017 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html package android.icu.impl.number; import android.icu.impl.FormattedStringBuilder; import android.icu.text.DecimalFormatSymbols; import android.icu.text.NumberFormat; import android.icu.text.UnicodeSet; /** Identical to {@link ConstantMultiFieldModifier}, but supports currency spacing. * @hide Only a subset of ICU is exposed in Android*/ 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); } }