diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/SimplePatternFormatter.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/SimpleFormatterImpl.java similarity index 60% rename from icu4j/main/classes/core/src/com/ibm/icu/impl/SimplePatternFormatter.java rename to icu4j/main/classes/core/src/com/ibm/icu/impl/SimpleFormatterImpl.java index 32dcb642ce0..7098c607d02 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/SimplePatternFormatter.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/SimpleFormatterImpl.java @@ -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. * - *
Factory methods throw exceptions for syntax errors - * and for too few or too many arguments/placeholders. + *
This class "compiles" pattern strings into a binary format + * and implements formatting etc. based on that. * - *
SimplePatternFormatter objects are immutable and can be safely cached like strings. + *
Format: + * Index 0: One more than the highest argument number. + * Followed by zero or more arguments or literal-text segments. * - *
Example: - *
- * SimplePatternFormatter fmt = SimplePatternFormatter.compile("{1} '{born}' in {0}"); - * - * // Output: "paul {born} in england" - * System.out.println(fmt.format("england", "paul")); - *- * - * @see com.ibm.icu.text.MessageFormat - * @see com.ibm.icu.text.MessagePattern.ApostropheMode + *
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. - * - *
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;
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/ListFormatter.java b/icu4j/main/classes/core/src/com/ibm/icu/text/ListFormatter.java
index 0dda989128d..cc1c76c098d 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/text/ListFormatter.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/text/ListFormatter.java
@@ -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) {
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/MeasureFormat.java b/icu4j/main/classes/core/src/com/ibm/icu/text/MeasureFormat.java
index cff1437e25a..15ecee7c3da 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/text/MeasureFormat.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/text/MeasureFormat.java
@@ -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