1 /* GENERATED SOURCE. DO NOT MODIFY. */ 2 // © 2019 and later: Unicode, Inc. and others. 3 // License & terms of use: http://www.unicode.org/copyright.html#License 4 package ohos.global.icu.impl; 5 6 import java.text.AttributedCharacterIterator; 7 import java.text.AttributedString; 8 import java.text.FieldPosition; 9 import java.text.Format.Field; 10 11 import ohos.global.icu.text.ConstrainedFieldPosition; 12 import ohos.global.icu.text.ListFormatter; 13 import ohos.global.icu.text.NumberFormat; 14 import ohos.global.icu.text.UFormat; 15 import ohos.global.icu.text.UnicodeSet; 16 17 /** 18 * Implementation of FormattedValue based on FormattedStringBuilder. 19 * 20 * The implementation currently revolves around numbers and number fields. 21 * However, it can be generalized in the future when there is a need. 22 * 23 * In C++, this implements FormattedValue. In Java, it is a stateless 24 * collection of static functions to avoid having to use nested objects. 25 * 26 * @author sffc (Shane Carr) 27 * @hide exposed on OHOS 28 */ 29 public class FormattedValueStringBuilderImpl { 30 31 /** 32 * Placeholder field used for calculating spans. 33 * Does not currently support nested fields beyond one level. 34 * @hide exposed on OHOS 35 */ 36 public static class SpanFieldPlaceholder { 37 public UFormat.SpanField spanField; 38 public Field normalField; 39 public Object value; 40 } 41 42 /** 43 * Finds the index at which a span field begins. 44 * 45 * @param value The value of the span field to search for. 46 * @return The index, or -1 if not found. 47 */ findSpan(FormattedStringBuilder self, Object value)48 public static int findSpan(FormattedStringBuilder self, Object value) { 49 for (int i = self.zero; i < self.zero + self.length; i++) { 50 if (!(self.fields[i] instanceof SpanFieldPlaceholder)) { 51 continue; 52 } 53 if (((SpanFieldPlaceholder) self.fields[i]).value.equals(value)) { 54 return i - self.zero; 55 } 56 } 57 return -1; 58 } 59 nextFieldPosition(FormattedStringBuilder self, FieldPosition fp)60 public static boolean nextFieldPosition(FormattedStringBuilder self, FieldPosition fp) { 61 java.text.Format.Field rawField = fp.getFieldAttribute(); 62 63 if (rawField == null) { 64 // Backwards compatibility: read from fp.getField() 65 if (fp.getField() == NumberFormat.INTEGER_FIELD) { 66 rawField = NumberFormat.Field.INTEGER; 67 } else if (fp.getField() == NumberFormat.FRACTION_FIELD) { 68 rawField = NumberFormat.Field.FRACTION; 69 } else { 70 // No field is set 71 return false; 72 } 73 } 74 75 if (!(rawField instanceof NumberFormat.Field)) { 76 throw new IllegalArgumentException( 77 "You must pass an instance of ohos.global.icu.text.NumberFormat.Field as your FieldPosition attribute. You passed: " 78 + rawField.getClass().toString()); 79 } 80 81 ConstrainedFieldPosition cfpos = new ConstrainedFieldPosition(); 82 cfpos.constrainField(rawField); 83 cfpos.setState(rawField, null, fp.getBeginIndex(), fp.getEndIndex()); 84 if (nextPosition(self, cfpos, null)) { 85 fp.setBeginIndex(cfpos.getStart()); 86 fp.setEndIndex(cfpos.getLimit()); 87 return true; 88 } 89 90 // Special case: fraction should start after integer if fraction is not present 91 if (rawField == NumberFormat.Field.FRACTION && fp.getEndIndex() == 0) { 92 boolean inside = false; 93 int i = self.zero; 94 for (; i < self.zero + self.length; i++) { 95 if (isIntOrGroup(self.fields[i]) || self.fields[i] == NumberFormat.Field.DECIMAL_SEPARATOR) { 96 inside = true; 97 } else if (inside) { 98 break; 99 } 100 } 101 fp.setBeginIndex(i - self.zero); 102 fp.setEndIndex(i - self.zero); 103 } 104 105 return false; 106 } 107 toCharacterIterator(FormattedStringBuilder self, Field numericField)108 public static AttributedCharacterIterator toCharacterIterator(FormattedStringBuilder self, Field numericField) { 109 ConstrainedFieldPosition cfpos = new ConstrainedFieldPosition(); 110 AttributedString as = new AttributedString(self.toString()); 111 while (nextPosition(self, cfpos, numericField)) { 112 // Backwards compatibility: field value = field 113 Object value = cfpos.getFieldValue(); 114 if (value == null) { 115 value = cfpos.getField(); 116 } 117 as.addAttribute(cfpos.getField(), value, cfpos.getStart(), cfpos.getLimit()); 118 } 119 return as.getIterator(); 120 } 121 122 static class NullField extends Field { 123 private static final long serialVersionUID = 1L; 124 static final NullField END = new NullField("end"); NullField(String name)125 private NullField(String name) { 126 super(name); 127 } 128 } 129 130 /** 131 * Implementation of nextPosition consistent with the contract of FormattedValue. 132 * 133 * @param cfpos 134 * The argument passed to the public API. 135 * @param numericField 136 * Optional. If non-null, apply this field to the entire numeric portion of the string. 137 * @return See FormattedValue#nextPosition. 138 */ nextPosition(FormattedStringBuilder self, ConstrainedFieldPosition cfpos, Field numericField)139 public static boolean nextPosition(FormattedStringBuilder self, ConstrainedFieldPosition cfpos, Field numericField) { 140 int fieldStart = -1; 141 Object currField = null; 142 for (int i = self.zero + cfpos.getLimit(); i <= self.zero + self.length; i++) { 143 Object _field = (i < self.zero + self.length) ? self.fields[i] : NullField.END; 144 // Case 1: currently scanning a field. 145 if (currField != null) { 146 if (currField != _field) { 147 int end = i - self.zero; 148 // Handle span fields; don't trim them 149 if (currField instanceof SpanFieldPlaceholder) { 150 assert handleSpan(currField, cfpos, fieldStart, end); 151 return true; 152 } 153 // Grouping separators can be whitespace; don't throw them out! 154 if (isTrimmable(currField)) { 155 end = trimBack(self, end); 156 } 157 if (end <= fieldStart) { 158 // Entire field position is ignorable; skip. 159 fieldStart = -1; 160 currField = null; 161 i--; // look at this index again 162 continue; 163 } 164 int start = fieldStart; 165 if (isTrimmable(currField)) { 166 start = trimFront(self, start); 167 } 168 cfpos.setState((Field) currField, null, start, end); 169 return true; 170 } 171 continue; 172 } 173 // Special case: coalesce the INTEGER if we are pointing at the end of the INTEGER. 174 if (cfpos.matchesField(NumberFormat.Field.INTEGER, null) 175 && i > self.zero 176 // don't return the same field twice in a row: 177 && i - self.zero > cfpos.getLimit() 178 && isIntOrGroup(self.fields[i - 1]) 179 && !isIntOrGroup(_field)) { 180 int j = i - 1; 181 for (; j >= self.zero && isIntOrGroup(self.fields[j]); j--) {} 182 cfpos.setState(NumberFormat.Field.INTEGER, null, j - self.zero + 1, i - self.zero); 183 return true; 184 } 185 // Special case: coalesce NUMERIC if we are pointing at the end of the NUMERIC. 186 if (numericField != null 187 && cfpos.matchesField(numericField, null) 188 && i > self.zero 189 // don't return the same field twice in a row: 190 && (i - self.zero > cfpos.getLimit() || cfpos.getField() != numericField) 191 && isNumericField(self.fields[i - 1]) 192 && !isNumericField(_field)) { 193 int j = i - 1; 194 for (; j >= self.zero && isNumericField(self.fields[j]); j--) {} 195 cfpos.setState(numericField, null, j - self.zero + 1, i - self.zero); 196 return true; 197 } 198 // Special case: emit normalField if we are pointing at the end of spanField. 199 if (i > self.zero 200 && self.fields[i-1] instanceof SpanFieldPlaceholder) { 201 int j = i - 1; 202 for (; j >= self.zero && self.fields[j] == self.fields[i-1]; j--) {} 203 if (handleSpan(self.fields[i-1], cfpos, j - self.zero + 1, i - self.zero)) { 204 return true; 205 } 206 } 207 // Special case: skip over INTEGER; will be coalesced later. 208 if (_field == NumberFormat.Field.INTEGER) { 209 _field = null; 210 } 211 // Case 2: no field starting at this position. 212 if (_field == null || _field == NullField.END) { 213 continue; 214 } 215 // Case 3: check for field starting at this position 216 // Case 3a: SpanField placeholder 217 if (_field instanceof SpanFieldPlaceholder) { 218 SpanFieldPlaceholder ph = (SpanFieldPlaceholder) _field; 219 if (cfpos.matchesField(ph.normalField, null) || cfpos.matchesField(ph.spanField, ph.value)) { 220 fieldStart = i - self.zero; 221 currField = _field; 222 } 223 } 224 // Case 3b: No SpanField 225 else if (cfpos.matchesField((Field) _field, null)) { 226 fieldStart = i - self.zero; 227 currField = _field; 228 } 229 } 230 231 assert currField == null; 232 return false; 233 } 234 isIntOrGroup(Object field)235 private static boolean isIntOrGroup(Object field) { 236 return field == NumberFormat.Field.INTEGER || field == NumberFormat.Field.GROUPING_SEPARATOR; 237 } 238 isNumericField(Object field)239 private static boolean isNumericField(Object field) { 240 return field == null || NumberFormat.Field.class.isAssignableFrom(field.getClass()); 241 } 242 isTrimmable(Object field)243 private static boolean isTrimmable(Object field) { 244 return field != NumberFormat.Field.GROUPING_SEPARATOR 245 && !(field instanceof ListFormatter.Field); 246 } 247 trimBack(FormattedStringBuilder self, int limit)248 private static int trimBack(FormattedStringBuilder self, int limit) { 249 return StaticUnicodeSets.get(StaticUnicodeSets.Key.DEFAULT_IGNORABLES) 250 .spanBack(self, limit, UnicodeSet.SpanCondition.CONTAINED); 251 } 252 trimFront(FormattedStringBuilder self, int start)253 private static int trimFront(FormattedStringBuilder self, int start) { 254 return StaticUnicodeSets.get(StaticUnicodeSets.Key.DEFAULT_IGNORABLES) 255 .span(self, start, UnicodeSet.SpanCondition.CONTAINED); 256 } 257 handleSpan(Object field, ConstrainedFieldPosition cfpos, int start, int limit)258 private static boolean handleSpan(Object field, ConstrainedFieldPosition cfpos, int start, int limit) { 259 SpanFieldPlaceholder ph = (SpanFieldPlaceholder) field; 260 if (cfpos.matchesField(ph.spanField, ph.value) 261 && cfpos.getLimit() < limit) { 262 cfpos.setState(ph.spanField, ph.value, start, limit); 263 return true; 264 } 265 if (cfpos.matchesField(ph.normalField, null) 266 && (cfpos.getLimit() < limit || cfpos.getField() != ph.normalField)) { 267 cfpos.setState(ph.normalField, null, start, limit); 268 return true; 269 } 270 return false; 271 } 272 } 273