ICU-10896 split SimplePatternFormatter into public SimpleFormatter and all-static SimpleFormatterImpl

X-SVN-Rev: 38194
This commit is contained in:
Markus Scherer 2016-01-23 05:17:18 +00:00
parent b15fd37fa9
commit 00122edbf6
9 changed files with 308 additions and 239 deletions

View file

@ -8,39 +8,21 @@ package com.ibm.icu.impl;
/**
* Formats simple patterns like "{1} was born in {0}".
* Minimal subset of MessageFormat; fast, simple, minimal dependencies.
* Supports only numbered arguments with no type nor style parameters,
* and formats only string values.
* Quoting via ASCII apostrophe compatible with ICU MessageFormat default behavior.
* Internal version of {@link com.ibm.icu.text.SimpleFormatter}
* with only static methods, to avoid wrapper objects.
*
* <p>Factory methods throw exceptions for syntax errors
* and for too few or too many arguments/placeholders.
* <p>This class "compiles" pattern strings into a binary format
* and implements formatting etc. based on that.
*
* <p>SimplePatternFormatter objects are immutable and can be safely cached like strings.
* <p>Format:
* Index 0: One more than the highest argument number.
* Followed by zero or more arguments or literal-text segments.
*
* <p>Example:
* <pre>
* SimplePatternFormatter fmt = SimplePatternFormatter.compile("{1} '{born}' in {0}");
*
* // Output: "paul {born} in england"
* System.out.println(fmt.format("england", "paul"));
* </pre>
*
* @see com.ibm.icu.text.MessageFormat
* @see com.ibm.icu.text.MessagePattern.ApostropheMode
* <p>An argument is stored as its number, less than ARG_NUM_LIMIT.
* A literal-text segment is stored as its length (at least 1) offset by ARG_NUM_LIMIT,
* followed by that many chars.
*/
public final class SimplePatternFormatter {
// For internal use in Java,
// it is most efficient to compile patterns to compiled-pattern strings
// and use them with static methods.
//
// If and when we make this public API,
// we should probably make only the non-static methods public
// and keep the static ones in an impl class.
//
// TODO: Consider changing methods & docs to use "argument" not "placeholder",
// consistent with MessageFormat.
public final class SimpleFormatterImpl {
/**
* Argument numbers must be smaller than this limit.
* Text segment lengths are offset by this much.
@ -48,66 +30,53 @@ public final class SimplePatternFormatter {
* except it is the maximum value of the first unit (max arg +1).
*/
private static final int ARG_NUM_LIMIT = 0x100;
private static final char LEN1_CHAR = (char)(ARG_NUM_LIMIT + 1);
private static final char LEN2_CHAR = (char)(ARG_NUM_LIMIT + 2);
private static final char LEN3_CHAR = (char)(ARG_NUM_LIMIT + 3);
/**
* Initial and maximum char/UChar value set for a text segment.
* Segment length char values are from ARG_NUM_LIMIT+1 to this value here.
* Normally 0xffff, but can be as small as ARG_NUM_LIMIT+1 for testing.
*/
private static final char SEGMENT_LENGTH_PLACEHOLDER_CHAR = (char)0xffff;
private static final char SEGMENT_LENGTH_ARGUMENT_CHAR = (char)0xffff;
/**
* Maximum length of a text segment. Longer segments are split into shorter ones.
*/
private static final int MAX_SEGMENT_LENGTH = SEGMENT_LENGTH_PLACEHOLDER_CHAR - ARG_NUM_LIMIT;
private static final int MAX_SEGMENT_LENGTH = SEGMENT_LENGTH_ARGUMENT_CHAR - ARG_NUM_LIMIT;
/**
* Binary representation of the compiled pattern.
* Index 0: One more than the highest argument number.
* Followed by zero or more arguments or literal-text segments.
*
* <p>An argument is stored as its number, less than ARG_NUM_LIMIT.
* A literal-text segment is stored as its length (at least 1) offset by ARG_NUM_LIMIT,
* followed by that many chars.
*/
private final String compiledPattern;
/** "Intern" some common patterns. */
private static final String[][] COMMON_PATTERNS = {
{ "{0} {1}", "\u0002\u0000" + LEN1_CHAR + " \u0001" },
{ "{0} ({1})", "\u0002\u0000" + LEN2_CHAR + " (\u0001" + LEN1_CHAR + ')' },
{ "{0}, {1}", "\u0002\u0000" + LEN2_CHAR + ", \u0001" },
{ "{0} {1}", "\u0002\u0000" + LEN3_CHAR + " \u0001" }, // en dash
};
private SimplePatternFormatter(String compiledPattern) {
this.compiledPattern = compiledPattern;
}
/**
* Creates a formatter from the pattern string.
*
* @param pattern The pattern string.
* @return The new SimplePatternFormatter object.
*/
public static SimplePatternFormatter compile(CharSequence pattern) {
return compileMinMaxPlaceholders(pattern, 0, Integer.MAX_VALUE);
}
/**
* Creates a formatter from the pattern string.
*
* @param pattern The pattern string.
* @param min The pattern must have at least this many placeholders.
* @param max The pattern must have at most this many placeholders.
* @return The new SimplePatternFormatter object.
*/
public static SimplePatternFormatter compileMinMaxPlaceholders(CharSequence pattern, int min, int max) {
StringBuilder sb = new StringBuilder();
String compiledPattern = compileToStringMinMaxPlaceholders(pattern, sb, min, max);
return new SimplePatternFormatter(compiledPattern);
}
/** Use only static methods. */
private SimpleFormatterImpl() {}
/**
* Creates a compiled form of the pattern string, for use with appropriate static methods.
* The number of arguments checked against the given limits is the
* highest argument number plus one, not the number of occurrences of arguments.
*
* @param pattern The pattern string.
* @param min The pattern must have at least this many placeholders.
* @param max The pattern must have at most this many placeholders.
* @param min The pattern must have at least this many arguments.
* @param max The pattern must have at most this many arguments.
* @return The compiled-pattern string.
* @throws IllegalArgumentException for bad argument syntax and too few or too many arguments.
*/
public static String compileToStringMinMaxPlaceholders(
public static String compileToStringMinMaxArguments(
CharSequence pattern, StringBuilder sb, int min, int max) {
// Return some precompiled common two-argument patterns.
if (min <= 2 && 2 <= max) {
for (String[] pair : COMMON_PATTERNS) {
if (pair[0].contentEquals(pattern)) {
assert pair[1].charAt(0) == 2;
return pair[1];
}
}
}
// Parse consistent with MessagePattern, but
// - support only simple numbered arguments
// - build a simple binary structure into the result string
@ -177,7 +146,7 @@ public final class SimplePatternFormatter {
// Append c and track the literal-text segment length.
if (textLength == 0) {
// Reserve a char for the length of a new text segment, preset the maximum length.
sb.append(SEGMENT_LENGTH_PLACEHOLDER_CHAR);
sb.append(SEGMENT_LENGTH_ARGUMENT_CHAR);
}
sb.append(c);
if (++textLength == MAX_SEGMENT_LENGTH) {
@ -190,38 +159,24 @@ public final class SimplePatternFormatter {
int argCount = maxArg + 1;
if (argCount < min) {
throw new IllegalArgumentException(
"Fewer than minimum " + min + " placeholders in pattern \"" + pattern + "\"");
"Fewer than minimum " + min + " arguments in pattern \"" + pattern + "\"");
}
if (argCount > max) {
throw new IllegalArgumentException(
"More than maximum " + max + " placeholders in pattern \"" + pattern + "\"");
"More than maximum " + max + " arguments in pattern \"" + pattern + "\"");
}
sb.setCharAt(0, (char)argCount);
return sb.toString();
}
/**
* @return The max argument number/placeholder ID + 1.
*/
public int getPlaceholderCount() {
return getPlaceholderCount(compiledPattern);
}
/**
* @param compiledPattern Compiled form of a pattern string.
* @return The max argument number/placeholder ID + 1.
* @return The max argument number + 1.
*/
public static int getPlaceholderCount(String compiledPattern) {
public static int getArgumentLimit(String compiledPattern) {
return compiledPattern.charAt(0);
}
/**
* Formats the given values.
*/
public String format(CharSequence... values) {
return formatCompiledPattern(compiledPattern, values);
}
/**
* Formats the given values.
*
@ -231,25 +186,6 @@ public final class SimplePatternFormatter {
return formatAndAppend(compiledPattern, new StringBuilder(), null, values).toString();
}
/**
* Formats the given values, appending to the appendTo builder.
*
* @param appendTo Gets the formatted pattern and values appended.
* @param offsets offsets[i] receives the offset of where
* values[i] replaced pattern argument {i}.
* Can be null, or can be shorter or longer than values.
* If there is no {i} in the pattern, then offsets[i] is set to -1.
* @param values The placeholder values.
* A placeholder value must not be the same object as appendTo.
* values.length must be at least getPlaceholderCount().
* Can be null if getPlaceholderCount()==0.
* @return appendTo
*/
public StringBuilder formatAndAppend(
StringBuilder appendTo, int[] offsets, CharSequence... values) {
return formatAndAppend(compiledPattern, appendTo, offsets, values);
}
/**
* Formats the given values, appending to the appendTo builder.
*
@ -259,16 +195,16 @@ public final class SimplePatternFormatter {
* values[i] replaced pattern argument {i}.
* Can be null, or can be shorter or longer than values.
* If there is no {i} in the pattern, then offsets[i] is set to -1.
* @param values The placeholder values.
* A placeholder value must not be the same object as appendTo.
* values.length must be at least getPlaceholderCount().
* Can be null if getPlaceholderCount()==0.
* @param values The argument values.
* An argument value must not be the same object as appendTo.
* values.length must be at least getArgumentLimit().
* Can be null if getArgumentLimit()==0.
* @return appendTo
*/
public static StringBuilder formatAndAppend(
String compiledPattern, StringBuilder appendTo, int[] offsets, CharSequence... values) {
int valuesLength = values != null ? values.length : 0;
if (valuesLength < getPlaceholderCount(compiledPattern)) {
if (valuesLength < getArgumentLimit(compiledPattern)) {
throw new IllegalArgumentException("Too few values.");
}
return format(compiledPattern, values, appendTo, null, true, offsets);
@ -277,27 +213,7 @@ public final class SimplePatternFormatter {
/**
* Formats the given values, replacing the contents of the result builder.
* May optimize by actually appending to the result if it is the same object
* as the initial argument's corresponding value.
*
* @param result Gets its contents replaced by the formatted pattern and values.
* @param offsets offsets[i] receives the offset of where
* values[i] replaced pattern argument {i}.
* Can be null, or can be shorter or longer than values.
* If there is no {i} in the pattern, then offsets[i] is set to -1.
* @param values The placeholder values.
* A placeholder value may be the same object as result.
* values.length must be at least getPlaceholderCount().
* @return result
*/
public StringBuilder formatAndReplace(
StringBuilder result, int[] offsets, CharSequence... values) {
return formatAndReplace(compiledPattern, result, offsets, values);
}
/**
* Formats the given values, replacing the contents of the result builder.
* May optimize by actually appending to the result if it is the same object
* as the initial argument's corresponding value.
* as the value corresponding to the initial argument in the pattern.
*
* @param compiledPattern Compiled form of a pattern string.
* @param result Gets its contents replaced by the formatted pattern and values.
@ -305,15 +221,15 @@ public final class SimplePatternFormatter {
* values[i] replaced pattern argument {i}.
* Can be null, or can be shorter or longer than values.
* If there is no {i} in the pattern, then offsets[i] is set to -1.
* @param values The placeholder values.
* A placeholder value may be the same object as result.
* values.length must be at least getPlaceholderCount().
* @param values The argument values.
* An argument value may be the same object as result.
* values.length must be at least getArgumentLimit().
* @return result
*/
public static StringBuilder formatAndReplace(
String compiledPattern, StringBuilder result, int[] offsets, CharSequence... values) {
int valuesLength = values != null ? values.length : 0;
if (valuesLength < getPlaceholderCount(compiledPattern)) {
if (valuesLength < getArgumentLimit(compiledPattern)) {
throw new IllegalArgumentException("Too few values.");
}
@ -324,7 +240,7 @@ public final class SimplePatternFormatter {
// If any non-initial argument value is the same object as the result,
// then we first copy its contents and use that instead while formatting.
String resultCopy = null;
if (getPlaceholderCount(compiledPattern) > 0) {
if (getArgumentLimit(compiledPattern) > 0) {
for (int i = 1; i < compiledPattern.length();) {
int n = compiledPattern.charAt(i++);
if (n < ARG_NUM_LIMIT) {
@ -347,33 +263,13 @@ public final class SimplePatternFormatter {
}
/**
* Returns a string similar to the original pattern, only for debugging.
*/
@Override
public String toString() {
String[] values = new String[getPlaceholderCount()];
for (int i = 0; i < values.length; i++) {
values[i] = String.format("{%d}", i);
}
return formatAndAppend(new StringBuilder(), null, values).toString();
}
/**
* Returns the pattern text with none of the placeholders.
* Like formatting with all-empty string values.
*/
public String getTextWithNoPlaceholders() {
return getTextWithNoPlaceholders(compiledPattern);
}
/**
* Returns the pattern text with none of the placeholders.
* Returns the pattern text with none of the arguments.
* Like formatting with all-empty string values.
*
* @param compiledPattern Compiled form of a pattern string.
*/
public static String getTextWithNoPlaceholders(String compiledPattern) {
int capacity = compiledPattern.length() - 1 - getPlaceholderCount(compiledPattern);
public static String getTextWithNoArguments(String compiledPattern) {
int capacity = compiledPattern.length() - 1 - getArgumentLimit(compiledPattern);
StringBuilder sb = new StringBuilder(capacity);
for (int i = 1; i < compiledPattern.length();) {
int segmentLength = compiledPattern.charAt(i++) - ARG_NUM_LIMIT;

View file

@ -15,7 +15,7 @@ import java.util.Locale;
import com.ibm.icu.impl.ICUCache;
import com.ibm.icu.impl.ICUResourceBundle;
import com.ibm.icu.impl.SimpleCache;
import com.ibm.icu.impl.SimplePatternFormatter;
import com.ibm.icu.impl.SimpleFormatterImpl;
import com.ibm.icu.util.ULocale;
import com.ibm.icu.util.UResourceBundle;
@ -27,7 +27,7 @@ import com.ibm.icu.util.UResourceBundle;
* @stable ICU 50
*/
final public class ListFormatter {
// Compiled SimplePatternFormatter patterns.
// Compiled SimpleFormatter patterns.
private final String two;
private final String start;
private final String middle;
@ -124,7 +124,7 @@ final public class ListFormatter {
}
private static String compilePattern(String pattern, StringBuilder sb) {
return SimplePatternFormatter.compileToStringMinMaxPlaceholders(pattern, sb, 2, 2);
return SimpleFormatterImpl.compileToStringMinMaxArguments(pattern, sb, 2, 2);
}
/**
@ -269,7 +269,7 @@ final public class ListFormatter {
// 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;
SimplePatternFormatter.formatAndReplace(
SimpleFormatterImpl.formatAndReplace(
pattern, current, offsets, current, next.toString());
if (offsets != null) {
if (offsets[0] == -1 || offsets[1] == -1) {

View file

@ -32,7 +32,7 @@ import com.ibm.icu.impl.DontCareFieldPosition;
import com.ibm.icu.impl.ICUData;
import com.ibm.icu.impl.ICUResourceBundle;
import com.ibm.icu.impl.SimpleCache;
import com.ibm.icu.impl.SimplePatternFormatter;
import com.ibm.icu.impl.SimpleFormatterImpl;
import com.ibm.icu.impl.StandardPlural;
import com.ibm.icu.impl.UResource;
import com.ibm.icu.math.BigDecimal;
@ -427,7 +427,8 @@ public class MeasureFormat extends UFormat {
StandardPlural.fromString(keywordHigh));
String rangeFormatter = getRangeFormat(getLocale(), formatWidth);
String formattedNumber = SimplePatternFormatter.formatCompiledPattern(rangeFormatter, lowFormatted, highFormatted);
String formattedNumber = SimpleFormatterImpl.formatCompiledPattern(
rangeFormatter, lowFormatted, highFormatted);
if (isCurrency) {
// Nasty hack
@ -451,7 +452,7 @@ public class MeasureFormat extends UFormat {
} else {
String formatter =
getPluralFormatter(lowValue.getUnit(), formatWidth, resolvedPlural.ordinal());
return SimplePatternFormatter.formatCompiledPattern(formatter, formattedNumber);
return SimpleFormatterImpl.formatCompiledPattern(formatter, formattedNumber);
}
}
@ -766,7 +767,7 @@ public class MeasureFormat extends UFormat {
}
}
if (patterns[index] == null) {
patterns[index] = SimplePatternFormatter.compileToStringMinMaxPlaceholders(
patterns[index] = SimpleFormatterImpl.compileToStringMinMaxArguments(
value.getString(), sb, minPlaceholders, 1);
}
}
@ -813,7 +814,7 @@ public class MeasureFormat extends UFormat {
public void put(UResource.Key key, UResource.Value value) {
if (key.contentEquals("per")) {
cacheData.styleToPerPattern.put(width,
SimplePatternFormatter.compileToStringMinMaxPlaceholders(
SimpleFormatterImpl.compileToStringMinMaxArguments(
value.getString(), sb, 2, 2));
}
}
@ -996,13 +997,13 @@ public class MeasureFormat extends UFormat {
String perUnitPattern =
getFormatterOrNull(perUnit, formatWidth, MeasureFormatData.PER_UNIT_INDEX);
if (perUnitPattern != null) {
SimplePatternFormatter.formatAndAppend(perUnitPattern, appendTo, offsets, formatted);
SimpleFormatterImpl.formatAndAppend(perUnitPattern, appendTo, offsets, formatted);
return offsets[0];
}
String perPattern = getPerFormatter(formatWidth);
String pattern = getPluralFormatter(perUnit, formatWidth, StandardPlural.ONE.ordinal());
String perUnitString = SimplePatternFormatter.getTextWithNoPlaceholders(pattern).trim();
SimplePatternFormatter.formatAndAppend(
String perUnitString = SimpleFormatterImpl.getTextWithNoArguments(pattern).trim();
SimpleFormatterImpl.formatAndAppend(
perPattern, appendTo, offsets, formatted, perUnitString);
return offsets[0];
}
@ -1414,7 +1415,7 @@ public class MeasureFormat extends UFormat {
new ConcurrentHashMap<ULocale, String>();
/**
* Return a formatter (compiled SimplePatternFormatter pattern) for a range, such as "{0}{1}".
* Return a formatter (compiled SimpleFormatter pattern) for a range, such as "{0}{1}".
* @param forLocale locale to get the format for
* @param width the format width
* @return range formatter, such as "{0}{1}"
@ -1449,7 +1450,8 @@ public class MeasureFormat extends UFormat {
} catch ( MissingResourceException ex ) {
resultString = rb.getStringWithFallback("NumberElements/latn/patterns/range");
}
result = SimplePatternFormatter.compileToStringMinMaxPlaceholders(resultString, new StringBuilder(), 2, 2);
result = SimpleFormatterImpl.compileToStringMinMaxArguments(
resultString, new StringBuilder(), 2, 2);
localeIdToRangeFormat.put(forLocale, result);
if (!forLocale.equals(realLocale)) {
localeIdToRangeFormat.put(realLocale, result);

View file

@ -8,9 +8,10 @@ package com.ibm.icu.text;
import java.text.FieldPosition;
import com.ibm.icu.impl.SimplePatternFormatter;
import com.ibm.icu.impl.SimpleFormatterImpl;
import com.ibm.icu.impl.StandardPlural;
import com.ibm.icu.text.PluralRules.FixedDecimal;
import com.ibm.icu.text.SimpleFormatter;
/**
* QuantityFormatter represents an unknown quantity of something and formats a known quantity
@ -21,8 +22,8 @@ import com.ibm.icu.text.PluralRules.FixedDecimal;
* PluralRules and DecimalFormat. It is package-protected as it is not meant for public use.
*/
class QuantityFormatter {
private final SimplePatternFormatter[] templates =
new SimplePatternFormatter[StandardPlural.COUNT];
private final SimpleFormatter[] templates =
new SimpleFormatter[StandardPlural.COUNT];
public QuantityFormatter() {}
@ -41,7 +42,7 @@ class QuantityFormatter {
if (templates[idx] != null) {
return;
}
templates[idx] = SimplePatternFormatter.compileMinMaxPlaceholders(template, 0, 1);
templates[idx] = SimpleFormatter.compileMinMaxArguments(template, 0, 1);
}
/**
@ -62,7 +63,7 @@ class QuantityFormatter {
public String format(double number, NumberFormat numberFormat, PluralRules pluralRules) {
String formatStr = numberFormat.format(number);
StandardPlural p = selectPlural(number, numberFormat, pluralRules);
SimplePatternFormatter formatter = templates[p.ordinal()];
SimpleFormatter formatter = templates[p.ordinal()];
if (formatter == null) {
formatter = templates[StandardPlural.OTHER_INDEX];
assert formatter != null;
@ -71,20 +72,20 @@ class QuantityFormatter {
}
/**
* Gets the SimplePatternFormatter for a particular variant.
* Gets the SimpleFormatter for a particular variant.
* @param variant "zero", "one", "two", "few", "many", "other"
* @return the SimplePatternFormatter
* @return the SimpleFormatter
*/
public SimplePatternFormatter getByVariant(CharSequence variant) {
public SimpleFormatter getByVariant(CharSequence variant) {
assert isValid();
int idx = StandardPlural.indexOrOtherIndexFromString(variant);
SimplePatternFormatter template = templates[idx];
SimpleFormatter template = templates[idx];
return (template == null && idx != StandardPlural.OTHER_INDEX) ?
templates[StandardPlural.OTHER_INDEX] : template;
}
// The following methods live here so that class PluralRules does not depend on number formatting,
// and the SimplePatternFormatter does not depend on FieldPosition.
// and the SimpleFormatter does not depend on FieldPosition.
/**
* Selects the standard plural form for the number/formatter/rules.
@ -123,7 +124,7 @@ class QuantityFormatter {
public static StringBuilder format(String compiledPattern, CharSequence value,
StringBuilder appendTo, FieldPosition pos) {
int[] offsets = new int[1];
SimplePatternFormatter.formatAndAppend(compiledPattern, appendTo, offsets, value);
SimpleFormatterImpl.formatAndAppend(compiledPattern, appendTo, offsets, value);
if (pos.getBeginIndex() != 0 || pos.getEndIndex() != 0) {
if (offsets[0] >= 0) {
pos.setBeginIndex(pos.getBeginIndex() + offsets[0]);

View file

@ -14,7 +14,7 @@ import com.ibm.icu.impl.DontCareFieldPosition;
import com.ibm.icu.impl.ICUCache;
import com.ibm.icu.impl.ICUResourceBundle;
import com.ibm.icu.impl.SimpleCache;
import com.ibm.icu.impl.SimplePatternFormatter;
import com.ibm.icu.impl.SimpleFormatterImpl;
import com.ibm.icu.impl.StandardPlural;
import com.ibm.icu.lang.UCharacter;
import com.ibm.icu.util.Calendar;
@ -409,7 +409,7 @@ public final class RelativeDateTimeFormatter {
numberFormat, pluralRules, formatStr, fieldPosition);
String formatter = getRelativeUnitPluralPattern(style, unit, pastFutureIndex, pluralForm);
result = SimplePatternFormatter.formatCompiledPattern(formatter, formatStr);
result = SimpleFormatterImpl.formatCompiledPattern(formatter, formatStr);
}
return adjustForContext(result);
@ -852,7 +852,7 @@ public final class RelativeDateTimeFormatter {
@Override
public void put(UResource.Key key, UResource.Value value) {
/* Make two lists of simplePatternFmtList, one for past and one for future.
* Set a SimplePatternFormatter for the <style, relative unit, plurality>
* Set a SimpleFormatter pattern for the <style, relative unit, plurality>
*
* Fill in values for the particular plural given, e.g., ONE, FEW, OTHER, etc.
*/
@ -869,9 +869,9 @@ public final class RelativeDateTimeFormatter {
}
int pluralIndex = StandardPlural.indexFromString(key.toString());
if (patterns[pastFutureIndex][pluralIndex] == null) {
patterns[pastFutureIndex][pluralIndex] =
SimplePatternFormatter.compileToStringMinMaxPlaceholders(value.getString(),
sb, 0, 1);
patterns[pastFutureIndex][pluralIndex] =
SimpleFormatterImpl.compileToStringMinMaxArguments(
value.getString(), sb, 0, 1);
}
}
}

View file

@ -0,0 +1,170 @@
/*
*******************************************************************************
* Copyright (C) 2014-2016, International Business Machines Corporation and
* others. All Rights Reserved.
*******************************************************************************
*/
package com.ibm.icu.text;
import com.ibm.icu.impl.SimpleFormatterImpl;
/**
* Formats simple patterns like "{1} was born in {0}".
* Minimal subset of MessageFormat; fast, simple, minimal dependencies.
* Supports only numbered arguments with no type nor style parameters,
* and formats only string values.
* Quoting via ASCII apostrophe compatible with ICU MessageFormat default behavior.
*
* <p>Factory methods throw exceptions for syntax errors
* and for too few or too many arguments/placeholders.
*
* <p>SimpleFormatter objects are immutable and can be safely cached like strings.
*
* <p>Example:
* <pre>
* SimpleFormatter fmt = SimpleFormatter.compile("{1} '{born}' in {0}");
*
* // Output: "paul {born} in england"
* System.out.println(fmt.format("england", "paul"));
* </pre>
*
* @see MessageFormat
* @see MessagePattern.ApostropheMode
* @draft ICU 57
* @provisional This API might change or be removed in a future release.
*/
public final class SimpleFormatter {
// For internal use in Java, use SimpleFormatterImpl directly instead:
// It is most efficient to compile patterns to compiled-pattern strings
// and use them with static methods.
// (Avoids allocating SimpleFormatter wrapper objects.)
/**
* Binary representation of the compiled pattern.
* @see SimpleFormatterImpl
*/
private final String compiledPattern;
private SimpleFormatter(String compiledPattern) {
this.compiledPattern = compiledPattern;
}
/**
* Creates a formatter from the pattern string.
*
* @param pattern The pattern string.
* @return The new SimpleFormatter object.
* @throws IllegalArgumentException for bad argument syntax.
* @draft ICU 57
* @provisional This API might change or be removed in a future release.
*/
public static SimpleFormatter compile(CharSequence pattern) {
return compileMinMaxArguments(pattern, 0, Integer.MAX_VALUE);
}
/**
* Creates a formatter from the pattern string.
* The number of arguments checked against the given limits is the
* highest argument number plus one, not the number of occurrences of arguments.
*
* @param pattern The pattern string.
* @param min The pattern must have at least this many arguments.
* @param max The pattern must have at most this many arguments.
* @return The new SimpleFormatter object.
* @throws IllegalArgumentException for bad argument syntax and too few or too many arguments.
* @draft ICU 57
* @provisional This API might change or be removed in a future release.
*/
public static SimpleFormatter compileMinMaxArguments(CharSequence pattern, int min, int max) {
StringBuilder sb = new StringBuilder();
String compiledPattern = SimpleFormatterImpl.compileToStringMinMaxArguments(pattern, sb, min, max);
return new SimpleFormatter(compiledPattern);
}
/**
* @return The max argument number + 1.
* @draft ICU 57
* @provisional This API might change or be removed in a future release.
*/
public int getArgumentLimit() {
return SimpleFormatterImpl.getArgumentLimit(compiledPattern);
}
/**
* Formats the given values.
* @draft ICU 57
* @provisional This API might change or be removed in a future release.
*/
public String format(CharSequence... values) {
return SimpleFormatterImpl.formatCompiledPattern(compiledPattern, values);
}
/**
* Formats the given values, appending to the appendTo builder.
*
* @param appendTo Gets the formatted pattern and values appended.
* @param offsets offsets[i] receives the offset of where
* values[i] replaced pattern argument {i}.
* Can be null, or can be shorter or longer than values.
* If there is no {i} in the pattern, then offsets[i] is set to -1.
* @param values The argument values.
* An argument value must not be the same object as appendTo.
* values.length must be at least getArgumentLimit().
* Can be null if getArgumentLimit()==0.
* @return appendTo
* @draft ICU 57
* @provisional This API might change or be removed in a future release.
*/
public StringBuilder formatAndAppend(
StringBuilder appendTo, int[] offsets, CharSequence... values) {
return SimpleFormatterImpl.formatAndAppend(compiledPattern, appendTo, offsets, values);
}
/**
* Formats the given values, replacing the contents of the result builder.
* May optimize by actually appending to the result if it is the same object
* as the value corresponding to the initial argument in the pattern.
*
* @param result Gets its contents replaced by the formatted pattern and values.
* @param offsets offsets[i] receives the offset of where
* values[i] replaced pattern argument {i}.
* Can be null, or can be shorter or longer than values.
* If there is no {i} in the pattern, then offsets[i] is set to -1.
* @param values The argument values.
* An argument value may be the same object as result.
* values.length must be at least getArgumentLimit().
* @return result
* @draft ICU 57
* @provisional This API might change or be removed in a future release.
*/
public StringBuilder formatAndReplace(
StringBuilder result, int[] offsets, CharSequence... values) {
return SimpleFormatterImpl.formatAndReplace(compiledPattern, result, offsets, values);
}
/**
* Returns a string similar to the original pattern, only for debugging.
*
* @draft ICU 57
* @provisional This API might change or be removed in a future release.
*/
@Override
public String toString() {
String[] values = new String[getArgumentLimit()];
for (int i = 0; i < values.length; i++) {
values[i] = "{" + i + '}';
}
return formatAndAppend(new StringBuilder(), null, values).toString();
}
/**
* Returns the pattern text with none of the arguments.
* Like formatting with all-empty string values.
*
* @draft ICU 57
* @provisional This API might change or be removed in a future release.
*/
public String getTextWithNoArguments() {
return SimpleFormatterImpl.getTextWithNoArguments(compiledPattern);
}
}

View file

@ -9,7 +9,7 @@ package com.ibm.icu.dev.test.format;
import java.util.Arrays;
import com.ibm.icu.dev.test.TestFmwk;
import com.ibm.icu.impl.SimplePatternFormatter;
import com.ibm.icu.impl.SimpleFormatterImpl;
import com.ibm.icu.impl.StandardPlural;
import com.ibm.icu.text.MeasureFormat;
import com.ibm.icu.text.MeasureFormat.FormatWidth;
@ -62,7 +62,7 @@ public class PluralRangesTest extends TestFmwk {
FormatWidth width = FormatWidth.valueOf(test[1]);
String expected = test[2];
String formatter = MeasureFormat.getRangeFormat(ulocale, width);
String actual = SimplePatternFormatter.formatCompiledPattern(formatter, "{0}", "{1}");
String actual = SimpleFormatterImpl.formatCompiledPattern(formatter, "{0}", "{1}");
assertEquals("range pattern " + Arrays.asList(test), expected, actual);
}
}

View file

@ -1,7 +1,7 @@
/*
*******************************************************************************
* Copyright (C) 1996-2014, International Business Machines Corporation and *
* others. All Rights Reserved. *
* Copyright (C) 1996-2016, International Business Machines Corporation and
* others. All Rights Reserved.
*******************************************************************************
*/
package com.ibm.icu.dev.test.impl;
@ -22,7 +22,7 @@ public class TestAll extends TestGroup {
"ICUServiceTest",
"ICUServiceThreadTest",
"ICUBinaryTest",
"SimplePatternFormatterTest",
"SimpleFormatterTest",
"TextTrieMapTest"
},
"Test miscellaneous implementation utilities");

View file

@ -7,16 +7,16 @@
package com.ibm.icu.dev.test.util;
import com.ibm.icu.dev.test.TestFmwk;
import com.ibm.icu.impl.SimplePatternFormatter;
import com.ibm.icu.text.MessageFormat;
import com.ibm.icu.text.SimpleFormatter;
import com.ibm.icu.util.ULocale;
public class SimplePatternFormatterTest extends TestFmwk {
public class SimpleFormatterTest extends TestFmwk {
/**
* Constructor
*/
public SimplePatternFormatterTest()
public SimpleFormatterTest()
{
}
@ -24,20 +24,20 @@ public class SimplePatternFormatterTest extends TestFmwk {
public static void main(String arg[])
{
SimplePatternFormatterTest test = new SimplePatternFormatterTest();
SimpleFormatterTest test = new SimpleFormatterTest();
try {
test.run(arg);
} catch (Exception e) {
test.errln("Error testing SimplePatternFormatterTest");
test.errln("Error testing SimpleFormatterTest");
}
}
public void TestWithNoPlaceholders() {
SimplePatternFormatter fmt = SimplePatternFormatter.compile("This doesn''t have templates '{0}");
public void TestWithNoArguments() {
SimpleFormatter fmt = SimpleFormatter.compile("This doesn''t have templates '{0}");
assertEquals(
"getPlaceholderCount",
"getArgumentLimit",
0,
fmt.getPlaceholderCount());
fmt.getArgumentLimit());
assertEquals(
"format",
"This doesn't have templates {0}",
@ -71,41 +71,41 @@ public class SimplePatternFormatterTest extends TestFmwk {
public void TestSyntaxErrors() {
try {
SimplePatternFormatter.compile("{}");
SimpleFormatter.compile("{}");
fail("Syntax error did not yield an exception.");
} catch (IllegalArgumentException expected) {
}
try {
SimplePatternFormatter.compile("{12d");
SimpleFormatter.compile("{12d");
fail("Syntax error did not yield an exception.");
} catch (IllegalArgumentException expected) {
}
}
public void TestOnePlaceholder() {
assertEquals("TestOnePlaceholder",
public void TestOneArgument() {
assertEquals("TestOneArgument",
"1 meter",
SimplePatternFormatter.compile("{0} meter").format("1"));
SimpleFormatter.compile("{0} meter").format("1"));
}
public void TestBigPlaceholder() {
SimplePatternFormatter fmt = SimplePatternFormatter.compile("a{20}c");
assertEquals("{20} count", 21, fmt.getPlaceholderCount());
public void TestBigArgument() {
SimpleFormatter fmt = SimpleFormatter.compile("a{20}c");
assertEquals("{20} count", 21, fmt.getArgumentLimit());
CharSequence[] values = new CharSequence[21];
values[20] = "b";
assertEquals("{20}=b", "abc", fmt.format(values));
}
public void TestGetTextWithNoPlaceholders() {
public void TestGetTextWithNoArguments() {
assertEquals(
"",
"Templates and are here.",
SimplePatternFormatter.compile(
"Templates {1}{2} and {3} are here.").getTextWithNoPlaceholders());
SimpleFormatter.compile(
"Templates {1}{2} and {3} are here.").getTextWithNoArguments());
}
public void TestTooFewPlaceholderValues() {
SimplePatternFormatter fmt = SimplePatternFormatter.compile(
public void TestTooFewArgumentValues() {
SimpleFormatter fmt = SimpleFormatter.compile(
"Templates {2}{1} and {4} are out of order.");
try {
fmt.format("freddy", "tommy", "frog", "leg");
@ -129,13 +129,13 @@ public class SimplePatternFormatterTest extends TestFmwk {
}
}
public void TestWithPlaceholders() {
SimplePatternFormatter fmt = SimplePatternFormatter.compile(
public void TestWithArguments() {
SimpleFormatter fmt = SimpleFormatter.compile(
"Templates {2}{1} and {4} are out of order.");
assertEquals(
"getPlaceholderCount",
"getArgumentLimit",
5,
fmt.getPlaceholderCount());
fmt.getArgumentLimit());
assertEquals(
"toString",
"Templates {2}{1} and {4} are out of order.",
@ -153,9 +153,9 @@ public class SimplePatternFormatterTest extends TestFmwk {
verifyOffsets(expectedOffsets, offsets);
}
public void TestFormatUseAppendToAsPlaceholder() {
SimplePatternFormatter fmt = SimplePatternFormatter.compile(
"Placeholders {0} and {1}");
public void TestFormatUseAppendToAsArgument() {
SimpleFormatter fmt = SimpleFormatter.compile(
"Arguments {0} and {1}");
StringBuilder appendTo = new StringBuilder("previous:");
try {
fmt.formatAndAppend(appendTo, null, appendTo, "frog");
@ -166,7 +166,7 @@ public class SimplePatternFormatterTest extends TestFmwk {
}
public void TestFormatReplaceNoOptimization() {
SimplePatternFormatter fmt = SimplePatternFormatter.compile("{2}, {0}, {1} and {3}");
SimpleFormatter fmt = SimpleFormatter.compile("{2}, {0}, {1} and {3}");
int[] offsets = new int[4];
StringBuilder result = new StringBuilder("original");
assertEquals(
@ -183,7 +183,7 @@ public class SimplePatternFormatterTest extends TestFmwk {
public void TestFormatReplaceNoOptimizationLeadingText() {
SimplePatternFormatter fmt = SimplePatternFormatter.compile("boo {2}, {0}, {1} and {3}");
SimpleFormatter fmt = SimpleFormatter.compile("boo {2}, {0}, {1} and {3}");
int[] offsets = new int[4];
StringBuilder result = new StringBuilder("original");
assertEquals(
@ -199,7 +199,7 @@ public class SimplePatternFormatterTest extends TestFmwk {
}
public void TestFormatReplaceOptimization() {
SimplePatternFormatter fmt = SimplePatternFormatter.compile("{2}, {0}, {1} and {3}");
SimpleFormatter fmt = SimpleFormatter.compile("{2}, {0}, {1} and {3}");
int[] offsets = new int[4];
StringBuilder result = new StringBuilder("original");
assertEquals(
@ -215,7 +215,7 @@ public class SimplePatternFormatterTest extends TestFmwk {
}
public void TestFormatReplaceOptimizationNoOffsets() {
SimplePatternFormatter fmt = SimplePatternFormatter.compile("{2}, {0}, {1} and {3}");
SimpleFormatter fmt = SimpleFormatter.compile("{2}, {0}, {1} and {3}");
StringBuilder result = new StringBuilder("original");
assertEquals(
"format",
@ -228,17 +228,17 @@ public class SimplePatternFormatterTest extends TestFmwk {
}
public void TestFormatReplaceNoOptimizationNoOffsets() {
SimplePatternFormatter fmt = SimplePatternFormatter.compile(
"Placeholders {0} and {1}");
SimpleFormatter fmt = SimpleFormatter.compile(
"Arguments {0} and {1}");
StringBuilder result = new StringBuilder("previous:");
assertEquals(
"",
"Placeholders previous: and frog",
"Arguments previous: and frog",
fmt.formatAndReplace(result, null, result, "frog").toString());
}
public void TestFormatReplaceNoOptimizationLeadingPlaceholderUsedTwice() {
SimplePatternFormatter fmt = SimplePatternFormatter.compile(
public void TestFormatReplaceNoOptimizationLeadingArgumentUsedTwice() {
SimpleFormatter fmt = SimpleFormatter.compile(
"{2}, {0}, {1} and {3} {2}");
StringBuilder result = new StringBuilder("original");
int[] offsets = new int[4];
@ -255,11 +255,11 @@ public class SimplePatternFormatterTest extends TestFmwk {
public void TestQuotingLikeMessageFormat() {
String pattern = "{0} don't can''t '{5}''}{a' again '}'{1} to the '{end";
SimplePatternFormatter spf = SimplePatternFormatter.compile(pattern);
SimpleFormatter spf = SimpleFormatter.compile(pattern);
MessageFormat mf = new MessageFormat(pattern, ULocale.ROOT);
String expected = "X don't can't {5}'}{a again }Y to the {end";
assertEquals("MessageFormat", expected, mf.format(new Object[] { "X", "Y" }));
assertEquals("SimplePatternFormatter", expected, spf.format("X", "Y"));
assertEquals("SimpleFormatter", expected, spf.format("X", "Y"));
}
void verifyOffsets(int[] expected, int[] actual) {