| // © 2016 and later: Unicode, Inc. and others. |
| // License & terms of use: http://www.unicode.org/copyright.html#License |
| /* |
| ******************************************************************************* |
| * Copyright (C) 2012-2016, Google, International Business Machines Corporation and |
| * others. All Rights Reserved. |
| ******************************************************************************* |
| */ |
| package com.ibm.icu.text; |
| |
| import java.io.InvalidObjectException; |
| import java.text.AttributedCharacterIterator; |
| import java.text.Format; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Iterator; |
| import java.util.Locale; |
| |
| import com.ibm.icu.impl.FormattedStringBuilder; |
| import com.ibm.icu.impl.FormattedValueStringBuilderImpl; |
| import com.ibm.icu.impl.FormattedValueStringBuilderImpl.SpanFieldPlaceholder; |
| import com.ibm.icu.impl.ICUCache; |
| import com.ibm.icu.impl.ICUData; |
| import com.ibm.icu.impl.ICUResourceBundle; |
| import com.ibm.icu.impl.SimpleCache; |
| import com.ibm.icu.impl.SimpleFormatterImpl; |
| import com.ibm.icu.impl.SimpleFormatterImpl.IterInternal; |
| import com.ibm.icu.impl.Utility; |
| import com.ibm.icu.util.ULocale; |
| import com.ibm.icu.util.UResourceBundle; |
| |
| /** |
| * Immutable class for formatting a list, using data from CLDR (or supplied |
| * separately). The class is not subclassable. |
| * |
| * @author Mark Davis |
| * @stable ICU 50 |
| */ |
| final public class ListFormatter { |
| // Compiled SimpleFormatter patterns. |
| private final String two; |
| private final String start; |
| private final String middle; |
| private final String end; |
| private final ULocale locale; |
| |
| /** |
| * Indicates the style of Listformatter |
| * TODO(ICU-20888): Remove this in ICU 68. |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| public enum Style { |
| /** |
| * Standard, conjunction style. |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| STANDARD("standard"), |
| /** |
| * Disjunction style. |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| OR("or"), |
| /** |
| * Style for full units |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| UNIT("unit"), |
| /** |
| * Style for units in abbrevated form |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| UNIT_SHORT("unit-short"), |
| /** |
| * Style for units in narrow form |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| UNIT_NARROW("unit-narrow"); |
| |
| private final String name; |
| |
| Style(String name) { |
| this.name = name; |
| } |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| public String getName() { |
| return name; |
| } |
| |
| } |
| |
| /** |
| * Type of meaning expressed by the list. |
| * |
| * @draft ICU 67 |
| * @provisional This API might change or be removed in a future release. |
| */ |
| public enum Type { |
| /** |
| * Conjunction formatting, e.g. "Alice, Bob, Charlie, and Delta". |
| * |
| * @draft ICU 67 |
| * @provisional This API might change or be removed in a future release. |
| */ |
| AND, |
| |
| /** |
| * Disjunction (or alternative, or simply one of) formatting, e.g. |
| * "Alice, Bob, Charlie, or Delta". |
| * |
| * @draft ICU 67 |
| * @provisional This API might change or be removed in a future release. |
| */ |
| OR, |
| |
| /** |
| * Formatting of a list of values with units, e.g. "5 pounds, 12 ounces". |
| * |
| * @draft ICU 67 |
| * @provisional This API might change or be removed in a future release. |
| */ |
| UNITS |
| }; |
| |
| /** |
| * Verbosity level of the list patterns. |
| * |
| * @draft ICU 67 |
| * @provisional This API might change or be removed in a future release. |
| */ |
| public enum Width { |
| /** |
| * Use list formatting with full words (no abbreviations) when possible. |
| * |
| * @draft ICU 67 |
| * @provisional This API might change or be removed in a future release. |
| */ |
| WIDE, |
| |
| /** |
| * Use list formatting of typical length. |
| * |
| * @draft ICU 67 |
| * @provisional This API might change or be removed in a future release. |
| */ |
| SHORT, |
| |
| /** |
| * Use list formatting of the shortest possible length. |
| * |
| * @draft ICU 67 |
| * @provisional This API might change or be removed in a future release. |
| */ |
| NARROW, |
| }; |
| |
| /** |
| * Class for span fields in FormattedDateInterval. |
| * |
| * @draft ICU 64 |
| * @provisional This API might change or be removed in a future release. |
| */ |
| public static final class SpanField extends UFormat.SpanField { |
| private static final long serialVersionUID = 3563544214705634403L; |
| |
| /** |
| * The concrete field used for spans in FormattedList. |
| * |
| * Instances of LIST_SPAN should have an associated value, the index |
| * within the input list that is represented by the span. |
| * |
| * @draft ICU 64 |
| * @provisional This API might change or be removed in a future release. |
| */ |
| public static final SpanField LIST_SPAN = new SpanField("list-span"); |
| |
| private SpanField(String name) { |
| super(name); |
| } |
| |
| /** |
| * serizalization method resolve instances to the constant |
| * ListFormatter.SpanField values |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| @Override |
| protected Object readResolve() throws InvalidObjectException { |
| if (this.getName().equals(LIST_SPAN.getName())) |
| return LIST_SPAN; |
| |
| throw new InvalidObjectException("An invalid object."); |
| } |
| } |
| |
| /** |
| * Field selectors for format fields defined by ListFormatter. |
| * @draft ICU 67 |
| * @provisional This API might change or be removed in a future release. |
| */ |
| public static final class Field extends Format.Field { |
| private static final long serialVersionUID = -8071145668708265437L; |
| |
| /** |
| * The literal text in the result which came from the resources. |
| * @draft ICU 67 |
| * @provisional This API might change or be removed in a future release. |
| */ |
| public static Field LITERAL = new Field("literal"); |
| |
| /** |
| * The element text in the result which came from the input strings. |
| * @draft ICU 67 |
| * @provisional This API might change or be removed in a future release. |
| */ |
| public static Field ELEMENT = new Field("element"); |
| |
| private Field(String name) { |
| super(name); |
| } |
| |
| /** |
| * Serizalization method resolve instances to the constant Field values |
| * |
| * @draft ICU 64 |
| * @provisional This API might change or be removed in a future release. |
| */ |
| @Override |
| protected Object readResolve() throws InvalidObjectException { |
| if (this.getName().equals(LITERAL.getName())) |
| return LITERAL; |
| if (this.getName().equals(ELEMENT.getName())) |
| return ELEMENT; |
| |
| throw new InvalidObjectException("An invalid object."); |
| } |
| } |
| |
| /** |
| * An immutable class containing the result of a list formatting operation. |
| * |
| * Instances of this class are immutable and thread-safe. |
| * |
| * Not intended for public subclassing. |
| * |
| * @draft ICU 67 |
| * @provisional This API might change or be removed in a future release. |
| */ |
| public static final class FormattedList implements FormattedValue { |
| private final FormattedStringBuilder string; |
| |
| FormattedList(FormattedStringBuilder string) { |
| this.string = string; |
| } |
| |
| /** |
| * {@inheritDoc} |
| * @draft ICU 67 |
| * @provisional This API might change or be removed in a future release. |
| */ |
| @Override |
| public String toString() { |
| return string.toString(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * @draft ICU 67 |
| * @provisional This API might change or be removed in a future release. |
| */ |
| @Override |
| public int length() { |
| return string.length(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * @draft ICU 67 |
| * @provisional This API might change or be removed in a future release. |
| */ |
| @Override |
| public char charAt(int index) { |
| return string.charAt(index); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * @draft ICU 67 |
| * @provisional This API might change or be removed in a future release. |
| */ |
| @Override |
| public CharSequence subSequence(int start, int end) { |
| return string.subString(start, end); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * @draft ICU 67 |
| * @provisional This API might change or be removed in a future release. |
| */ |
| @Override |
| public <A extends Appendable> A appendTo(A appendable) { |
| return Utility.appendTo(string, appendable); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * @draft ICU 67 |
| * @provisional This API might change or be removed in a future release. |
| */ |
| @Override |
| public boolean nextPosition(ConstrainedFieldPosition cfpos) { |
| return FormattedValueStringBuilderImpl.nextPosition(string, cfpos, null); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * @draft ICU 67 |
| * @provisional This API might change or be removed in a future release. |
| */ |
| @Override |
| public AttributedCharacterIterator toCharacterIterator() { |
| return FormattedValueStringBuilderImpl.toCharacterIterator(string, null); |
| } |
| } |
| |
| /** |
| * <b>Internal:</b> Create a ListFormatter from component strings, |
| * with definitions as in LDML. |
| * |
| * @param two |
| * string for two items, containing {0} for the first, and {1} |
| * for the second. |
| * @param start |
| * string for the start of a list items, containing {0} for the |
| * first, and {1} for the rest. |
| * @param middle |
| * string for the start of a list items, containing {0} for the |
| * first part of the list, and {1} for the rest of the list. |
| * @param end |
| * string for the end of a list items, containing {0} for the |
| * first part of the list, and {1} for the last item. |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| public ListFormatter(String two, String start, String middle, String end) { |
| this( |
| compilePattern(two, new StringBuilder()), |
| compilePattern(start, new StringBuilder()), |
| compilePattern(middle, new StringBuilder()), |
| compilePattern(end, new StringBuilder()), |
| null); |
| } |
| |
| private ListFormatter(String two, String start, String middle, String end, ULocale locale) { |
| this.two = two; |
| this.start = start; |
| this.middle = middle; |
| this.end = end; |
| this.locale = locale; |
| } |
| |
| private static String compilePattern(String pattern, StringBuilder sb) { |
| return SimpleFormatterImpl.compileToStringMinMaxArguments(pattern, sb, 2, 2); |
| } |
| |
| /** |
| * Create a list formatter that is appropriate for a locale. |
| * |
| * @param locale |
| * the locale in question. |
| * @return ListFormatter |
| * @draft ICU 67 |
| * @provisional This API might change or be removed in a future release. |
| */ |
| public static ListFormatter getInstance(ULocale locale, Type type, Width width) { |
| String styleName = typeWidthToStyleString(type, width); |
| if (styleName == null) { |
| throw new IllegalArgumentException("Invalid list format type/width"); |
| } |
| return cache.get(locale, styleName); |
| } |
| |
| /** |
| * Create a list formatter that is appropriate for a locale. |
| * |
| * @param locale |
| * the locale in question. |
| * @return ListFormatter |
| * @draft ICU 67 |
| * @provisional This API might change or be removed in a future release. |
| */ |
| public static ListFormatter getInstance(Locale locale, Type type, Width width) { |
| return getInstance(ULocale.forLocale(locale), type, width); |
| } |
| |
| /** |
| * Create a list formatter that is appropriate for a locale and style. |
| * |
| * @param locale the locale in question. |
| * @param style the style |
| * @return ListFormatter |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| public static ListFormatter getInstance(ULocale locale, Style style) { |
| return cache.get(locale, style.getName()); |
| } |
| |
| /** |
| * Create a list formatter that is appropriate for a locale. |
| * |
| * @param locale |
| * the locale in question. |
| * @return ListFormatter |
| * @stable ICU 50 |
| */ |
| public static ListFormatter getInstance(ULocale locale) { |
| return getInstance(locale, Style.STANDARD); |
| } |
| |
| /** |
| * Create a list formatter that is appropriate for a locale. |
| * |
| * @param locale |
| * the locale in question. |
| * @return ListFormatter |
| * @stable ICU 50 |
| */ |
| public static ListFormatter getInstance(Locale locale) { |
| return getInstance(ULocale.forLocale(locale), Style.STANDARD); |
| } |
| |
| /** |
| * Create a list formatter that is appropriate for the default FORMAT locale. |
| * |
| * @return ListFormatter |
| * @stable ICU 50 |
| */ |
| public static ListFormatter getInstance() { |
| return getInstance(ULocale.getDefault(ULocale.Category.FORMAT)); |
| } |
| |
| /** |
| * Format a list of objects. |
| * |
| * @param items |
| * items to format. The toString() method is called on each. |
| * @return items formatted into a string |
| * @stable ICU 50 |
| */ |
| public String format(Object... items) { |
| return format(Arrays.asList(items)); |
| } |
| |
| /** |
| * Format a collection of objects. The toString() method is called on each. |
| * |
| * @param items |
| * items to format. The toString() method is called on each. |
| * @return items formatted into a string |
| * @stable ICU 50 |
| */ |
| public String format(Collection<?> items) { |
| return formatImpl(items, false).toString(); |
| } |
| |
| /** |
| * Format a list of objects to a FormattedList. You can access the offsets |
| * of each element from the FormattedList. |
| * |
| * @param items |
| * items to format. The toString() method is called on each. |
| * @return items formatted into a FormattedList |
| * @draft ICU 67 |
| * @provisional This API might change or be removed in a future release. |
| */ |
| public FormattedList formatToValue(Object... items) { |
| return formatToValue(Arrays.asList(items)); |
| } |
| |
| |
| /** |
| * Format a collection of objects to a FormattedList. You can access the offsets |
| * of each element from the FormattedList. |
| * |
| * @param items |
| * items to format. The toString() method is called on each. |
| * @return items formatted into a FormattedList |
| * @draft ICU 67 |
| * @provisional This API might change or be removed in a future release. |
| */ |
| public FormattedList formatToValue(Collection<?> items) { |
| return formatImpl(items, true).toValue(); |
| } |
| |
| // Formats a collection of objects and returns the formatted string plus the offset |
| // in the string where the index th element appears. index is zero based. If index is |
| // negative or greater than or equal to the size of items then this function returns -1 for |
| // the offset. |
| FormattedListBuilder formatImpl(Collection<?> items, boolean needsFields) { |
| Iterator<?> it = items.iterator(); |
| int count = items.size(); |
| switch (count) { |
| case 0: |
| return new FormattedListBuilder("", needsFields); |
| case 1: |
| return new FormattedListBuilder(it.next(), needsFields); |
| case 2: |
| return new FormattedListBuilder(it.next(), needsFields).append(two, it.next(), 1); |
| } |
| FormattedListBuilder builder = new FormattedListBuilder(it.next(), needsFields); |
| builder.append(start, it.next(), 1); |
| for (int idx = 2; idx < count - 1; ++idx) { |
| builder.append(middle, it.next(), idx); |
| } |
| return builder.append(end, it.next(), count - 1); |
| } |
| |
| /** |
| * Returns the pattern to use for a particular item count. |
| * @param count the item count. |
| * @return the pattern with {0}, {1}, {2}, etc. For English, |
| * getPatternForNumItems(3) == "{0}, {1}, and {2}" |
| * @throws IllegalArgumentException when count is 0 or negative. |
| * @stable ICU 52 |
| */ |
| public String getPatternForNumItems(int count) { |
| if (count <= 0) { |
| throw new IllegalArgumentException("count must be > 0"); |
| } |
| ArrayList<String> list = new ArrayList<>(); |
| for (int i = 0; i < count; i++) { |
| list.add(String.format("{%d}", i)); |
| } |
| return format(list); |
| } |
| |
| /** |
| * Returns the locale of this object. |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| public ULocale getLocale() { |
| return locale; |
| } |
| |
| // Builds a formatted list |
| static class FormattedListBuilder { |
| private FormattedStringBuilder string; |
| boolean needsFields; |
| |
| // Start is the first object in the list; If needsFields is true, enable the slightly |
| // more expensive code path that records offsets of each element. |
| public FormattedListBuilder(Object start, boolean needsFields) { |
| string = new FormattedStringBuilder(); |
| this.needsFields = needsFields; |
| string.setAppendableField(Field.LITERAL); |
| appendElement(start, 0); |
| } |
| |
| // Appends additional object. pattern is a template indicating where the new object gets |
| // added in relation to the rest of the list. {0} represents the rest of the list; {1} |
| // represents the new object in pattern. next is the object to be added. position is the |
| // index of the next object in the list of inputs. |
| public FormattedListBuilder append(String compiledPattern, Object next, int position) { |
| assert SimpleFormatterImpl.getArgumentLimit(compiledPattern) == 2; |
| string.setAppendIndex(0); |
| long state = 0; |
| while (true) { |
| state = IterInternal.step(state, compiledPattern, string); |
| if (state == IterInternal.DONE) { |
| break; |
| } |
| int argIndex = IterInternal.getArgIndex(state); |
| if (argIndex == 0) { |
| string.setAppendIndex(string.length()); |
| } else { |
| appendElement(next, position); |
| } |
| } |
| return this; |
| } |
| |
| private void appendElement(Object element, int position) { |
| if (needsFields) { |
| SpanFieldPlaceholder field = new SpanFieldPlaceholder(); |
| field.spanField = SpanField.LIST_SPAN; |
| field.normalField = Field.ELEMENT; |
| field.value = position; |
| string.append(element.toString(), field); |
| } else { |
| string.append(element.toString(), null); |
| } |
| } |
| |
| public void appendTo(Appendable appendable) { |
| Utility.appendTo(string, appendable); |
| } |
| |
| public int getOffset(int fieldPositionFoundIndex) { |
| return FormattedValueStringBuilderImpl.findSpan(string, fieldPositionFoundIndex); |
| } |
| |
| @Override |
| public String toString() { |
| return string.toString(); |
| } |
| |
| public FormattedList toValue() { |
| return new FormattedList(string); |
| } |
| } |
| |
| private static class Cache { |
| private final ICUCache<String, ListFormatter> cache = |
| new SimpleCache<>(); |
| |
| public ListFormatter get(ULocale locale, String style) { |
| String key = String.format("%s:%s", locale.toString(), style); |
| ListFormatter result = cache.get(key); |
| if (result == null) { |
| result = load(locale, style); |
| cache.put(key, result); |
| } |
| return result; |
| } |
| |
| private static ListFormatter load(ULocale ulocale, String style) { |
| ICUResourceBundle r = (ICUResourceBundle)UResourceBundle. |
| getBundleInstance(ICUData.ICU_BASE_NAME, ulocale); |
| StringBuilder sb = new StringBuilder(); |
| return new ListFormatter( |
| compilePattern(r.getWithFallback("listPattern/" + style + "/2").getString(), sb), |
| compilePattern(r.getWithFallback("listPattern/" + style + "/start").getString(), sb), |
| compilePattern(r.getWithFallback("listPattern/" + style + "/middle").getString(), sb), |
| compilePattern(r.getWithFallback("listPattern/" + style + "/end").getString(), sb), |
| ulocale); |
| } |
| } |
| |
| static Cache cache = new Cache(); |
| |
| static String typeWidthToStyleString(Type type, Width width) { |
| switch (type) { |
| case AND: |
| switch (width) { |
| case WIDE: |
| return "standard"; |
| case SHORT: |
| return "standard-short"; |
| case NARROW: |
| return "standard-narrow"; |
| } |
| break; |
| |
| case OR: |
| switch (width) { |
| case WIDE: |
| return "or"; |
| case SHORT: |
| return "or-short"; |
| case NARROW: |
| return "or-narrow"; |
| } |
| break; |
| |
| case UNITS: |
| switch (width) { |
| case WIDE: |
| return "unit"; |
| case SHORT: |
| return "unit-short"; |
| case NARROW: |
| return "unit-narrow"; |
| } |
| } |
| |
| return null; |
| } |
| } |