LEFT | RIGHT |
(no file at all) | |
1 // © 2017 and later: Unicode, Inc. and others. | 1 // © 2017 and later: Unicode, Inc. and others. |
2 // License & terms of use: http://www.unicode.org/copyright.html#License | 2 // License & terms of use: http://www.unicode.org/copyright.html#License |
3 package com.ibm.icu.impl.number; | 3 package com.ibm.icu.impl.number; |
4 | 4 |
5 import com.ibm.icu.text.DecimalFormatSymbols; | 5 import com.ibm.icu.text.DecimalFormatSymbols; |
6 import com.ibm.icu.text.NumberFormat; | 6 import com.ibm.icu.text.NumberFormat; |
7 import com.ibm.icu.text.UnicodeSet; | 7 import com.ibm.icu.text.UnicodeSet; |
8 | 8 |
9 /** Identical to {@link ConstantMultiFieldModifier}, but supports currency spaci
ng. */ | 9 /** Identical to {@link ConstantMultiFieldModifier}, but supports currency spaci
ng. */ |
10 public class CurrencySpacingEnabledModifier extends ConstantMultiFieldModifier { | 10 public class CurrencySpacingEnabledModifier extends ConstantMultiFieldModifier { |
11 | 11 |
12 // These are the default currency spacing UnicodeSets in CLDR. | 12 // These are the default currency spacing UnicodeSets in CLDR. |
13 // Pre-compute them for performance. | 13 // Pre-compute them for performance. |
14 // The unit test testCurrencySpacingPatternStability() will start failing if
these change in CLDR. | 14 // The unit test testCurrencySpacingPatternStability() will start failing if
these change in CLDR. |
15 private static final UnicodeSet UNISET_DIGIT = new UnicodeSet("[:digit:]").f
reeze(); | 15 private static final UnicodeSet UNISET_DIGIT = new UnicodeSet("[:digit:]").f
reeze(); |
16 private static final UnicodeSet UNISET_NOTS = new UnicodeSet("[:^S:]").freez
e(); | 16 private static final UnicodeSet UNISET_NOTS = new UnicodeSet("[:^S:]").freez
e(); |
17 | 17 |
18 // Constants for better readability. Types are for compiler checking. | 18 // Constants for better readability. Types are for compiler checking. |
19 static final byte PREFIX = 0; | 19 static final byte PREFIX = 0; |
20 static final byte SUFFIX = 1; | 20 static final byte SUFFIX = 1; |
21 static final short IN_CURRENCY = 0; | 21 static final short IN_CURRENCY = 0; |
22 static final short IN_NUMBER = 1; | 22 static final short IN_NUMBER = 1; |
23 | 23 |
24 private final UnicodeSet afterPrefixUnicodeSet; | 24 private final UnicodeSet afterPrefixUnicodeSet; |
25 private final String afterPrefixInsert; | 25 private final String afterPrefixInsert; |
26 private final UnicodeSet beforeSuffixUnicodeSet; | 26 private final UnicodeSet beforeSuffixUnicodeSet; |
27 private final String beforeSuffixInsert; | 27 private final String beforeSuffixInsert; |
28 | 28 |
29 /** Safe code path */ | 29 /** Safe code path */ |
30 public CurrencySpacingEnabledModifier(NumberStringBuilder prefix, NumberStri
ngBuilder suffix, boolean strong, | 30 public CurrencySpacingEnabledModifier( |
| 31 NumberStringBuilder prefix, |
| 32 NumberStringBuilder suffix, |
| 33 boolean strong, |
31 DecimalFormatSymbols symbols) { | 34 DecimalFormatSymbols symbols) { |
32 super(prefix, suffix, strong); | 35 super(prefix, suffix, strong); |
33 | 36 |
34 // Check for currency spacing. Do not build the UnicodeSets unless there
is | 37 // Check for currency spacing. Do not build the UnicodeSets unless there
is |
35 // a currency code point at a boundary. | 38 // a currency code point at a boundary. |
36 if (prefix.length() > 0 && prefix.fieldAt(prefix.length() - 1) == Number
Format.Field.CURRENCY) { | 39 if (prefix.length() > 0 && prefix.fieldAt(prefix.length() - 1) == Number
Format.Field.CURRENCY) { |
37 int prefixCp = prefix.getLastCodePoint(); | 40 int prefixCp = prefix.getLastCodePoint(); |
38 UnicodeSet prefixUnicodeSet = getUnicodeSet(symbols, IN_CURRENCY, PR
EFIX); | 41 UnicodeSet prefixUnicodeSet = getUnicodeSet(symbols, IN_CURRENCY, PR
EFIX); |
39 if (prefixUnicodeSet.contains(prefixCp)) { | 42 if (prefixUnicodeSet.contains(prefixCp)) { |
40 afterPrefixUnicodeSet = getUnicodeSet(symbols, IN_NUMBER, PREFIX
); | 43 afterPrefixUnicodeSet = getUnicodeSet(symbols, IN_NUMBER, PREFIX
); |
(...skipping 22 matching lines...) Expand all Loading... |
63 beforeSuffixUnicodeSet = null; | 66 beforeSuffixUnicodeSet = null; |
64 beforeSuffixInsert = null; | 67 beforeSuffixInsert = null; |
65 } | 68 } |
66 } | 69 } |
67 | 70 |
68 /** Safe code path */ | 71 /** Safe code path */ |
69 @Override | 72 @Override |
70 public int apply(NumberStringBuilder output, int leftIndex, int rightIndex)
{ | 73 public int apply(NumberStringBuilder output, int leftIndex, int rightIndex)
{ |
71 // Currency spacing logic | 74 // Currency spacing logic |
72 int length = 0; | 75 int length = 0; |
73 if (rightIndex - leftIndex > 0 && afterPrefixUnicodeSet != null | 76 if (rightIndex - leftIndex > 0 |
| 77 && afterPrefixUnicodeSet != null |
74 && afterPrefixUnicodeSet.contains(output.codePointAt(leftIndex))
) { | 78 && afterPrefixUnicodeSet.contains(output.codePointAt(leftIndex))
) { |
75 // TODO: Should we use the CURRENCY field here? | 79 // TODO: Should we use the CURRENCY field here? |
76 length += output.insert(leftIndex, afterPrefixInsert, null); | 80 length += output.insert(leftIndex, afterPrefixInsert, null); |
77 } | 81 } |
78 if (rightIndex - leftIndex > 0 && beforeSuffixUnicodeSet != null | 82 if (rightIndex - leftIndex > 0 |
| 83 && beforeSuffixUnicodeSet != null |
79 && beforeSuffixUnicodeSet.contains(output.codePointBefore(rightI
ndex))) { | 84 && beforeSuffixUnicodeSet.contains(output.codePointBefore(rightI
ndex))) { |
80 // TODO: Should we use the CURRENCY field here? | 85 // TODO: Should we use the CURRENCY field here? |
81 length += output.insert(rightIndex + length, beforeSuffixInsert, nul
l); | 86 length += output.insert(rightIndex + length, beforeSuffixInsert, nul
l); |
82 } | 87 } |
83 | 88 |
84 // Call super for the remaining logic | 89 // Call super for the remaining logic |
85 length += super.apply(output, leftIndex, rightIndex + length); | 90 length += super.apply(output, leftIndex, rightIndex + length); |
86 return length; | 91 return length; |
87 } | 92 } |
88 | 93 |
89 /** Unsafe code path */ | 94 /** Unsafe code path */ |
90 public static int applyCurrencySpacing(NumberStringBuilder output, int prefi
xStart, int prefixLen, int suffixStart, | 95 public static int applyCurrencySpacing( |
91 int suffixLen, DecimalFormatSymbols symbols) { | 96 NumberStringBuilder output, |
| 97 int prefixStart, |
| 98 int prefixLen, |
| 99 int suffixStart, |
| 100 int suffixLen, |
| 101 DecimalFormatSymbols symbols) { |
92 int length = 0; | 102 int length = 0; |
93 boolean hasPrefix = (prefixLen > 0); | 103 boolean hasPrefix = (prefixLen > 0); |
94 boolean hasSuffix = (suffixLen > 0); | 104 boolean hasSuffix = (suffixLen > 0); |
95 boolean hasNumber = (suffixStart - prefixStart - prefixLen > 0); // coul
d be empty string | 105 boolean hasNumber = (suffixStart - prefixStart - prefixLen > 0); // coul
d be empty string |
96 if (hasPrefix && hasNumber) { | 106 if (hasPrefix && hasNumber) { |
97 length += applyCurrencySpacingAffix(output, prefixStart + prefixLen,
PREFIX, symbols); | 107 length += applyCurrencySpacingAffix(output, prefixStart + prefixLen,
PREFIX, symbols); |
98 } | 108 } |
99 if (hasSuffix && hasNumber) { | 109 if (hasSuffix && hasNumber) { |
100 length += applyCurrencySpacingAffix(output, suffixStart + length, SU
FFIX, symbols); | 110 length += applyCurrencySpacingAffix(output, suffixStart + length, SU
FFIX, symbols); |
101 } | 111 } |
102 return length; | 112 return length; |
103 } | 113 } |
104 | 114 |
105 /** Unsafe code path */ | 115 /** Unsafe code path */ |
106 private static int applyCurrencySpacingAffix(NumberStringBuilder output, int
index, byte affix, | 116 private static int applyCurrencySpacingAffix( |
| 117 NumberStringBuilder output, |
| 118 int index, |
| 119 byte affix, |
107 DecimalFormatSymbols symbols) { | 120 DecimalFormatSymbols symbols) { |
108 // NOTE: For prefix, output.fieldAt(index-1) gets the last field type in
the prefix. | 121 // NOTE: For prefix, output.fieldAt(index-1) gets the last field type in
the prefix. |
109 // This works even if the last code point in the prefix is 2 code units
because the | 122 // This works even if the last code point in the prefix is 2 code units
because the |
110 // field value gets populated to both indices in the field array. | 123 // field value gets populated to both indices in the field array. |
111 NumberFormat.Field affixField = (affix == PREFIX) ? output.fieldAt(index
- 1) : output.fieldAt(index); | 124 NumberFormat.Field affixField = (affix == PREFIX) ? output.fieldAt(index
- 1) |
| 125 : output.fieldAt(index); |
112 if (affixField != NumberFormat.Field.CURRENCY) { | 126 if (affixField != NumberFormat.Field.CURRENCY) { |
113 return 0; | 127 return 0; |
114 } | 128 } |
115 int affixCp = (affix == PREFIX) ? output.codePointBefore(index) : output
.codePointAt(index); | 129 int affixCp = (affix == PREFIX) ? output.codePointBefore(index) : output
.codePointAt(index); |
116 UnicodeSet affixUniset = getUnicodeSet(symbols, IN_CURRENCY, affix); | 130 UnicodeSet affixUniset = getUnicodeSet(symbols, IN_CURRENCY, affix); |
117 if (!affixUniset.contains(affixCp)) { | 131 if (!affixUniset.contains(affixCp)) { |
118 return 0; | 132 return 0; |
119 } | 133 } |
120 int numberCp = (affix == PREFIX) ? output.codePointAt(index) : output.co
dePointBefore(index); | 134 int numberCp = (affix == PREFIX) ? output.codePointAt(index) : output.co
dePointBefore(index); |
121 UnicodeSet numberUniset = getUnicodeSet(symbols, IN_NUMBER, affix); | 135 UnicodeSet numberUniset = getUnicodeSet(symbols, IN_NUMBER, affix); |
122 if (!numberUniset.contains(numberCp)) { | 136 if (!numberUniset.contains(numberCp)) { |
123 return 0; | 137 return 0; |
124 } | 138 } |
125 String spacingString = getInsertString(symbols, affix); | 139 String spacingString = getInsertString(symbols, affix); |
126 | 140 |
127 // NOTE: This next line *inserts* the spacing string, triggering an arra
ycopy. | 141 // NOTE: This next line *inserts* the spacing string, triggering an arra
ycopy. |
128 // It would be more efficient if this could be done before affixes were
attached, | 142 // It would be more efficient if this could be done before affixes were
attached, |
129 // so that it could be prepended/appended instead of inserted. | 143 // so that it could be prepended/appended instead of inserted. |
130 // However, the build code path is more efficient, and this is the most
natural | 144 // However, the build code path is more efficient, and this is the most
natural |
131 // place to put currency spacing in the non-build code path. | 145 // place to put currency spacing in the non-build code path. |
132 // TODO: Should we use the CURRENCY field here? | 146 // TODO: Should we use the CURRENCY field here? |
133 return output.insert(index, spacingString, null); | 147 return output.insert(index, spacingString, null); |
134 } | 148 } |
135 | 149 |
136 private static UnicodeSet getUnicodeSet(DecimalFormatSymbols symbols, short
position, byte affix) { | 150 private static UnicodeSet getUnicodeSet(DecimalFormatSymbols symbols, short
position, byte affix) { |
137 String pattern = symbols | 151 String pattern = symbols |
138 .getPatternForCurrencySpacing(position == IN_CURRENCY ? DecimalF
ormatSymbols.CURRENCY_SPC_CURRENCY_MATCH | 152 .getPatternForCurrencySpacing( |
139 : DecimalFormatSymbols.CURRENCY_SPC_SURROUNDING_MATCH, a
ffix == SUFFIX); | 153 position == IN_CURRENCY ? DecimalFormatSymbols.CURRENCY_
SPC_CURRENCY_MATCH |
| 154 : DecimalFormatSymbols.CURRENCY_SPC_SURROUNDING_
MATCH, |
| 155 affix == SUFFIX); |
140 if (pattern.equals("[:digit:]")) { | 156 if (pattern.equals("[:digit:]")) { |
141 return UNISET_DIGIT; | 157 return UNISET_DIGIT; |
142 } else if (pattern.equals("[:^S:]")) { | 158 } else if (pattern.equals("[:^S:]")) { |
143 return UNISET_NOTS; | 159 return UNISET_NOTS; |
144 } else { | 160 } else { |
145 return new UnicodeSet(pattern); | 161 return new UnicodeSet(pattern); |
146 } | 162 } |
147 } | 163 } |
148 | 164 |
149 private static String getInsertString(DecimalFormatSymbols symbols, byte aff
ix) { | 165 private static String getInsertString(DecimalFormatSymbols symbols, byte aff
ix) { |
150 return symbols.getPatternForCurrencySpacing(DecimalFormatSymbols.CURRENC
Y_SPC_INSERT, affix == SUFFIX); | 166 return symbols.getPatternForCurrencySpacing(DecimalFormatSymbols.CURRENC
Y_SPC_INSERT, |
| 167 affix == SUFFIX); |
151 } | 168 } |
152 } | 169 } |
LEFT | RIGHT |