/* GENERATED SOURCE. DO NOT MODIFY. */ // © 2019 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html package android.icu.impl; import java.text.AttributedCharacterIterator; import java.text.AttributedString; import java.text.FieldPosition; import java.text.Format.Field; import android.icu.text.ConstrainedFieldPosition; import android.icu.text.ListFormatter; import android.icu.text.NumberFormat; import android.icu.text.UFormat; import android.icu.text.UnicodeSet; /** * Implementation of FormattedValue based on FormattedStringBuilder. * * The implementation currently revolves around numbers and number fields. * However, it can be generalized in the future when there is a need. * * In C++, this implements FormattedValue. In Java, it is a stateless * collection of static functions to avoid having to use nested objects. * * @author sffc (Shane Carr) * @hide Only a subset of ICU is exposed in Android */ public class FormattedValueStringBuilderImpl { /** * Placeholder field used for calculating spans. * Does not currently support nested fields beyond one level. * @hide Only a subset of ICU is exposed in Android */ public static class SpanFieldPlaceholder implements FormattedStringBuilder.FieldWrapper { public UFormat.SpanField spanField; public Field normalField; public Object value; public int start; public int length; public Field unwrap() { return normalField; } } /** * Finds the index at which a span field begins. * * @param value The value of the span field to search for. * @return The index, or -1 if not found. */ public static int findSpan(FormattedStringBuilder self, Object value) { for (int i = self.zero; i < self.zero + self.length; i++) { if (!(self.fields[i] instanceof SpanFieldPlaceholder)) { continue; } if (((SpanFieldPlaceholder) self.fields[i]).value.equals(value)) { return i - self.zero; } } return -1; } /** * Upgrade a range of a string to a span field. * * Similar to appendSpanInfo in ICU4C. */ public static void applySpanRange( FormattedStringBuilder self, UFormat.SpanField spanField, Object value, int start, int end) { for (int i = start + self.zero; i < end + self.zero; i++) { Object oldField = self.fields[i]; SpanFieldPlaceholder newField = new SpanFieldPlaceholder(); newField.spanField = spanField; newField.normalField = (java.text.Format.Field) oldField; newField.value = value; newField.start = start; newField.length = end - start; self.fields[i] = newField; } } public static boolean nextFieldPosition(FormattedStringBuilder self, FieldPosition fp) { java.text.Format.Field rawField = fp.getFieldAttribute(); if (rawField == null) { // Backwards compatibility: read from fp.getField() if (fp.getField() == NumberFormat.INTEGER_FIELD) { rawField = NumberFormat.Field.INTEGER; } else if (fp.getField() == NumberFormat.FRACTION_FIELD) { rawField = NumberFormat.Field.FRACTION; } else { // No field is set return false; } } if (!(rawField instanceof NumberFormat.Field)) { throw new IllegalArgumentException( "You must pass an instance of android.icu.text.NumberFormat.Field as your FieldPosition attribute. You passed: " + rawField.getClass().toString()); } ConstrainedFieldPosition cfpos = new ConstrainedFieldPosition(); cfpos.constrainField(rawField); cfpos.setState(rawField, null, fp.getBeginIndex(), fp.getEndIndex()); if (nextPosition(self, cfpos, null)) { fp.setBeginIndex(cfpos.getStart()); fp.setEndIndex(cfpos.getLimit()); return true; } // Special case: fraction should start after integer if fraction is not present if (rawField == NumberFormat.Field.FRACTION && fp.getEndIndex() == 0) { boolean inside = false; int i = self.zero; for (; i < self.zero + self.length; i++) { if (isIntOrGroup(self.fields[i]) || self.fields[i] == NumberFormat.Field.DECIMAL_SEPARATOR) { inside = true; } else if (inside) { break; } } fp.setBeginIndex(i - self.zero); fp.setEndIndex(i - self.zero); } return false; } public static AttributedCharacterIterator toCharacterIterator(FormattedStringBuilder self, Field numericField) { ConstrainedFieldPosition cfpos = new ConstrainedFieldPosition(); AttributedString as = new AttributedString(self.toString()); while (nextPosition(self, cfpos, numericField)) { // Backwards compatibility: field value = field Object value = cfpos.getFieldValue(); if (value == null) { value = cfpos.getField(); } as.addAttribute(cfpos.getField(), value, cfpos.getStart(), cfpos.getLimit()); } return as.getIterator(); } static class NullField extends Field { private static final long serialVersionUID = 1L; static final NullField END = new NullField("end"); private NullField(String name) { super(name); } } /** * Implementation of nextPosition consistent with the contract of FormattedValue. * * @param cfpos * The argument passed to the public API. * @param numericField * Optional. If non-null, apply this field to the entire numeric portion of the string. * @return See FormattedValue#nextPosition. */ public static boolean nextPosition(FormattedStringBuilder self, ConstrainedFieldPosition cfpos, Field numericField) { int fieldStart = -1; Object currField = null; boolean prevIsSpan = false; if (cfpos.getLimit() > 0) { prevIsSpan = cfpos.getField() instanceof UFormat.SpanField && cfpos.getStart() < cfpos.getLimit(); } boolean prevIsNumeric = false; if (numericField != null) { prevIsNumeric = cfpos.getField() == numericField; } boolean prevIsInteger = cfpos.getField() == NumberFormat.Field.INTEGER; for (int i = self.zero + cfpos.getLimit(); i <= self.zero + self.length; i++) { Object _field = (i < self.zero + self.length) ? self.fields[i] : NullField.END; // Case 1: currently scanning a field. if (currField != null) { if (currField != _field) { int end = i - self.zero; // Grouping separators can be whitespace; don't throw them out! if (isTrimmable(currField)) { end = trimBack(self, end); } if (end <= fieldStart) { // Entire field position is ignorable; skip. fieldStart = -1; currField = null; i--; // look at this index again continue; } int start = fieldStart; if (isTrimmable(currField)) { start = trimFront(self, start); } cfpos.setState((Field) currField, null, start, end); return true; } continue; } // Special case: emit normalField if we are pointing at the end of spanField. if (i > self.zero && prevIsSpan) { assert self.fields[i-1] instanceof SpanFieldPlaceholder; SpanFieldPlaceholder ph = (SpanFieldPlaceholder) self.fields[i-1]; if (ph.normalField == ListFormatter.Field.ELEMENT) { // Special handling for ULISTFMT_ELEMENT_FIELD if (cfpos.matchesField(ListFormatter.Field.ELEMENT, null)) { fieldStart = i - self.zero - ph.length; int end = fieldStart + ph.length; cfpos.setState(ListFormatter.Field.ELEMENT, null, fieldStart, end); return true; } } else { // Re-wind, since there may be multiple fields in the span. i -= ph.length; assert i >= self.zero; _field = ((SpanFieldPlaceholder) self.fields[i]).normalField; } } // Special case: coalesce the INTEGER if we are pointing at the end of the INTEGER. if (cfpos.matchesField(NumberFormat.Field.INTEGER, null) && i > self.zero && !prevIsInteger && !prevIsNumeric && isIntOrGroup(self.fields[i - 1]) && !isIntOrGroup(_field)) { int j = i - 1; for (; j >= self.zero && isIntOrGroup(self.fields[j]); j--) {} cfpos.setState(NumberFormat.Field.INTEGER, null, j - self.zero + 1, i - self.zero); return true; } // Special case: coalesce NUMERIC if we are pointing at the end of the NUMERIC. if (numericField != null && cfpos.matchesField(numericField, null) && i > self.zero && !prevIsNumeric && isNumericField(self.fields[i - 1]) && !isNumericField(_field)) { // Re-wind to the beginning of the field and then emit it int j = i - 1; for (; j >= self.zero && isNumericField(self.fields[j]); j--) {} cfpos.setState(numericField, null, j - self.zero + 1, i - self.zero); return true; } // Check for span field SpanFieldPlaceholder ph = null; if (_field instanceof SpanFieldPlaceholder) { ph = (SpanFieldPlaceholder) _field; _field = ph.normalField; } if (ph != null && (ph.start == -1 || ph.start == i - self.zero)) { if (cfpos.matchesField(ph.spanField, ph.value)) { fieldStart = i - self.zero; int end = fieldStart + ph.length; cfpos.setState(ph.spanField, ph.value, fieldStart, end); return true; } else if (ph.normalField == ListFormatter.Field.ELEMENT) { // Special handling for ListFormatter.Field.ELEMENT if (cfpos.matchesField(ListFormatter.Field.ELEMENT, null)) { fieldStart = i - self.zero; int end = fieldStart + ph.length; cfpos.setState(ListFormatter.Field.ELEMENT, null, fieldStart, end); return true; } else { // Failed to match; jump ahead i += ph.length - 1; // goto loopend } } } // Special case: skip over INTEGER; will be coalesced later. else if (_field == NumberFormat.Field.INTEGER) { _field = null; } // No field starting at this position. else if (_field == null || _field == NullField.END) { // goto loopend } // No SpanField else if (cfpos.matchesField((Field) _field, null)) { fieldStart = i - self.zero; currField = _field; } // loopend: prevIsSpan = false; prevIsNumeric = false; prevIsInteger = false; } assert currField == null; // Always set the position to the end so that we don't revisit previous sections cfpos.setState( cfpos.getField(), cfpos.getFieldValue(), self.length, self.length); return false; } private static boolean isIntOrGroup(Object field) { field = FormattedStringBuilder.unwrapField(field); return field == NumberFormat.Field.INTEGER || field == NumberFormat.Field.GROUPING_SEPARATOR; } private static boolean isNumericField(Object field) { field = FormattedStringBuilder.unwrapField(field); return field == null || NumberFormat.Field.class.isAssignableFrom(field.getClass()); } private static boolean isTrimmable(Object field) { return field != NumberFormat.Field.GROUPING_SEPARATOR && !(field instanceof ListFormatter.Field); } private static int trimBack(FormattedStringBuilder self, int limit) { return StaticUnicodeSets.get(StaticUnicodeSets.Key.DEFAULT_IGNORABLES) .spanBack(self, limit, UnicodeSet.SpanCondition.CONTAINED); } private static int trimFront(FormattedStringBuilder self, int start) { return StaticUnicodeSets.get(StaticUnicodeSets.Key.DEFAULT_IGNORABLES) .span(self, start, UnicodeSet.SpanCondition.CONTAINED); } }