ICU-20099 Implementing Java ListFormatter proposals for ICU 67.

See #904
This commit is contained in:
Shane Carr 2019-11-16 07:14:54 +00:00 committed by Shane F. Carr
parent a4a5c603ac
commit edf3f9c727
14 changed files with 834 additions and 211 deletions

View file

@ -2,7 +2,6 @@
// License & terms of use: http://www.unicode.org/copyright.html#License
package com.ibm.icu.impl;
import java.text.Format.Field;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
@ -24,23 +23,29 @@ import com.ibm.icu.text.NumberFormat;
*
* @author sffc (Shane Carr)
*/
public class FormattedStringBuilder implements CharSequence {
public class FormattedStringBuilder implements CharSequence, Appendable {
/** A constant, empty FormattedStringBuilder. Do NOT call mutative operations on this. */
public static final FormattedStringBuilder EMPTY = new FormattedStringBuilder();
char[] chars;
Field[] fields;
Object[] fields;
int zero;
int length;
/** Number of characters from the end where .append() operations insert. */
int appendOffset = 0;
/** Field applied when Appendable methods are used. */
Object appendableField = null;
public FormattedStringBuilder() {
this(40);
}
public FormattedStringBuilder(int capacity) {
chars = new char[capacity];
fields = new Field[capacity];
fields = new Object[capacity];
zero = capacity / 2;
length = 0;
}
@ -72,7 +77,7 @@ public class FormattedStringBuilder implements CharSequence {
return chars[zero + index];
}
public Field fieldAt(int index) {
public Object fieldAt(int index) {
assert index >= 0;
assert index < length;
return fields[zero + index];
@ -106,11 +111,20 @@ public class FormattedStringBuilder implements CharSequence {
return this;
}
public int appendChar16(char codeUnit, Field field) {
return insertChar16(length, codeUnit, field);
/**
* Sets the index at which append operations insert. Defaults to the end.
*
* @param index The index at which append operations should insert.
*/
public void setAppendIndex(int index) {
appendOffset = length - index;
}
public int insertChar16(int index, char codeUnit, Field field) {
public int appendChar16(char codeUnit, Object field) {
return insertChar16(length - appendOffset, codeUnit, field);
}
public int insertChar16(int index, char codeUnit, Object field) {
int count = 1;
int position = prepareForInsert(index, count);
chars[position] = codeUnit;
@ -123,8 +137,8 @@ public class FormattedStringBuilder implements CharSequence {
*
* @return The number of chars added: 1 if the code point is in the BMP, or 2 otherwise.
*/
public int appendCodePoint(int codePoint, Field field) {
return insertCodePoint(length, codePoint, field);
public int appendCodePoint(int codePoint, Object field) {
return insertCodePoint(length - appendOffset, codePoint, field);
}
/**
@ -132,7 +146,7 @@ public class FormattedStringBuilder implements CharSequence {
*
* @return The number of chars added: 1 if the code point is in the BMP, or 2 otherwise.
*/
public int insertCodePoint(int index, int codePoint, Field field) {
public int insertCodePoint(int index, int codePoint, Object field) {
int count = Character.charCount(codePoint);
int position = prepareForInsert(index, count);
Character.toChars(codePoint, chars, position);
@ -147,8 +161,8 @@ public class FormattedStringBuilder implements CharSequence {
*
* @return The number of chars added, which is the length of CharSequence.
*/
public int append(CharSequence sequence, Field field) {
return insert(length, sequence, field);
public int append(CharSequence sequence, Object field) {
return insert(length - appendOffset, sequence, field);
}
/**
@ -156,7 +170,7 @@ public class FormattedStringBuilder implements CharSequence {
*
* @return The number of chars added, which is the length of CharSequence.
*/
public int insert(int index, CharSequence sequence, Field field) {
public int insert(int index, CharSequence sequence, Object field) {
if (sequence.length() == 0) {
// Nothing to insert.
return 0;
@ -175,7 +189,7 @@ public class FormattedStringBuilder implements CharSequence {
*
* @return The number of chars added, which is the length of CharSequence.
*/
public int insert(int index, CharSequence sequence, int start, int end, Field field) {
public int insert(int index, CharSequence sequence, int start, int end, Object field) {
int count = end - start;
int position = prepareForInsert(index, count);
for (int i = 0; i < count; i++) {
@ -199,7 +213,7 @@ public class FormattedStringBuilder implements CharSequence {
CharSequence sequence,
int startOther,
int endOther,
Field field) {
Object field) {
int thisLength = endThis - startThis;
int otherLength = endOther - startOther;
int count = otherLength - thisLength;
@ -224,8 +238,8 @@ public class FormattedStringBuilder implements CharSequence {
*
* @return The number of chars added, which is the length of the char array.
*/
public int append(char[] chars, Field[] fields) {
return insert(length, chars, fields);
public int append(char[] chars, Object[] fields) {
return insert(length - appendOffset, chars, fields);
}
/**
@ -234,7 +248,7 @@ public class FormattedStringBuilder implements CharSequence {
*
* @return The number of chars added, which is the length of the char array.
*/
public int insert(int index, char[] chars, Field[] fields) {
public int insert(int index, char[] chars, Object[] fields) {
assert fields == null || chars.length == fields.length;
int count = chars.length;
if (count == 0)
@ -253,7 +267,7 @@ public class FormattedStringBuilder implements CharSequence {
* @return The number of chars added, which is the length of the other {@link FormattedStringBuilder}.
*/
public int append(FormattedStringBuilder other) {
return insert(length, other);
return insert(length - appendOffset, other);
}
/**
@ -288,6 +302,9 @@ public class FormattedStringBuilder implements CharSequence {
* @return The position in the char array to insert the chars.
*/
private int prepareForInsert(int index, int count) {
if (index == -1) {
index = length;
}
if (index == 0 && zero - count >= 0) {
// Append to start
zero -= count;
@ -309,13 +326,13 @@ public class FormattedStringBuilder implements CharSequence {
int oldCapacity = getCapacity();
int oldZero = zero;
char[] oldChars = chars;
Field[] oldFields = fields;
Object[] oldFields = fields;
if (length + count > oldCapacity) {
int newCapacity = (length + count) * 2;
int newZero = newCapacity / 2 - (length + count) / 2;
char[] newChars = new char[newCapacity];
Field[] newFields = new Field[newCapacity];
Object[] newFields = new Object[newCapacity];
// First copy the prefix and then the suffix, leaving room for the new chars that the
// caller wants to insert.
@ -408,7 +425,7 @@ public class FormattedStringBuilder implements CharSequence {
return new String(chars, zero, length);
}
private static final Map<Field, Character> fieldToDebugChar = new HashMap<>();
private static final Map<Object, Character> fieldToDebugChar = new HashMap<>();
static {
fieldToDebugChar.put(NumberFormat.Field.SIGN, '-');
@ -459,17 +476,59 @@ public class FormattedStringBuilder implements CharSequence {
}
/** @return A new array containing the field values of this string builder. */
public Field[] toFieldArray() {
public Object[] toFieldArray() {
return Arrays.copyOfRange(fields, zero, zero + length);
}
/**
* Call this method before using any of the Appendable overrides.
*
* @param field The field used when inserting strings.
*/
public void setAppendableField(Object field) {
appendableField = field;
}
/**
* This method is provided for Java Appendable compatibility. In most cases, please use the append methods that take
* a Field parameter. If you do use this method, you must call {@link #setAppendableField} first.
*/
@Override
public Appendable append(CharSequence csq) {
assert appendableField != null;
insert(length - appendOffset, csq, appendableField);
return this;
}
/**
* This method is provided for Java Appendable compatibility. In most cases, please use the append methods that take
* a Field parameter. If you do use this method, you must call {@link #setAppendableField} first.
*/
@Override
public Appendable append(CharSequence csq, int start, int end) {
assert appendableField != null;
insert(length - appendOffset, csq, start, end, appendableField);
return this;
}
/**
* This method is provided for Java Appendable compatibility. In most cases, please use the append methods that take
* a Field parameter. If you do use this method, you must call {@link #setAppendableField} first.
*/
@Override
public Appendable append(char c) {
assert appendableField != null;
insertChar16(length - appendOffset, c, appendableField);
return this;
}
/**
* @return Whether the contents and field values of this string builder are equal to the given chars
* and fields.
* @see #toCharArray
* @see #toFieldArray
*/
public boolean contentEquals(char[] chars, Field[] fields) {
public boolean contentEquals(char[] chars, Object[] fields) {
if (chars.length != length)
return false;
if (fields.length != length)

View file

@ -8,7 +8,9 @@ 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;
/**
@ -24,6 +26,33 @@ import com.ibm.icu.text.UnicodeSet;
*/
public class FormattedValueStringBuilderImpl {
/**
* Placeholder field used for calculating spans.
* Does not currently support nested fields beyond one level.
*/
public static class SpanFieldPlaceholder {
public UFormat.SpanField spanField;
public Field normalField;
public Object value;
}
/**
* 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;
}
public static boolean nextFieldPosition(FormattedStringBuilder self, FieldPosition fp) {
java.text.Format.Field rawField = fp.getFieldAttribute();
@ -78,7 +107,11 @@ public class FormattedValueStringBuilderImpl {
AttributedString as = new AttributedString(self.toString());
while (nextPosition(self, cfpos, numericField)) {
// Backwards compatibility: field value = field
as.addAttribute(cfpos.getField(), cfpos.getField(), cfpos.getStart(), cfpos.getLimit());
Object value = cfpos.getFieldValue();
if (value == null) {
value = cfpos.getField();
}
as.addAttribute(cfpos.getField(), value, cfpos.getStart(), cfpos.getLimit());
}
return as.getIterator();
}
@ -102,15 +135,20 @@ public class FormattedValueStringBuilderImpl {
*/
public static boolean nextPosition(FormattedStringBuilder self, ConstrainedFieldPosition cfpos, Field numericField) {
int fieldStart = -1;
Field currField = null;
Object currField = null;
for (int i = self.zero + cfpos.getLimit(); i <= self.zero + self.length; i++) {
Field _field = (i < self.zero + self.length) ? self.fields[i] : NullField.END;
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;
// Handle span fields; don't trim them
if (currField instanceof SpanFieldPlaceholder) {
assert handleSpan(currField, cfpos, fieldStart, end);
return true;
}
// Grouping separators can be whitespace; don't throw them out!
if (currField != NumberFormat.Field.GROUPING_SEPARATOR) {
if (isTrimmable(currField)) {
end = trimBack(self, end);
}
if (end <= fieldStart) {
@ -121,10 +159,10 @@ public class FormattedValueStringBuilderImpl {
continue;
}
int start = fieldStart;
if (currField != NumberFormat.Field.GROUPING_SEPARATOR) {
if (isTrimmable(currField)) {
start = trimFront(self, start);
}
cfpos.setState(currField, null, start, end);
cfpos.setState((Field) currField, null, start, end);
return true;
}
continue;
@ -154,6 +192,15 @@ public class FormattedValueStringBuilderImpl {
cfpos.setState(numericField, null, j - self.zero + 1, i - self.zero);
return true;
}
// Special case: emit normalField if we are pointing at the end of spanField.
if (i > self.zero
&& self.fields[i-1] instanceof SpanFieldPlaceholder) {
int j = i - 1;
for (; j >= self.zero && self.fields[j] == self.fields[i-1]; j--) {}
if (handleSpan(self.fields[i-1], cfpos, j - self.zero + 1, i - self.zero)) {
return true;
}
}
// Special case: skip over INTEGER; will be coalesced later.
if (_field == NumberFormat.Field.INTEGER) {
_field = null;
@ -163,7 +210,16 @@ public class FormattedValueStringBuilderImpl {
continue;
}
// Case 3: check for field starting at this position
if (cfpos.matchesField(_field, null)) {
// Case 3a: SpanField placeholder
if (_field instanceof SpanFieldPlaceholder) {
SpanFieldPlaceholder ph = (SpanFieldPlaceholder) _field;
if (cfpos.matchesField(ph.normalField, null) || cfpos.matchesField(ph.spanField, ph.value)) {
fieldStart = i - self.zero;
currField = _field;
}
}
// Case 3b: No SpanField
else if (cfpos.matchesField((Field) _field, null)) {
fieldStart = i - self.zero;
currField = _field;
}
@ -173,14 +229,19 @@ public class FormattedValueStringBuilderImpl {
return false;
}
private static boolean isIntOrGroup(Field field) {
private static boolean isIntOrGroup(Object field) {
return field == NumberFormat.Field.INTEGER || field == NumberFormat.Field.GROUPING_SEPARATOR;
}
private static boolean isNumericField(Field field) {
private static boolean isNumericField(Object 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);
@ -190,4 +251,19 @@ public class FormattedValueStringBuilderImpl {
return StaticUnicodeSets.get(StaticUnicodeSets.Key.DEFAULT_IGNORABLES)
.span(self, start, UnicodeSet.SpanCondition.CONTAINED);
}
private static boolean handleSpan(Object field, ConstrainedFieldPosition cfpos, int start, int limit) {
SpanFieldPlaceholder ph = (SpanFieldPlaceholder) field;
if (cfpos.matchesField(ph.spanField, ph.value)
&& cfpos.getLimit() < limit) {
cfpos.setState(ph.spanField, ph.value, start, limit);
return true;
}
if (cfpos.matchesField(ph.normalField, null)
&& (cfpos.getLimit() < limit || cfpos.getField() != ph.normalField)) {
cfpos.setState(ph.normalField, null, start, limit);
return true;
}
return false;
}
}

View file

@ -8,6 +8,11 @@
*/
package com.ibm.icu.impl;
import java.io.IOException;
import java.text.Format;
import com.ibm.icu.util.ICUUncheckedIOException;
/**
* Formats simple patterns like "{1} was born in {0}".
* Internal version of {@link com.ibm.icu.text.SimpleFormatter}
@ -304,17 +309,124 @@ public final class SimpleFormatterImpl {
return sb.toString();
}
/** Poor-man's iterator interface. See ICU-20406. */
public static class Int64Iterator {
/**
* Returns the length of the pattern text with none of the arguments.
* @param compiledPattern Compiled form of a pattern string.
* @param codePoints true to count code points; false to count code units.
* @return The number of code points or code units.
*/
public static int getLength(String compiledPattern, boolean codePoints) {
int result = 0;
for (int i = 1; i < compiledPattern.length();) {
int segmentLength = compiledPattern.charAt(i++) - ARG_NUM_LIMIT;
if (segmentLength > 0) {
int limit = i + segmentLength;
if (codePoints) {
result += Character.codePointCount(compiledPattern, i, limit);
} else {
result += (limit - i);
}
i = limit;
}
}
return result;
}
/**
* Returns the length in code units of the pattern text up until the first argument.
* @param compiledPattern Compiled form of a pattern string.
* @return The number of code units.
*/
public static int getPrefixLength(String compiledPattern) {
if (compiledPattern.length() == 1) {
return 0;
} else if (compiledPattern.charAt(0) == 0) {
return compiledPattern.length() - 2;
} else if (compiledPattern.charAt(1) <= ARG_NUM_LIMIT) {
return 0;
} else {
return compiledPattern.charAt(1) - ARG_NUM_LIMIT;
}
}
/**
* Special case for using FormattedStringBuilder with patterns with 0 or 1 argument.
*
* With 1 argument, treat the current contents of the FormattedStringBuilder between
* start and end as the argument {0}. Insert the extra strings from compiledPattern
* to surround the argument in the output.
*
* With 0 arguments, overwrite the entire contents of the FormattedStringBuilder
* between start and end.
*
* @param compiledPattern Compiled form of a pattern string.
* @param field Field to use when adding chars to the output.
* @param start The start index of the argument already in the output string.
* @param end The end index of the argument already in the output string.
* @param output Destination for formatted output.
* @return Net number of characters added to the formatted string.
*/
public static int formatPrefixSuffix(
String compiledPattern,
Format.Field field,
int start,
int end,
FormattedStringBuilder output) {
int argLimit = getArgumentLimit(compiledPattern);
if (argLimit == 0) {
// No arguments in compiled pattern; overwrite the entire segment with our string.
return output.splice(start, end, compiledPattern, 2, compiledPattern.length(), field);
} else {
assert argLimit == 1;
int suffixOffset;
int length = 0;
if (compiledPattern.charAt(1) != '\u0000') {
int prefixLength = compiledPattern.charAt(1) - ARG_NUM_LIMIT;
length = output.insert(start, compiledPattern, 2, 2 + prefixLength, field);
suffixOffset = 3 + prefixLength;
} else {
suffixOffset = 2;
}
if (suffixOffset < compiledPattern.length()) {
int suffixLength = compiledPattern.charAt(suffixOffset) - ARG_NUM_LIMIT;
length += output.insert(end + length, compiledPattern, 1 + suffixOffset,
1 + suffixOffset + suffixLength, field);
}
return length;
}
}
/** Internal iterator interface for maximum efficiency.
*
* Usage boilerplate:
*
* <pre>
* long state = 0;
* while (true) {
* state = IterInternal.step(state, compiledPattern, output);
* if (state == IterInternal.DONE) {
* break;
* }
* int argIndex = IterInternal.getArgIndex(state);
* // Append the string corresponding to argIndex to output
* }
* </pre>
*
*/
public static class IterInternal {
public static final long DONE = -1;
public static long step(CharSequence compiledPattern, long state, StringBuffer output) {
public static long step(long state, CharSequence compiledPattern, Appendable output) {
int i = (int) (state >>> 32);
assert i < compiledPattern.length();
i++;
while (i < compiledPattern.length() && compiledPattern.charAt(i) > ARG_NUM_LIMIT) {
int limit = i + compiledPattern.charAt(i) + 1 - ARG_NUM_LIMIT;
output.append(compiledPattern, i + 1, limit);
try {
output.append(compiledPattern, i + 1, limit);
} catch (IOException e) {
throw new ICUUncheckedIOException(e);
}
i = limit;
}
if (i == compiledPattern.length()) {

View file

@ -18,8 +18,8 @@ public class ConstantMultiFieldModifier implements Modifier {
// value and is treated internally as immutable.
protected final char[] prefixChars;
protected final char[] suffixChars;
protected final Field[] prefixFields;
protected final Field[] suffixFields;
protected final Object[] prefixFields;
protected final Object[] suffixFields;
private final boolean overwrite;
private final boolean strong;

View file

@ -2,8 +2,6 @@
// License & terms of use: http://www.unicode.org/copyright.html#License
package com.ibm.icu.impl.number;
import java.text.Format.Field;
import com.ibm.icu.impl.FormattedStringBuilder;
import com.ibm.icu.text.DecimalFormatSymbols;
import com.ibm.icu.text.NumberFormat;
@ -125,7 +123,7 @@ public class CurrencySpacingEnabledModifier extends ConstantMultiFieldModifier {
// NOTE: For prefix, output.fieldAt(index-1) gets the last field type in the prefix.
// This works even if the last code point in the prefix is 2 code units because the
// field value gets populated to both indices in the field array.
Field affixField = (affix == PREFIX) ? output.fieldAt(index - 1)
Object affixField = (affix == PREFIX) ? output.fieldAt(index - 1)
: output.fieldAt(index);
if (affixField != NumberFormat.Field.CURRENCY) {
return 0;

View file

@ -17,9 +17,6 @@ public class SimpleModifier implements Modifier {
private final String compiledPattern;
private final Field field;
private final boolean strong;
private final int prefixLength;
private final int suffixOffset;
private final int suffixLength;
// Parameters: used for number range formatting
private final Parameters parameters;
@ -39,53 +36,21 @@ public class SimpleModifier implements Modifier {
this.field = field;
this.strong = strong;
this.parameters = parameters;
int argLimit = SimpleFormatterImpl.getArgumentLimit(compiledPattern);
if (argLimit == 0) {
// No arguments in compiled pattern
prefixLength = compiledPattern.charAt(1) - ARG_NUM_LIMIT;
assert 2 + prefixLength == compiledPattern.length();
// Set suffixOffset = -1 to indicate no arguments in compiled pattern.
suffixOffset = -1;
suffixLength = 0;
} else {
assert argLimit == 1;
if (compiledPattern.charAt(1) != '\u0000') {
prefixLength = compiledPattern.charAt(1) - ARG_NUM_LIMIT;
suffixOffset = 3 + prefixLength;
} else {
prefixLength = 0;
suffixOffset = 2;
}
if (3 + prefixLength < compiledPattern.length()) {
suffixLength = compiledPattern.charAt(suffixOffset) - ARG_NUM_LIMIT;
} else {
suffixLength = 0;
}
}
}
@Override
public int apply(FormattedStringBuilder output, int leftIndex, int rightIndex) {
return formatAsPrefixSuffix(output, leftIndex, rightIndex);
return SimpleFormatterImpl.formatPrefixSuffix(compiledPattern, field, leftIndex, rightIndex, output);
}
@Override
public int getPrefixLength() {
return prefixLength;
return SimpleFormatterImpl.getPrefixLength(compiledPattern);
}
@Override
public int getCodePointCount() {
int count = 0;
if (prefixLength > 0) {
count += Character.codePointCount(compiledPattern, 2, 2 + prefixLength);
}
if (suffixLength > 0) {
count += Character
.codePointCount(compiledPattern, 1 + suffixOffset, 1 + suffixOffset + suffixLength);
}
return count;
return SimpleFormatterImpl.getLength(compiledPattern, true);
}
@Override
@ -117,49 +82,6 @@ public class SimpleModifier implements Modifier {
return compiledPattern.equals(_other.compiledPattern) && field == _other.field && strong == _other.strong;
}
/**
* TODO: This belongs in SimpleFormatterImpl. The only reason I haven't moved it there yet is because
* DoubleSidedStringBuilder is an internal class and SimpleFormatterImpl feels like it should not
* depend on it.
*
* <p>
* Formats a value that is already stored inside the StringBuilder <code>result</code> between the
* indices <code>startIndex</code> and <code>endIndex</code> by inserting characters before the start
* index and after the end index.
*
* <p>
* This is well-defined only for patterns with exactly one argument.
*
* @param result
* The StringBuilder containing the value argument.
* @param startIndex
* The left index of the value within the string builder.
* @param endIndex
* The right index of the value within the string builder.
* @return The number of characters (UTF-16 code points) that were added to the StringBuilder.
*/
public int formatAsPrefixSuffix(
FormattedStringBuilder result,
int startIndex,
int endIndex) {
if (suffixOffset == -1) {
// There is no argument for the inner number; overwrite the entire segment with our string.
return result.splice(startIndex, endIndex, compiledPattern, 2, 2 + prefixLength, field);
} else {
if (prefixLength > 0) {
result.insert(startIndex, compiledPattern, 2, 2 + prefixLength, field);
}
if (suffixLength > 0) {
result.insert(endIndex + prefixLength,
compiledPattern,
1 + suffixOffset,
1 + suffixOffset + suffixLength,
field);
}
return prefixLength + suffixLength;
}
}
/**
* TODO: Like above, this belongs with the rest of the SimpleFormatterImpl code.
* I put it here so that the SimpleFormatter uses in FormattedStringBuilder are near each other.

View file

@ -1032,11 +1032,11 @@ public class DateIntervalFormat extends UFormat {
fInfo.getFallbackIntervalPattern(), patternSB, 2, 2);
long state = 0;
while (true) {
state = SimpleFormatterImpl.Int64Iterator.step(compiledPattern, state, appendTo);
if (state == SimpleFormatterImpl.Int64Iterator.DONE) {
state = SimpleFormatterImpl.IterInternal.step(state, compiledPattern, appendTo);
if (state == SimpleFormatterImpl.IterInternal.DONE) {
break;
}
if (SimpleFormatterImpl.Int64Iterator.getArgIndex(state) == 0) {
if (SimpleFormatterImpl.IterInternal.getArgIndex(state) == 0) {
if (output != null) {
output.register(0);
}
@ -1090,11 +1090,11 @@ public class DateIntervalFormat extends UFormat {
// {1} is single date portion
long state = 0;
while (true) {
state = SimpleFormatterImpl.Int64Iterator.step(compiledPattern, state, appendTo);
if (state == SimpleFormatterImpl.Int64Iterator.DONE) {
state = SimpleFormatterImpl.IterInternal.step(state, compiledPattern, appendTo);
if (state == SimpleFormatterImpl.IterInternal.DONE) {
break;
}
if (SimpleFormatterImpl.Int64Iterator.getArgIndex(state) == 0) {
if (SimpleFormatterImpl.IterInternal.getArgIndex(state) == 0) {
fDateFormat.applyPattern(fTimePattern);
fallbackFormatRange(fromCalendar, toCalendar, appendTo, patternSB, pos, output, attributes);
} else {

View file

@ -8,19 +8,25 @@
*/
package com.ibm.icu.text;
import java.io.IOException;
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.util.ICUUncheckedIOException;
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;
@ -41,6 +47,7 @@ final public class ListFormatter {
/**
* Indicates the style of Listformatter
* TODO(ICU-20888): Remove this in ICU 68.
* @internal
* @deprecated This API is ICU internal only.
*/
@ -98,6 +105,242 @@ final public class ListFormatter {
}
/**
* 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.
@ -139,6 +382,50 @@ final public class ListFormatter {
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.
*
@ -163,20 +450,6 @@ final public class ListFormatter {
return getInstance(ULocale.forLocale(locale), Style.STANDARD);
}
/**
* 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 the default FORMAT locale.
*
@ -208,30 +481,59 @@ final public class ListFormatter {
* @stable ICU 50
*/
public String format(Collection<?> items) {
return format(items, -1).toString();
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 format(Collection<?> items, int index) {
FormattedListBuilder formatImpl(Collection<?> items, boolean needsFields) {
Iterator<?> it = items.iterator();
int count = items.size();
switch (count) {
case 0:
return new FormattedListBuilder("", false);
return new FormattedListBuilder("", needsFields);
case 1:
return new FormattedListBuilder(it.next(), index == 0);
return new FormattedListBuilder(it.next(), needsFields);
case 2:
return new FormattedListBuilder(it.next(), index == 0).append(two, it.next(), index == 1);
return new FormattedListBuilder(it.next(), needsFields).append(two, it.next(), 1);
}
FormattedListBuilder builder = new FormattedListBuilder(it.next(), index == 0);
builder.append(start, it.next(), index == 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(), index == idx);
builder.append(middle, it.next(), idx);
}
return builder.append(end, it.next(), index == count - 1);
return builder.append(end, it.next(), count - 1);
}
/**
@ -246,7 +548,7 @@ final public class ListFormatter {
if (count <= 0) {
throw new IllegalArgumentException("count must be > 0");
}
ArrayList<String> list = new ArrayList<String>();
ArrayList<String> list = new ArrayList<>();
for (int i = 0; i < count; i++) {
list.add(String.format("{%d}", i));
}
@ -265,64 +567,74 @@ final public class ListFormatter {
// Builds a formatted list
static class FormattedListBuilder {
private StringBuilder current;
private int offset;
private FormattedStringBuilder string;
boolean needsFields;
// Start is the first object in the list; If recordOffset is true, records the offset of
// this first object.
public FormattedListBuilder(Object start, boolean recordOffset) {
this.current = new StringBuilder(start.toString());
this.offset = recordOffset ? 0 : -1;
// 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. If recordOffset
// is true, records the offset of next in the formatted string.
public FormattedListBuilder append(String pattern, Object next, boolean recordOffset) {
int[] offsets = (recordOffset || offsetRecorded()) ? new int[2] : null;
SimpleFormatterImpl.formatAndReplace(
pattern, current, offsets, current, next.toString());
if (offsets != null) {
if (offsets[0] == -1 || offsets[1] == -1) {
throw new IllegalArgumentException(
"{0} or {1} missing from pattern " + pattern);
// 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;
}
if (recordOffset) {
offset = offsets[1];
int argIndex = IterInternal.getArgIndex(state);
if (argIndex == 0) {
string.setAppendIndex(string.length());
} else {
offset += offsets[0];
appendElement(next, position);
}
}
return this;
}
public void appendTo(Appendable appendable) {
try {
appendable.append(current);
} catch(IOException e) {
throw new ICUUncheckedIOException(e);
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 current.toString();
return string.toString();
}
// Gets the last recorded offset or -1 if no offset recorded.
public int getOffset() {
return offset;
}
private boolean offsetRecorded() {
return offset >= 0;
public FormattedList toValue() {
return new FormattedList(string);
}
}
private static class Cache {
private final ICUCache<String, ListFormatter> cache =
new SimpleCache<String, ListFormatter>();
new SimpleCache<>();
public ListFormatter get(ULocale locale, String style) {
String key = String.format("%s:%s", locale.toString(), style);
@ -348,4 +660,42 @@ final public class ListFormatter {
}
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;
}
}

View file

@ -464,7 +464,7 @@ public class MeasureFormat extends UFormat {
results[i] = formatMeasureInteger(measures[i]).toString();
}
}
FormattedListBuilder builder = listFormatter.format(Arrays.asList(results), -1);
FormattedListBuilder builder = listFormatter.formatImpl(Arrays.asList(results), false);
builder.appendTo(appendTo);
}
@ -811,13 +811,13 @@ public class MeasureFormat extends UFormat {
}
results[i] = result.toString();
}
ListFormatter.FormattedListBuilder builder = listFormatter.format(Arrays.asList(results),
fieldPositionFoundIndex);
ListFormatter.FormattedListBuilder builder = listFormatter.formatImpl(Arrays.asList(results), true);
// Fix up FieldPosition indexes if our field is found.
if (builder.getOffset() != -1) {
fieldPosition.setBeginIndex(fpos.getBeginIndex() + builder.getOffset());
fieldPosition.setEndIndex(fpos.getEndIndex() + builder.getOffset());
int offset = builder.getOffset(fieldPositionFoundIndex);
if (offset != -1) {
fieldPosition.setBeginIndex(fpos.getBeginIndex() + offset);
fieldPosition.setEndIndex(fpos.getEndIndex() + offset);
}
builder.appendTo(appendTo);
}

View file

@ -8,7 +8,6 @@
*/
package com.ibm.icu.text;
import java.io.IOException;
import java.io.InvalidObjectException;
import java.text.AttributedCharacterIterator;
import java.text.Format;
@ -24,13 +23,12 @@ import com.ibm.icu.impl.SimpleFormatterImpl;
import com.ibm.icu.impl.SoftCache;
import com.ibm.icu.impl.StandardPlural;
import com.ibm.icu.impl.UResource;
import com.ibm.icu.impl.Utility;
import com.ibm.icu.impl.number.DecimalQuantity;
import com.ibm.icu.impl.number.DecimalQuantity_DualStorageBCD;
import com.ibm.icu.impl.number.SimpleModifier;
import com.ibm.icu.lang.UCharacter;
import com.ibm.icu.util.Calendar;
import com.ibm.icu.util.ICUException;
import com.ibm.icu.util.ICUUncheckedIOException;
import com.ibm.icu.util.ULocale;
import com.ibm.icu.util.UResourceBundle;
@ -531,13 +529,7 @@ public final class RelativeDateTimeFormatter {
*/
@Override
public <A extends Appendable> A appendTo(A appendable) {
try {
appendable.append(string);
} catch (IOException e) {
// Throw as an unchecked exception to avoid users needing try/catch
throw new ICUUncheckedIOException(e);
}
return appendable;
return Utility.appendTo(string, appendable);
}
/**
@ -723,8 +715,7 @@ public final class RelativeDateTimeFormatter {
StandardPlural pluralForm = StandardPlural.orOtherFromString(pluralKeyword);
String compiledPattern = getRelativeUnitPluralPattern(style, unit, pastFutureIndex, pluralForm);
SimpleModifier modifier = new SimpleModifier(compiledPattern, Field.LITERAL, false);
modifier.formatAsPrefixSuffix(output, 0, output.length());
SimpleFormatterImpl.formatPrefixSuffix(compiledPattern, Field.LITERAL, 0, output.length(), output);
return output;
}

View file

@ -8,7 +8,6 @@ import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import java.text.FieldPosition;
import java.text.Format.Field;
import org.junit.Test;
@ -170,7 +169,7 @@ public class FormattedStringBuilderTest {
FormattedStringBuilder sb = new FormattedStringBuilder();
sb.append(str, null);
sb.append(str, NumberFormat.Field.CURRENCY);
Field[] fields = sb.toFieldArray();
Object[] fields = sb.toFieldArray();
assertEquals(str.length() * 2, fields.length);
for (int i = 0; i < str.length(); i++) {
assertEquals(null, fields[i]);
@ -198,7 +197,7 @@ public class FormattedStringBuilderTest {
int numNull = 0;
int numCurr = 0;
int numInt = 0;
Field[] oldFields = fields;
Object[] oldFields = fields;
fields = sb.toFieldArray();
for (int i = 0; i < sb.length(); i++) {
assertEquals(oldFields[i % oldFields.length], fields[i]);

View file

@ -9,6 +9,7 @@
package com.ibm.icu.dev.test.format;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Locale;
import org.junit.Test;
@ -17,6 +18,9 @@ import org.junit.runners.JUnit4;
import com.ibm.icu.dev.test.TestFmwk;
import com.ibm.icu.text.ListFormatter;
import com.ibm.icu.text.ListFormatter.FormattedList;
import com.ibm.icu.text.ListFormatter.Type;
import com.ibm.icu.text.ListFormatter.Width;
import com.ibm.icu.util.ULocale;
@RunWith(JUnit4.class)
@ -209,4 +213,83 @@ public class ListFormatterTest extends TestFmwk {
ULocale defaultLocale = ULocale.getDefault(ULocale.Category.FORMAT);
return defaultLocale.equals(ULocale.ENGLISH) || defaultLocale.equals(ULocale.US);
}
@Test
public void TestFormattedValue() {
ListFormatter fmt = ListFormatter.getInstance(ULocale.ENGLISH);
{
String message = "Field position test 1";
String expectedString = "hello, wonderful, and world";
String[] inputs = {
"hello",
"wonderful",
"world"
};
FormattedList result = fmt.formatToValue(Arrays.asList(inputs));
Object[][] expectedFieldPositions = new Object[][] {
// field, begin index, end index
{ListFormatter.SpanField.LIST_SPAN, 0, 5, 0},
{ListFormatter.Field.ELEMENT, 0, 5},
{ListFormatter.Field.LITERAL, 5, 7},
{ListFormatter.SpanField.LIST_SPAN, 7, 16, 1},
{ListFormatter.Field.ELEMENT, 7, 16},
{ListFormatter.Field.LITERAL, 16, 22},
{ListFormatter.SpanField.LIST_SPAN, 22, 27, 2},
{ListFormatter.Field.ELEMENT, 22, 27}};
FormattedValueTest.checkFormattedValue(
message,
result,
expectedString,
expectedFieldPositions);
}
}
@Test
public void TestCreateStyled() {
// Locale en has interesting data
Object[][] cases = {
{ "pt", Type.AND, Width.WIDE, "A, B e C" },
{ "pt", Type.AND, Width.SHORT, "A, B e C" },
{ "pt", Type.AND, Width.NARROW, "A, B, C" },
{ "pt", Type.OR, Width.WIDE, "A, B ou C" },
{ "pt", Type.OR, Width.SHORT, "A, B ou C" },
{ "pt", Type.OR, Width.NARROW, "A, B ou C" },
{ "pt", Type.UNITS, Width.WIDE, "A, B e C" },
{ "pt", Type.UNITS, Width.SHORT, "A, B e C" },
{ "pt", Type.UNITS, Width.NARROW, "A B C" },
{ "en", Type.AND, Width.WIDE, "A, B, and C" },
{ "en", Type.AND, Width.SHORT, "A, B, & C" },
{ "en", Type.AND, Width.NARROW, "A, B, C" },
{ "en", Type.OR, Width.WIDE, "A, B, or C" },
{ "en", Type.OR, Width.SHORT, "A, B, or C" },
{ "en", Type.OR, Width.NARROW, "A, B, or C" },
{ "en", Type.UNITS, Width.WIDE, "A, B, C" },
{ "en", Type.UNITS, Width.SHORT, "A, B, C" },
{ "en", Type.UNITS, Width.NARROW, "A B C" },
};
for (Object[] cas : cases) {
Locale loc = new Locale((String) cas[0]);
ULocale uloc = new ULocale((String) cas[0]);
Type type = (Type) cas[1];
Width width = (Width) cas[2];
String expected = (String) cas[3];
ListFormatter fmt1 = ListFormatter.getInstance(loc, type, width);
ListFormatter fmt2 = ListFormatter.getInstance(uloc, type, width);
String message = "TestCreateStyled loc="
+ loc + " type="
+ type + " width="
+ width;
String[] inputs = {
"A",
"B",
"C"
};
String result = fmt1.format(Arrays.asList(inputs));
assertEquals(message, expected, result);
// Coverage for the other factory method overload:
result = fmt2.format(Arrays.asList(inputs));
assertEquals(message, expected, result);
}
}
}

View file

@ -30,6 +30,7 @@ import com.ibm.icu.text.DateIntervalInfo;
import com.ibm.icu.text.DecimalFormat;
import com.ibm.icu.text.DecimalFormatSymbols;
import com.ibm.icu.text.DurationFormat;
import com.ibm.icu.text.ListFormatter;
import com.ibm.icu.text.MessageFormat;
import com.ibm.icu.text.NumberFormat;
import com.ibm.icu.text.PluralFormat;
@ -1831,6 +1832,36 @@ public class FormatHandler
}
}
public static class ListFormatterFieldHandler implements SerializableTestUtility.Handler
{
@Override
public Object[] getTestObjects()
{
return new Object[] {ListFormatter.Field.ELEMENT, ListFormatter.Field.LITERAL};
}
@Override
public boolean hasSameBehavior(Object a, Object b)
{
return (a == b);
}
}
public static class ListFormatterSpanFieldHandler implements SerializableTestUtility.Handler
{
@Override
public Object[] getTestObjects()
{
return new Object[] {ListFormatter.SpanField.LIST_SPAN};
}
@Override
public boolean hasSameBehavior(Object a, Object b)
{
return (a == b);
}
}
public static class DateFormatHandler implements SerializableTestUtility.Handler
{
static HashMap cannedPatterns = new HashMap();

View file

@ -820,6 +820,8 @@ public class SerializableTestUtility {
map.put("com.ibm.icu.text.MessageFormat$Field", new FormatHandler.MessageFormatFieldHandler());
map.put("com.ibm.icu.text.RelativeDateTimeFormatter$Field", new FormatHandler.RelativeDateTimeFormatterFieldHandler());
map.put("com.ibm.icu.text.DateIntervalFormat$SpanField", new FormatHandler.DateIntervalSpanFieldHandler());
map.put("com.ibm.icu.text.ListFormatter$Field", new FormatHandler.ListFormatterFieldHandler());
map.put("com.ibm.icu.text.ListFormatter$SpanField", new FormatHandler.ListFormatterSpanFieldHandler());
map.put("com.ibm.icu.impl.duration.BasicDurationFormat", new FormatHandler.BasicDurationFormatHandler());
map.put("com.ibm.icu.impl.RelativeDateFormat", new FormatHandler.RelativeDateFormatHandler());