// 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;
*
* @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;
}
return chars[zero + index];
}
- public Field fieldAt(int index) {
+ public Object fieldAt(int index) {
assert index >= 0;
assert index < length;
return fields[zero + index];
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 appendChar16(char codeUnit, Object field) {
+ return insertChar16(length - appendOffset, codeUnit, field);
}
- public int insertChar16(int index, char codeUnit, Field field) {
+ public int insertChar16(int index, char codeUnit, Object field) {
int count = 1;
int position = prepareForInsert(index, count);
chars[position] = codeUnit;
*
* @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);
}
/**
*
* @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);
*
* @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);
}
/**
*
* @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;
*
* @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++) {
CharSequence sequence,
int startOther,
int endOther,
- Field field) {
+ Object field) {
int thisLength = endThis - startThis;
int otherLength = endOther - startOther;
int count = otherLength - thisLength;
*
* @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);
}
/**
*
* @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)
* @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);
}
/**
* @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;
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.
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, '-');
}
/** @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)
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;
/**
*/
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();
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();
}
*/
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) {
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;
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;
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;
}
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);
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;
+ }
}
*/
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}
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()) {
// 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;
// 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;
// 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;
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;
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
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.
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);
}
// {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 {
*/
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;
/**
* Indicates the style of Listformatter
+ * TODO(ICU-20888): Remove this in ICU 68.
* @internal
* @deprecated This API is ICU internal only.
*/
}
+ /**
+ * 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 locale
* the locale in question.
* @return ListFormatter
- * @stable ICU 50
+ * @draft ICU 67
+ * @provisional This API might change or be removed in a future release.
*/
- public static ListFormatter getInstance(ULocale locale) {
- return getInstance(locale, Style.STANDARD);
+ 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);
}
/**
* @param locale
* the locale in question.
* @return ListFormatter
- * @stable ICU 50
+ * @draft ICU 67
+ * @provisional This API might change or be removed in a future release.
*/
- public static ListFormatter getInstance(Locale locale) {
- return getInstance(ULocale.forLocale(locale), Style.STANDARD);
+ public static ListFormatter getInstance(Locale locale, Type type, Width width) {
+ return getInstance(ULocale.forLocale(locale), type, width);
}
/**
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.
*
* @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);
}
/**
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));
}
// Builds a formatted list
static class FormattedListBuilder {
- private StringBuilder current;
- private int offset;
-
- // 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;
+ 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. 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);
}
}
- @Override
- public String toString() {
- return current.toString();
+ public void appendTo(Appendable appendable) {
+ Utility.appendTo(string, appendable);
}
- // Gets the last recorded offset or -1 if no offset recorded.
- public int getOffset() {
- return offset;
+ public int getOffset(int fieldPositionFoundIndex) {
+ return FormattedValueStringBuilderImpl.findSpan(string, fieldPositionFoundIndex);
}
- private boolean offsetRecorded() {
- return offset >= 0;
+ @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<String, ListFormatter>();
+ new SimpleCache<>();
public ListFormatter get(ULocale locale, String style) {
String key = String.format("%s:%s", locale.toString(), style);
}
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;
+ }
}
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);
}
}
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);
}
*/
package com.ibm.icu.text;
-import java.io.IOException;
import java.io.InvalidObjectException;
import java.text.AttributedCharacterIterator;
import java.text.Format;
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;
*/
@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);
}
/**
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;
}
import static org.junit.Assert.assertTrue;
import java.text.FieldPosition;
-import java.text.Format.Field;
import org.junit.Test;
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]);
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]);
package com.ibm.icu.dev.test.format;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Locale;
import org.junit.Test;
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)
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);
+ }
+ }
}
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;
}
}
+ 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();
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());