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
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
--- /dev/null
+/*
+ *******************************************************************************
+ * 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);
+ }
+
+ }
+
+}
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;
* @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;
/**
* @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;
// 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);
}
/**
public ULocale getLocale() {
return locale;
}
+
+ // 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;
+ }
+
+ // 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;
+ }
- 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);
+ @Override
+ public String toString() {
+ return current;
}
- 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);
+
+ // Gets the last recorded offset or -1 if no offset recorded.
+ public int getOffset() {
+ return offset;
+ }
+
+ private boolean offsetRecorded() {
+ return offset >= 0;
}
}
// 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);
}
}
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;
* @draft ICU 53
* @provisional
*/
- @SuppressWarnings("unchecked")
public <T extends Appendable> T formatMeasures(
T appendable, FieldPosition fieldPosition, Measure... measures) {
// fast track for trivial cases
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);
+
}
/**
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();
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.
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];
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"
// 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);
/*
*******************************************************************************
- * Copyright (C) 2013, International Business Machines Corporation and *
+ * Copyright (C) 2013-2014, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
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
static {
int idx = 0;
+ // Other must be first.
INDEX_MAP.put("other", idx++);
INDEX_MAP.put("zero", idx++);
INDEX_MAP.put("one", idx++);
*/
static class Builder {
- private String[] templates;
+ private Template[] templates;
/**
* Adds a template.
* 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];
}
}
}
- private final String[] templates;
+ private final Template[] templates;
- private QuantityFormatter(String[] templates) {
+ private QuantityFormatter(Template[] templates) {
this.templates = templates;
}
} 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;
}
}
/*
*******************************************************************************
- * Copyright (C) 1996-2007, International Business Machines Corporation and *
+ * Copyright (C) 1996-2014, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
"ICUServiceTest",
"ICUServiceThreadTest",
"ICUBinaryTest",
+ "TemplateTest",
"TextTrieMapTest"
},
"Test miscellaneous implementation utilities");
--- /dev/null
+/*
+ *******************************************************************************
+ * 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());
+ }
+
+}