1 /* GENERATED SOURCE. DO NOT MODIFY. */ 2 // © 2017 and later: Unicode, Inc. and others. 3 // License & terms of use: http://www.unicode.org/copyright.html#License 4 package ohos.global.icu.impl.number; 5 6 import ohos.global.icu.impl.FormattedStringBuilder; 7 import ohos.global.icu.text.DecimalFormatSymbols; 8 import ohos.global.icu.text.NumberFormat; 9 import ohos.global.icu.text.UnicodeSet; 10 11 /** Identical to {@link ConstantMultiFieldModifier}, but supports currency spacing. 12 * @hide exposed on OHOS*/ 13 public class CurrencySpacingEnabledModifier extends ConstantMultiFieldModifier { 14 15 // These are the default currency spacing UnicodeSets in CLDR. 16 // Pre-compute them for performance. 17 // The unit test testCurrencySpacingPatternStability() will start failing if these change in CLDR. 18 private static final UnicodeSet UNISET_DIGIT = new UnicodeSet("[:digit:]").freeze(); 19 private static final UnicodeSet UNISET_NOTS = new UnicodeSet("[:^S:]").freeze(); 20 21 // Constants for better readability. Types are for compiler checking. 22 static final byte PREFIX = 0; 23 static final byte SUFFIX = 1; 24 static final short IN_CURRENCY = 0; 25 static final short IN_NUMBER = 1; 26 27 private final UnicodeSet afterPrefixUnicodeSet; 28 private final String afterPrefixInsert; 29 private final UnicodeSet beforeSuffixUnicodeSet; 30 private final String beforeSuffixInsert; 31 32 /** Safe code path */ CurrencySpacingEnabledModifier( FormattedStringBuilder prefix, FormattedStringBuilder suffix, boolean overwrite, boolean strong, DecimalFormatSymbols symbols)33 public CurrencySpacingEnabledModifier( 34 FormattedStringBuilder prefix, 35 FormattedStringBuilder suffix, 36 boolean overwrite, 37 boolean strong, 38 DecimalFormatSymbols symbols) { 39 super(prefix, suffix, overwrite, strong); 40 41 // Check for currency spacing. Do not build the UnicodeSets unless there is 42 // a currency code point at a boundary. 43 if (prefix.length() > 0 && prefix.fieldAt(prefix.length() - 1) == NumberFormat.Field.CURRENCY) { 44 int prefixCp = prefix.getLastCodePoint(); 45 UnicodeSet prefixUnicodeSet = getUnicodeSet(symbols, IN_CURRENCY, PREFIX); 46 if (prefixUnicodeSet.contains(prefixCp)) { 47 afterPrefixUnicodeSet = getUnicodeSet(symbols, IN_NUMBER, PREFIX); 48 afterPrefixUnicodeSet.freeze(); // no-op if set is already frozen 49 afterPrefixInsert = getInsertString(symbols, PREFIX); 50 } else { 51 afterPrefixUnicodeSet = null; 52 afterPrefixInsert = null; 53 } 54 } else { 55 afterPrefixUnicodeSet = null; 56 afterPrefixInsert = null; 57 } 58 if (suffix.length() > 0 && suffix.fieldAt(0) == NumberFormat.Field.CURRENCY) { 59 int suffixCp = suffix.getFirstCodePoint(); 60 UnicodeSet suffixUnicodeSet = getUnicodeSet(symbols, IN_CURRENCY, SUFFIX); 61 if (suffixUnicodeSet.contains(suffixCp)) { 62 beforeSuffixUnicodeSet = getUnicodeSet(symbols, IN_NUMBER, SUFFIX); 63 beforeSuffixUnicodeSet.freeze(); // no-op if set is already frozen 64 beforeSuffixInsert = getInsertString(symbols, SUFFIX); 65 } else { 66 beforeSuffixUnicodeSet = null; 67 beforeSuffixInsert = null; 68 } 69 } else { 70 beforeSuffixUnicodeSet = null; 71 beforeSuffixInsert = null; 72 } 73 } 74 75 /** Safe code path */ 76 @Override apply(FormattedStringBuilder output, int leftIndex, int rightIndex)77 public int apply(FormattedStringBuilder output, int leftIndex, int rightIndex) { 78 // Currency spacing logic 79 int length = 0; 80 if (rightIndex - leftIndex > 0 81 && afterPrefixUnicodeSet != null 82 && afterPrefixUnicodeSet.contains(output.codePointAt(leftIndex))) { 83 // TODO: Should we use the CURRENCY field here? 84 length += output.insert(leftIndex, afterPrefixInsert, null); 85 } 86 if (rightIndex - leftIndex > 0 87 && beforeSuffixUnicodeSet != null 88 && beforeSuffixUnicodeSet.contains(output.codePointBefore(rightIndex))) { 89 // TODO: Should we use the CURRENCY field here? 90 length += output.insert(rightIndex + length, beforeSuffixInsert, null); 91 } 92 93 // Call super for the remaining logic 94 length += super.apply(output, leftIndex, rightIndex + length); 95 return length; 96 } 97 98 /** Unsafe code path */ applyCurrencySpacing( FormattedStringBuilder output, int prefixStart, int prefixLen, int suffixStart, int suffixLen, DecimalFormatSymbols symbols)99 public static int applyCurrencySpacing( 100 FormattedStringBuilder output, 101 int prefixStart, 102 int prefixLen, 103 int suffixStart, 104 int suffixLen, 105 DecimalFormatSymbols symbols) { 106 int length = 0; 107 boolean hasPrefix = (prefixLen > 0); 108 boolean hasSuffix = (suffixLen > 0); 109 boolean hasNumber = (suffixStart - prefixStart - prefixLen > 0); // could be empty string 110 if (hasPrefix && hasNumber) { 111 length += applyCurrencySpacingAffix(output, prefixStart + prefixLen, PREFIX, symbols); 112 } 113 if (hasSuffix && hasNumber) { 114 length += applyCurrencySpacingAffix(output, suffixStart + length, SUFFIX, symbols); 115 } 116 return length; 117 } 118 119 /** Unsafe code path */ applyCurrencySpacingAffix( FormattedStringBuilder output, int index, byte affix, DecimalFormatSymbols symbols)120 private static int applyCurrencySpacingAffix( 121 FormattedStringBuilder output, 122 int index, 123 byte affix, 124 DecimalFormatSymbols symbols) { 125 // NOTE: For prefix, output.fieldAt(index-1) gets the last field type in the prefix. 126 // This works even if the last code point in the prefix is 2 code units because the 127 // field value gets populated to both indices in the field array. 128 Object affixField = (affix == PREFIX) ? output.fieldAt(index - 1) 129 : output.fieldAt(index); 130 if (affixField != NumberFormat.Field.CURRENCY) { 131 return 0; 132 } 133 int affixCp = (affix == PREFIX) ? output.codePointBefore(index) : output.codePointAt(index); 134 UnicodeSet affixUniset = getUnicodeSet(symbols, IN_CURRENCY, affix); 135 if (!affixUniset.contains(affixCp)) { 136 return 0; 137 } 138 int numberCp = (affix == PREFIX) ? output.codePointAt(index) : output.codePointBefore(index); 139 UnicodeSet numberUniset = getUnicodeSet(symbols, IN_NUMBER, affix); 140 if (!numberUniset.contains(numberCp)) { 141 return 0; 142 } 143 String spacingString = getInsertString(symbols, affix); 144 145 // NOTE: This next line *inserts* the spacing string, triggering an arraycopy. 146 // It would be more efficient if this could be done before affixes were attached, 147 // so that it could be prepended/appended instead of inserted. 148 // However, the build code path is more efficient, and this is the most natural 149 // place to put currency spacing in the non-build code path. 150 // TODO: Should we use the CURRENCY field here? 151 return output.insert(index, spacingString, null); 152 } 153 getUnicodeSet(DecimalFormatSymbols symbols, short position, byte affix)154 private static UnicodeSet getUnicodeSet(DecimalFormatSymbols symbols, short position, byte affix) { 155 String pattern = symbols 156 .getPatternForCurrencySpacing( 157 position == IN_CURRENCY ? DecimalFormatSymbols.CURRENCY_SPC_CURRENCY_MATCH 158 : DecimalFormatSymbols.CURRENCY_SPC_SURROUNDING_MATCH, 159 affix == SUFFIX); 160 if (pattern.equals("[:digit:]")) { 161 return UNISET_DIGIT; 162 } else if (pattern.equals("[:^S:]")) { 163 return UNISET_NOTS; 164 } else { 165 return new UnicodeSet(pattern); 166 } 167 } 168 getInsertString(DecimalFormatSymbols symbols, byte affix)169 private static String getInsertString(DecimalFormatSymbols symbols, byte affix) { 170 return symbols.getPatternForCurrencySpacing(DecimalFormatSymbols.CURRENCY_SPC_INSERT, 171 affix == SUFFIX); 172 } 173 } 174