ICU-10646 Introduce Template class. Change ListFormatter and RelativeDateTimeFormatter to use the Template class instead of ad hoc replacement of placeholders.
X-SVN-Rev: 35014
This commit is contained in:
parent
4397b041e3
commit
1cf0f06b01
2
.gitattributes
vendored
2
.gitattributes
vendored
@ -260,6 +260,7 @@ icu4j/main/classes/core/.settings/edu.umd.cs.findbugs.core.prefs -text
|
||||
icu4j/main/classes/core/.settings/org.eclipse.core.resources.prefs -text
|
||||
icu4j/main/classes/core/.settings/org.eclipse.jdt.core.prefs -text
|
||||
icu4j/main/classes/core/manifest.stub -text
|
||||
icu4j/main/classes/core/src/com/ibm/icu/impl/Template.java -text
|
||||
icu4j/main/classes/currdata/.externalToolBuilders/copy-data-currdata.launch -text
|
||||
icu4j/main/classes/currdata/.settings/org.eclipse.core.resources.prefs -text
|
||||
icu4j/main/classes/currdata/.settings/org.eclipse.jdt.core.prefs -text
|
||||
@ -548,6 +549,7 @@ icu4j/main/tests/core/src/com/ibm/icu/dev/test/serializable/data/ICU_52.1/com.ib
|
||||
icu4j/main/tests/core/src/com/ibm/icu/dev/test/serializable/data/ICU_52.1/com.ibm.icu.util.ULocale.dat -text
|
||||
icu4j/main/tests/core/src/com/ibm/icu/dev/test/serializable/data/ICU_52.1/com.ibm.icu.util.UResourceTypeMismatchException.dat -text
|
||||
icu4j/main/tests/core/src/com/ibm/icu/dev/test/serializable/data/ICU_52.1/com.ibm.icu.util.VTimeZone.dat -text
|
||||
icu4j/main/tests/core/src/com/ibm/icu/dev/test/util/TemplateTest.java -text
|
||||
icu4j/main/tests/core/src/com/ibm/icu/dev/test/util/Trie2Test.setRanges1.16.tri2 -text
|
||||
icu4j/main/tests/core/src/com/ibm/icu/dev/test/util/Trie2Test.setRanges1.32.tri2 -text
|
||||
icu4j/main/tests/core/src/com/ibm/icu/dev/test/util/Trie2Test.setRanges2.16.tri2 -text
|
||||
|
347
icu4j/main/classes/core/src/com/ibm/icu/impl/Template.java
Normal file
347
icu4j/main/classes/core/src/com/ibm/icu/impl/Template.java
Normal file
@ -0,0 +1,347 @@
|
||||
/*
|
||||
*******************************************************************************
|
||||
* Copyright (C) 2014, International Business Machines Corporation and *
|
||||
* others. All Rights Reserved. *
|
||||
*******************************************************************************
|
||||
*/
|
||||
package com.ibm.icu.impl;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Compiled version of a template such as "{1} was born in {0}".
|
||||
* <p>
|
||||
* Using Template objects is both faster and safer than adhoc replacement
|
||||
* such as <code>pattern.replace("{0}", "Colorado").replace("{1} "Fred");</code>.
|
||||
* They are faster because they are precompiled; they are safer because they
|
||||
* account for curly braces escaped by apostrophe (').
|
||||
*
|
||||
* Placeholders are of the form \{[0-9]+\}. If a curly brace is preceded
|
||||
* by a single quote, it becomes a curly brace instead of the start of a
|
||||
* placeholder. Two single quotes resolve to one single quote.
|
||||
* <p>
|
||||
* Template objects are immutable and can be safely cached like strings.
|
||||
* <p>
|
||||
* Example:
|
||||
* <pre>
|
||||
* Template template = Template.compile("{1} '{born} in {0}");
|
||||
*
|
||||
* // Output: "paul {born} in england"
|
||||
* System.out.println(template.evaluate("england", "paul"));
|
||||
* </pre>
|
||||
*/
|
||||
public class Template {
|
||||
private final String patternWithoutPlaceholders;
|
||||
private final int placeholderCount;
|
||||
|
||||
// [0] first offset; [1] first placeholderId; [2] second offset;
|
||||
// [3] second placeholderId etc.
|
||||
private final int[] placeholderIdsOrderedByOffset;
|
||||
|
||||
private Template(String pattern, PlaceholdersBuilder builder) {
|
||||
this.patternWithoutPlaceholders = pattern;
|
||||
this.placeholderIdsOrderedByOffset =
|
||||
builder.getPlaceholderIdsOrderedByOffset();
|
||||
this.placeholderCount = builder.getPlaceholderCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles a string into a template.
|
||||
* @param pattern The string.
|
||||
* @return the new template object.
|
||||
*/
|
||||
public static Template compile(String pattern) {
|
||||
PlaceholdersBuilder placeholdersBuilder = new PlaceholdersBuilder();
|
||||
PlaceholderIdBuilder idBuilder = new PlaceholderIdBuilder();
|
||||
StringBuilder newPattern = new StringBuilder();
|
||||
State state = State.INIT;
|
||||
for (int i = 0; i < pattern.length(); i++) {
|
||||
char ch = pattern.charAt(i);
|
||||
switch (state) {
|
||||
case INIT:
|
||||
if (ch == 0x27) {
|
||||
state = State.APOSTROPHE;
|
||||
} else if (ch == '{') {
|
||||
state = State.PLACEHOLDER;
|
||||
idBuilder.reset();
|
||||
} else {
|
||||
newPattern.append(ch);
|
||||
}
|
||||
break;
|
||||
case APOSTROPHE:
|
||||
if (ch == 0x27) {
|
||||
newPattern.append("'");
|
||||
} else if (ch == '{') {
|
||||
newPattern.append("{");
|
||||
} else {
|
||||
newPattern.append("'");
|
||||
newPattern.append(ch);
|
||||
}
|
||||
state = State.INIT;
|
||||
break;
|
||||
case PLACEHOLDER:
|
||||
if (ch >= '0' && ch <= '9') {
|
||||
idBuilder.add(ch);
|
||||
} else if (ch == '}' && idBuilder.isValid()) {
|
||||
placeholdersBuilder.add(idBuilder.getId(), newPattern.length());
|
||||
state = State.INIT;
|
||||
} else {
|
||||
newPattern.append('{');
|
||||
idBuilder.appendTo(newPattern);
|
||||
newPattern.append(ch);
|
||||
state = State.INIT;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
switch (state) {
|
||||
case INIT:
|
||||
break;
|
||||
case APOSTROPHE:
|
||||
newPattern.append("'");
|
||||
break;
|
||||
case PLACEHOLDER:
|
||||
newPattern.append('{');
|
||||
idBuilder.appendTo(newPattern);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
return new Template(newPattern.toString(), placeholdersBuilder);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates this template with given values. The first value
|
||||
* corresponds to {0}; the second to {1} etc.
|
||||
* @param values the values.
|
||||
* @return The result.
|
||||
* @throws IllegalArgumentException if the number of arguments is
|
||||
* insufficient to match all the placeholders.
|
||||
*/
|
||||
public String evaluate(Object... values) {
|
||||
StringResultBuilder builder = new StringResultBuilder();
|
||||
evaluatePrivate(values, builder);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates this template with given values. The first value
|
||||
* corresponds to {0}; the second to {1} etc.
|
||||
* @param values the values.
|
||||
* @return The result of the evaluation.
|
||||
* @throws IllegalArgumentException if the number of arguments is
|
||||
* insufficient to match all the placeholders.
|
||||
*/
|
||||
public Evaluation evaluateFull(Object... values) {
|
||||
EvaluationResultBuilder builder = new EvaluationResultBuilder();
|
||||
evaluatePrivate(values, builder);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the max placeholder ID + 1.
|
||||
*/
|
||||
public int getPlaceholderCount() {
|
||||
return placeholderCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates this template using values {0}, {1} etc. Note that this is
|
||||
* not the same as the original pattern string used to build the template.
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
String[] values = new String[this.getPlaceholderCount()];
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
values[i] = String.format("{%d}", i);
|
||||
}
|
||||
return evaluate((Object[]) values);
|
||||
}
|
||||
|
||||
/**
|
||||
* The immutable evaluation of a template.
|
||||
*/
|
||||
public static class Evaluation {
|
||||
|
||||
private final String result;
|
||||
private final int[] offsets;
|
||||
|
||||
private Evaluation(String result, int[] placeholderOffsets) {
|
||||
this.result = result;
|
||||
this.offsets = placeholderOffsets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the offset of a particular placeholder in the evaluated
|
||||
* string. Returns -1 if the placeholder did not exist in the
|
||||
* corresponding template.
|
||||
* @throws IndexOutOfBoundsException if placeholderId is negative.
|
||||
*/
|
||||
public int getOffset(int placeholderId) {
|
||||
if (placeholderId < 0) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
if (placeholderId >= offsets.length) {
|
||||
return -1;
|
||||
}
|
||||
return offsets[placeholderId];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the evaluated string.
|
||||
*/
|
||||
public String toString() {
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void evaluatePrivate(Object[] values, ResultBuilder builder) {
|
||||
if (values.length < placeholderCount) {
|
||||
throw new IllegalArgumentException(
|
||||
"There must be at least as values as placeholders.");
|
||||
}
|
||||
builder.setPlaceholderCount(placeholderCount);
|
||||
if (placeholderIdsOrderedByOffset.length == 0) {
|
||||
builder.setResult(patternWithoutPlaceholders);
|
||||
return;
|
||||
}
|
||||
StringBuilder result = new StringBuilder();
|
||||
result.append(
|
||||
patternWithoutPlaceholders,
|
||||
0,
|
||||
placeholderIdsOrderedByOffset[0]);
|
||||
builder.setPlaceholderOffset(
|
||||
placeholderIdsOrderedByOffset[1], result.length());
|
||||
result.append(values[placeholderIdsOrderedByOffset[1]]);
|
||||
for (int i = 2; i < placeholderIdsOrderedByOffset.length; i += 2) {
|
||||
result.append(
|
||||
patternWithoutPlaceholders,
|
||||
placeholderIdsOrderedByOffset[i - 2],
|
||||
placeholderIdsOrderedByOffset[i]);
|
||||
builder.setPlaceholderOffset(
|
||||
placeholderIdsOrderedByOffset[i + 1], result.length());
|
||||
result.append(values[placeholderIdsOrderedByOffset[i + 1]]);
|
||||
}
|
||||
result.append(
|
||||
patternWithoutPlaceholders,
|
||||
placeholderIdsOrderedByOffset[placeholderIdsOrderedByOffset.length - 2],
|
||||
patternWithoutPlaceholders.length());
|
||||
builder.setResult(result.toString());
|
||||
}
|
||||
|
||||
private static enum State {
|
||||
INIT,
|
||||
APOSTROPHE,
|
||||
PLACEHOLDER,
|
||||
}
|
||||
|
||||
private static class PlaceholderIdBuilder {
|
||||
private int id = 0;
|
||||
private int idLen = 0;
|
||||
|
||||
public void reset() {
|
||||
id = 0;
|
||||
idLen = 0;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void appendTo(StringBuilder appendTo) {
|
||||
if (idLen > 0) {
|
||||
appendTo.append(id);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
return idLen > 0;
|
||||
}
|
||||
|
||||
public void add(char ch) {
|
||||
id = id * 10 + ch - '0';
|
||||
idLen++;
|
||||
}
|
||||
}
|
||||
|
||||
private static class PlaceholdersBuilder {
|
||||
private List<Integer> placeholderIdsOrderedByOffset = new ArrayList<Integer>();
|
||||
private int placeholderCount = 0;
|
||||
|
||||
public void add(int placeholderId, int offset) {
|
||||
placeholderIdsOrderedByOffset.add(offset);
|
||||
placeholderIdsOrderedByOffset.add(placeholderId);
|
||||
if (placeholderId >= placeholderCount) {
|
||||
placeholderCount = placeholderId + 1;
|
||||
}
|
||||
}
|
||||
|
||||
public int getPlaceholderCount() {
|
||||
return placeholderCount;
|
||||
}
|
||||
|
||||
public int[] getPlaceholderIdsOrderedByOffset() {
|
||||
int[] result = new int[placeholderIdsOrderedByOffset.size()];
|
||||
for (int i = 0; i < result.length; i++) {
|
||||
result[i] = placeholderIdsOrderedByOffset.get(i).intValue();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private static interface ResultBuilder {
|
||||
void setPlaceholderCount(int length);
|
||||
void setPlaceholderOffset(int i, int length);
|
||||
void setResult(String patternWithoutPlaceholders);
|
||||
}
|
||||
|
||||
private static class StringResultBuilder implements ResultBuilder {
|
||||
|
||||
private String result;
|
||||
|
||||
public void setPlaceholderCount(int count) {
|
||||
}
|
||||
|
||||
public void setPlaceholderOffset(int placeholderId, int offset) {
|
||||
}
|
||||
|
||||
public void setResult(String result) {
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
public String build() {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private static class EvaluationResultBuilder implements ResultBuilder {
|
||||
private int[] placeholderOffsets;
|
||||
private String result;
|
||||
|
||||
public void setPlaceholderCount(int count) {
|
||||
placeholderOffsets = new int[count];
|
||||
for (int i = 0; i < count; i++) {
|
||||
placeholderOffsets[i] = -1;
|
||||
}
|
||||
}
|
||||
|
||||
public void setPlaceholderOffset(int placeholderId, int offset) {
|
||||
placeholderOffsets[placeholderId] = offset;
|
||||
}
|
||||
|
||||
public void setResult(String result) {
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
public Evaluation build() {
|
||||
return new Evaluation(this.result, this.placeholderOffsets);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -18,6 +18,7 @@ import java.util.MissingResourceException;
|
||||
import com.ibm.icu.impl.ICUCache;
|
||||
import com.ibm.icu.impl.ICUResourceBundle;
|
||||
import com.ibm.icu.impl.SimpleCache;
|
||||
import com.ibm.icu.impl.Template;
|
||||
import com.ibm.icu.util.ULocale;
|
||||
import com.ibm.icu.util.UResourceBundle;
|
||||
|
||||
@ -30,10 +31,10 @@ import com.ibm.icu.util.UResourceBundle;
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
*/
|
||||
final public class ListFormatter {
|
||||
private final String two;
|
||||
private final String start;
|
||||
private final String middle;
|
||||
private final String end;
|
||||
private final Template two;
|
||||
private final Template start;
|
||||
private final Template middle;
|
||||
private final Template end;
|
||||
private final ULocale locale;
|
||||
|
||||
/**
|
||||
@ -102,10 +103,16 @@ final public class ListFormatter {
|
||||
* @deprecated This API is ICU internal only.
|
||||
*/
|
||||
public ListFormatter(String two, String start, String middle, String end) {
|
||||
this(two, start, middle, end, null);
|
||||
this(
|
||||
Template.compile(two),
|
||||
Template.compile(start),
|
||||
Template.compile(middle),
|
||||
Template.compile(end),
|
||||
null);
|
||||
|
||||
}
|
||||
|
||||
private ListFormatter(String two, String start, String middle, String end, ULocale locale) {
|
||||
private ListFormatter(Template two, Template start, Template middle, Template end, ULocale locale) {
|
||||
this.two = two;
|
||||
this.start = start;
|
||||
this.middle = middle;
|
||||
@ -189,22 +196,30 @@ final public class ListFormatter {
|
||||
// TODO optimize this for the common case that the patterns are all of the
|
||||
// form {0}<sometext>{1}.
|
||||
// We avoid MessageFormat, because there is no "sub" formatting.
|
||||
return format(items, -1).toString();
|
||||
}
|
||||
|
||||
// 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) {
|
||||
Iterator<?> it = items.iterator();
|
||||
int count = items.size();
|
||||
switch (count) {
|
||||
case 0:
|
||||
return "";
|
||||
return new FormattedListBuilder("", false);
|
||||
case 1:
|
||||
return it.next().toString();
|
||||
return new FormattedListBuilder(it.next(), index == 0);
|
||||
case 2:
|
||||
return format2(two, it.next(), it.next());
|
||||
return new FormattedListBuilder(it.next(), index == 0).append(two, it.next(), index == 1);
|
||||
}
|
||||
String result = it.next().toString();
|
||||
result = format2(start, result, it.next());
|
||||
for (count -= 3; count > 0; --count) {
|
||||
result = format2(middle, result, it.next());
|
||||
FormattedListBuilder builder = new FormattedListBuilder(it.next(), index == 0);
|
||||
builder.append(start, it.next(), index == 1);
|
||||
for (int idx = 2; idx < count - 1; ++idx) {
|
||||
builder.append(middle, it.next(), index == idx);
|
||||
}
|
||||
return format2(end, result, it.next());
|
||||
return builder.append(end, it.next(), index == count - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -236,16 +251,57 @@ final public class ListFormatter {
|
||||
return locale;
|
||||
}
|
||||
|
||||
private String format2(String pattern, Object a, Object b) {
|
||||
int i0 = pattern.indexOf("{0}");
|
||||
int i1 = pattern.indexOf("{1}");
|
||||
if (i0 < 0 || i1 < 0) {
|
||||
throw new IllegalArgumentException("Missing {0} or {1} in pattern " + pattern);
|
||||
// Builds a formatted list
|
||||
static class FormattedListBuilder {
|
||||
private String 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 = start.toString();
|
||||
this.offset = recordOffset ? 0 : -1;
|
||||
}
|
||||
if (i0 < i1) {
|
||||
return pattern.substring(0, i0) + a + pattern.substring(i0+3, i1) + b + pattern.substring(i1+3);
|
||||
} else {
|
||||
return pattern.substring(0, i1) + b + pattern.substring(i1+3, i0) + a + pattern.substring(i0+3);
|
||||
|
||||
// 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(Template pattern, Object next, boolean recordOffset) {
|
||||
if (pattern.getPlaceholderCount() != 2) {
|
||||
throw new IllegalArgumentException("Need {0} and {1} only in pattern " + pattern);
|
||||
}
|
||||
if (recordOffset || offsetRecorded()) {
|
||||
Template.Evaluation evaluation = pattern.evaluateFull(current, next);
|
||||
int oneOffset = evaluation.getOffset(1);
|
||||
int zeroOffset = evaluation.getOffset(0);
|
||||
if (zeroOffset == -1 || oneOffset == -1) {
|
||||
throw new IllegalArgumentException("{0} or {1} missing from pattern " + pattern);
|
||||
}
|
||||
if (recordOffset) {
|
||||
offset = oneOffset;
|
||||
} else {
|
||||
offset += zeroOffset;
|
||||
}
|
||||
current = evaluation.toString();
|
||||
} else {
|
||||
current = pattern.evaluate(current, next);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return current;
|
||||
}
|
||||
|
||||
// Gets the last recorded offset or -1 if no offset recorded.
|
||||
public int getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
private boolean offsetRecorded() {
|
||||
return offset >= 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -280,17 +336,17 @@ final public class ListFormatter {
|
||||
// for listPattern/duration and listPattern/duration-narrow in root.txt.
|
||||
try {
|
||||
return new ListFormatter(
|
||||
r.getWithFallback("listPattern/" + style + "/2").getString(),
|
||||
r.getWithFallback("listPattern/" + style + "/start").getString(),
|
||||
r.getWithFallback("listPattern/" + style + "/middle").getString(),
|
||||
r.getWithFallback("listPattern/" + style + "/end").getString(),
|
||||
Template.compile(r.getWithFallback("listPattern/" + style + "/2").getString()),
|
||||
Template.compile(r.getWithFallback("listPattern/" + style + "/start").getString()),
|
||||
Template.compile(r.getWithFallback("listPattern/" + style + "/middle").getString()),
|
||||
Template.compile(r.getWithFallback("listPattern/" + style + "/end").getString()),
|
||||
ulocale);
|
||||
} catch (MissingResourceException e) {
|
||||
return new ListFormatter(
|
||||
r.getWithFallback("listPattern/standard/2").getString(),
|
||||
r.getWithFallback("listPattern/standard/start").getString(),
|
||||
r.getWithFallback("listPattern/standard/middle").getString(),
|
||||
r.getWithFallback("listPattern/standard/end").getString(),
|
||||
Template.compile(r.getWithFallback("listPattern/standard/2").getString()),
|
||||
Template.compile(r.getWithFallback("listPattern/standard/start").getString()),
|
||||
Template.compile(r.getWithFallback("listPattern/standard/middle").getString()),
|
||||
Template.compile(r.getWithFallback("listPattern/standard/end").getString()),
|
||||
ulocale);
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ import java.io.ObjectStreamException;
|
||||
import java.text.AttributedCharacterIterator;
|
||||
import java.text.FieldPosition;
|
||||
import java.text.ParsePosition;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.EnumMap;
|
||||
@ -346,7 +347,6 @@ public class MeasureFormat extends UFormat {
|
||||
* @draft ICU 53
|
||||
* @provisional
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends Appendable> T formatMeasures(
|
||||
T appendable, FieldPosition fieldPosition, Measure... measures) {
|
||||
// fast track for trivial cases
|
||||
@ -368,26 +368,16 @@ public class MeasureFormat extends UFormat {
|
||||
|
||||
ListFormatter listFormatter = ListFormatter.getInstance(
|
||||
getLocale(), formatWidth.getListFormatterStyle());
|
||||
String[] results = null;
|
||||
if (fieldPosition == DontCareFieldPosition.INSTANCE) {
|
||||
|
||||
// Fast track: No field position.
|
||||
results = new String[measures.length];
|
||||
for (int i = 0; i < measures.length; i++) {
|
||||
results[i] = formatMeasure(measures[i]);
|
||||
}
|
||||
} else {
|
||||
|
||||
// Slow track: Have to calculate field position.
|
||||
results = formatMeasuresSlowTrack(listFormatter, fieldPosition, measures);
|
||||
if (fieldPosition != DontCareFieldPosition.INSTANCE) {
|
||||
return append(formatMeasuresSlowTrack(listFormatter, fieldPosition, measures), appendable);
|
||||
}
|
||||
|
||||
// This is safe because appendable is of type T.
|
||||
try {
|
||||
return (T) appendable.append(listFormatter.format((Object[]) results));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
// Fast track: No field position.
|
||||
String[] results = new String[measures.length];
|
||||
for (int i = 0; i < measures.length; i++) {
|
||||
results[i] = formatMeasure(measures[i]);
|
||||
}
|
||||
return append(listFormatter.format((Object[]) results), appendable);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -633,16 +623,13 @@ public class MeasureFormat extends UFormat {
|
||||
private <T extends Appendable> T formatMeasure(
|
||||
Measure measure, T appendable, FieldPosition fieldPosition) {
|
||||
if (measure.getUnit() instanceof Currency) {
|
||||
try {
|
||||
appendable.append(
|
||||
currencyFormat.format(
|
||||
new CurrencyAmount(measure.getNumber(), (Currency) measure.getUnit()),
|
||||
new StringBuffer(),
|
||||
fieldPosition));
|
||||
return appendable;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return append(
|
||||
currencyFormat.format(
|
||||
new CurrencyAmount(measure.getNumber(), (Currency) measure.getUnit()),
|
||||
new StringBuffer(),
|
||||
fieldPosition),
|
||||
appendable);
|
||||
|
||||
}
|
||||
Number n = measure.getNumber();
|
||||
MeasureUnit unit = measure.getUnit();
|
||||
@ -653,21 +640,17 @@ public class MeasureFormat extends UFormat {
|
||||
Map<FormatWidth, Map<String, PatternData>> styleToCountToFormat = unitToStyleToCountToFormat.get(unit);
|
||||
Map<String, PatternData> countToFormat = styleToCountToFormat.get(formatWidth);
|
||||
PatternData messagePatternData = countToFormat.get(keyword);
|
||||
try {
|
||||
appendable.append(messagePatternData.prefix);
|
||||
if (messagePatternData.suffix != null) { // there is a number (may not happen with, say, Arabic dual)
|
||||
// Fix field position
|
||||
if (fpos.getBeginIndex() != 0 || fpos.getEndIndex() != 0) {
|
||||
fieldPosition.setBeginIndex(fpos.getBeginIndex() + messagePatternData.prefix.length());
|
||||
fieldPosition.setEndIndex(fpos.getEndIndex() + messagePatternData.prefix.length());
|
||||
}
|
||||
appendable.append(formattedNumber);
|
||||
appendable.append(messagePatternData.suffix);
|
||||
append(messagePatternData.prefix, appendable);
|
||||
if (messagePatternData.suffix != null) { // there is a number (may not happen with, say, Arabic dual)
|
||||
// Fix field position
|
||||
if (fpos.getBeginIndex() != 0 || fpos.getEndIndex() != 0) {
|
||||
fieldPosition.setBeginIndex(fpos.getBeginIndex() + messagePatternData.prefix.length());
|
||||
fieldPosition.setEndIndex(fpos.getEndIndex() + messagePatternData.prefix.length());
|
||||
}
|
||||
return appendable;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
append(formattedNumber, appendable);
|
||||
append(messagePatternData.suffix, appendable);
|
||||
}
|
||||
return appendable;
|
||||
}
|
||||
|
||||
// Wrapper around NumberFormat that provides immutability and thread-safety.
|
||||
@ -724,7 +707,7 @@ public class MeasureFormat extends UFormat {
|
||||
return new MeasureProxy(getLocale(), formatWidth, numberFormat.get(), CURRENCY_FORMAT);
|
||||
}
|
||||
|
||||
private String[] formatMeasuresSlowTrack(ListFormatter listFormatter, FieldPosition fieldPosition,
|
||||
private String formatMeasuresSlowTrack(ListFormatter listFormatter, FieldPosition fieldPosition,
|
||||
Measure... measures) {
|
||||
String[] results = new String[measures.length];
|
||||
|
||||
@ -742,23 +725,15 @@ public class MeasureFormat extends UFormat {
|
||||
results[i] = formatMeasure(measures[i]);
|
||||
}
|
||||
}
|
||||
ListFormatter.FormattedListBuilder builder =
|
||||
listFormatter.format(Arrays.asList(results), fieldPositionFoundIndex);
|
||||
|
||||
// Fix up FieldPosition indexes if our field is found.
|
||||
if (fieldPositionFoundIndex != -1) {
|
||||
String listPattern = listFormatter.getPatternForNumItems(measures.length);
|
||||
int positionInPattern = listPattern.indexOf("{" + fieldPositionFoundIndex + "}");
|
||||
if (positionInPattern == -1) {
|
||||
throw new IllegalStateException("Can't find position with ListFormatter.");
|
||||
}
|
||||
// Now we have to adjust our position in pattern
|
||||
// based on the previous values.
|
||||
for (int i = 0; i < fieldPositionFoundIndex; i++) {
|
||||
positionInPattern += (results[i].length() - ("{" + i + "}").length());
|
||||
}
|
||||
fieldPosition.setBeginIndex(fpos.getBeginIndex() + positionInPattern);
|
||||
fieldPosition.setEndIndex(fpos.getEndIndex() + positionInPattern);
|
||||
if (builder.getOffset() != -1) {
|
||||
fieldPosition.setBeginIndex(fpos.getBeginIndex() + builder.getOffset());
|
||||
fieldPosition.setEndIndex(fpos.getEndIndex() + builder.getOffset());
|
||||
}
|
||||
return results;
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
// type is one of "hm", "ms" or "hms"
|
||||
@ -853,21 +828,26 @@ public class MeasureFormat extends UFormat {
|
||||
// When we get to the smallest amount, skip over it and copy
|
||||
// 'smallestAmountFormatted' to the builder instead.
|
||||
for (iterator.first(); iterator.getIndex() < iterator.getEndIndex();) {
|
||||
try {
|
||||
if (iterator.getAttributes().containsKey(smallestField)) {
|
||||
appendable.append(smallestAmountFormatted);
|
||||
iterator.setIndex(iterator.getRunLimit(smallestField));
|
||||
} else {
|
||||
appendable.append(iterator.current());
|
||||
iterator.next();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
if (iterator.getAttributes().containsKey(smallestField)) {
|
||||
append(smallestAmountFormatted, appendable);
|
||||
iterator.setIndex(iterator.getRunLimit(smallestField));
|
||||
} else {
|
||||
append(iterator.current(), appendable);
|
||||
iterator.next();
|
||||
}
|
||||
}
|
||||
return appendable;
|
||||
}
|
||||
|
||||
private static <T extends Appendable> T append(Object o, T appendable) {
|
||||
try {
|
||||
appendable.append(o.toString());
|
||||
return appendable;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private Object writeReplace() throws ObjectStreamException {
|
||||
return new MeasureProxy(
|
||||
getLocale(), formatWidth, numberFormat.get(), MEASURE_FORMAT);
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
*******************************************************************************
|
||||
* Copyright (C) 2013, International Business Machines Corporation and *
|
||||
* Copyright (C) 2013-2014, International Business Machines Corporation and *
|
||||
* others. All Rights Reserved. *
|
||||
*******************************************************************************
|
||||
*/
|
||||
@ -9,6 +9,8 @@ package com.ibm.icu.text;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.ibm.icu.impl.Template;
|
||||
|
||||
/**
|
||||
* QuantityFormatter represents an unknown quantity of something and formats a known quantity
|
||||
* in terms of that something. For example, a QuantityFormatter that represents X apples may
|
||||
@ -25,6 +27,7 @@ class QuantityFormatter {
|
||||
|
||||
static {
|
||||
int idx = 0;
|
||||
// Other must be first.
|
||||
INDEX_MAP.put("other", idx++);
|
||||
INDEX_MAP.put("zero", idx++);
|
||||
INDEX_MAP.put("one", idx++);
|
||||
@ -42,7 +45,7 @@ class QuantityFormatter {
|
||||
*/
|
||||
static class Builder {
|
||||
|
||||
private String[] templates;
|
||||
private Template[] templates;
|
||||
|
||||
/**
|
||||
* Adds a template.
|
||||
@ -51,16 +54,27 @@ class QuantityFormatter {
|
||||
* example, in English, the template for the "one" variant may be "{0} apple" while the
|
||||
* template for the "other" variant may be "{0} apples"
|
||||
* @return a reference to this Builder for chaining.
|
||||
* @throws IllegalArgumentException if variant is not recognized or
|
||||
* if template has more than just the {0} placeholder.
|
||||
*/
|
||||
public Builder add(String variant, String template) {
|
||||
ensureCapacity();
|
||||
templates[INDEX_MAP.get(variant)] = template;
|
||||
Integer idx = INDEX_MAP.get(variant);
|
||||
if (idx == null) {
|
||||
throw new IllegalArgumentException(variant);
|
||||
}
|
||||
Template newT = Template.compile(template);
|
||||
if (newT.getPlaceholderCount() > 1) {
|
||||
throw new IllegalArgumentException(
|
||||
"Extra placeholders: " + template);
|
||||
}
|
||||
templates[idx.intValue()] = newT;
|
||||
return this;
|
||||
}
|
||||
|
||||
private void ensureCapacity() {
|
||||
if (templates == null) {
|
||||
templates = new String[MAX_INDEX];
|
||||
templates = new Template[MAX_INDEX];
|
||||
}
|
||||
}
|
||||
|
||||
@ -83,9 +97,9 @@ class QuantityFormatter {
|
||||
|
||||
}
|
||||
|
||||
private final String[] templates;
|
||||
private final Template[] templates;
|
||||
|
||||
private QuantityFormatter(String[] templates) {
|
||||
private QuantityFormatter(Template[] templates) {
|
||||
this.templates = templates;
|
||||
}
|
||||
|
||||
@ -105,11 +119,12 @@ class QuantityFormatter {
|
||||
} else {
|
||||
variant = pluralRules.select(quantity);
|
||||
}
|
||||
return getByVariant(variant).replace("{0}", formatStr);
|
||||
return getByVariant(variant).evaluate(formatStr);
|
||||
}
|
||||
|
||||
private String getByVariant(String variant) {
|
||||
String template = templates[INDEX_MAP.get(variant)];
|
||||
private Template getByVariant(String variant) {
|
||||
Integer idxObj = INDEX_MAP.get(variant);
|
||||
Template template = templates[idxObj == null ? 0 : idxObj.intValue()];
|
||||
return template == null ? templates[0] : template;
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
*******************************************************************************
|
||||
* Copyright (C) 1996-2007, International Business Machines Corporation and *
|
||||
* Copyright (C) 1996-2014, International Business Machines Corporation and *
|
||||
* others. All Rights Reserved. *
|
||||
*******************************************************************************
|
||||
*/
|
||||
@ -22,6 +22,7 @@ public class TestAll extends TestGroup {
|
||||
"ICUServiceTest",
|
||||
"ICUServiceThreadTest",
|
||||
"ICUBinaryTest",
|
||||
"TemplateTest",
|
||||
"TextTrieMapTest"
|
||||
},
|
||||
"Test miscellaneous implementation utilities");
|
||||
|
@ -0,0 +1,112 @@
|
||||
/*
|
||||
*******************************************************************************
|
||||
* Copyright (C) 2014, International Business Machines Corporation and *
|
||||
* others. All Rights Reserved. *
|
||||
*******************************************************************************
|
||||
*/
|
||||
package com.ibm.icu.dev.test.util;
|
||||
|
||||
import com.ibm.icu.dev.test.TestFmwk;
|
||||
import com.ibm.icu.impl.Template;
|
||||
|
||||
/**
|
||||
* @author rocketman
|
||||
*
|
||||
*/
|
||||
public class TemplateTest extends TestFmwk {
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public TemplateTest()
|
||||
{
|
||||
}
|
||||
|
||||
// public methods -----------------------------------------------
|
||||
|
||||
public static void main(String arg[])
|
||||
{
|
||||
TemplateTest test = new TemplateTest();
|
||||
try {
|
||||
test.run(arg);
|
||||
} catch (Exception e) {
|
||||
test.errln("Error testing templatetest");
|
||||
}
|
||||
}
|
||||
|
||||
public void TestWithNoPlaceholders() {
|
||||
Template t = Template.compile("This doesn''t have templates '{0}");
|
||||
assertEquals(
|
||||
"getPlaceholderCount",
|
||||
0,
|
||||
t.getPlaceholderCount());
|
||||
assertEquals(
|
||||
"evaluate",
|
||||
"This doesn't have templates {0}",
|
||||
t.evaluate());
|
||||
assertEquals(
|
||||
"toString",
|
||||
"This doesn't have templates {0}",
|
||||
t.toString());
|
||||
Template.Evaluation eval = t.evaluateFull();
|
||||
assertEquals(
|
||||
"toString2",
|
||||
"This doesn't have templates {0}",
|
||||
eval.toString());
|
||||
assertEquals(
|
||||
"getOffset(0)",
|
||||
-1,
|
||||
eval.getOffset(0));
|
||||
t = Template.compile("Some {} messed {12d up stuff.");
|
||||
assertEquals(
|
||||
"getPlaceholderCount",
|
||||
0,
|
||||
t.getPlaceholderCount());
|
||||
assertEquals(
|
||||
"evaluate",
|
||||
"Some {} messed {12d up stuff.",
|
||||
t.evaluate("to"));
|
||||
}
|
||||
|
||||
public void TestOnePlaceholder() {
|
||||
assertEquals("TestOnePlaceholder",
|
||||
"1 meter",
|
||||
Template.compile("{0} meter").evaluate(1));
|
||||
}
|
||||
|
||||
public void TestWithPlaceholders() {
|
||||
Template t = Template.compile(
|
||||
"Templates {2}{1} and {4} are out of order.");
|
||||
assertEquals(
|
||||
"getPlaceholderCount",
|
||||
5,
|
||||
t.getPlaceholderCount());
|
||||
try {
|
||||
t.evaluate("freddy", "tommy", "frog", "leg");
|
||||
fail("Expected IllegalArgumentException");
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Expected
|
||||
}
|
||||
assertEquals(
|
||||
"evaluate",
|
||||
"Templates frogtommy and {0} are out of order.",
|
||||
t.evaluate("freddy", "tommy", "frog", "leg", "{0}"));
|
||||
assertEquals(
|
||||
"toString",
|
||||
"Templates {2}{1} and {4} are out of order.",
|
||||
t.toString());
|
||||
Template.Evaluation eval =
|
||||
t.evaluateFull("freddy", "tommy", "frog", "leg", "{0}");
|
||||
int[] offsets = {-1, 14, 10, -1, 24, -1};
|
||||
for (int i = 0; i < offsets.length; i++) {
|
||||
if (offsets[i] != eval.getOffset(i)) {
|
||||
fail("getOffset() returned wrong value for " + i);
|
||||
}
|
||||
}
|
||||
assertEquals(
|
||||
"toString2",
|
||||
"Templates frogtommy and {0} are out of order.",
|
||||
eval.toString());
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user