All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.ibm.icu.impl.FormattedValueStringBuilderImpl Maven / Gradle / Ivy

Go to download

International Component for Unicode for Java (ICU4J) is a mature, widely used Java library providing Unicode and Globalization support

There is a newer version: 76.1
Show newest version
// © 2019 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
package com.ibm.icu.impl;

import java.text.AttributedCharacterIterator;
import java.text.AttributedString;
import java.text.FieldPosition;
import java.text.Format.Field;

import com.ibm.icu.text.ConstrainedFieldPosition;
import com.ibm.icu.text.ListFormatter;
import com.ibm.icu.text.NumberFormat;
import com.ibm.icu.text.UFormat;
import com.ibm.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)
 */
public class FormattedValueStringBuilderImpl {

    /**
     * Placeholder field used for calculating spans.
     * Does not currently support nested fields beyond one level.
     */
    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 com.ibm.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);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy