mirror of
https://github.com/unicode-org/icu.git
synced 2025-04-07 06:25:30 +00:00
ICU-20099 Implementing Java ListFormatter proposals for ICU 67.
See #904
This commit is contained in:
parent
a4a5c603ac
commit
edf3f9c727
14 changed files with 834 additions and 211 deletions
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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]);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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());
|
||||
|
|
Loading…
Add table
Reference in a new issue