493 lines
21 KiB
Java
493 lines
21 KiB
Java
![]() |
/* GENERATED SOURCE. DO NOT MODIFY. */
|
|||
|
// © 2016 and later: Unicode, Inc. and others.
|
|||
|
// License & terms of use: http://www.unicode.org/copyright.html
|
|||
|
/*
|
|||
|
*******************************************************************************
|
|||
|
* Copyright (C) 2014-2016, International Business Machines Corporation and
|
|||
|
* others. All Rights Reserved.
|
|||
|
*******************************************************************************
|
|||
|
*/
|
|||
|
package android.icu.impl;
|
|||
|
|
|||
|
import java.io.IOException;
|
|||
|
import java.text.Format;
|
|||
|
|
|||
|
import android.icu.util.ICUUncheckedIOException;
|
|||
|
|
|||
|
/**
|
|||
|
* Formats simple patterns like "{1} was born in {0}".
|
|||
|
* Internal version of {@link android.icu.text.SimpleFormatter}
|
|||
|
* with only static methods, to avoid wrapper objects.
|
|||
|
*
|
|||
|
* <p>This class "compiles" pattern strings into a binary format
|
|||
|
* and implements formatting etc. based on that.
|
|||
|
*
|
|||
|
* <p>Format:
|
|||
|
* 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.
|
|||
|
* @hide Only a subset of ICU is exposed in Android
|
|||
|
*/
|
|||
|
public final class SimpleFormatterImpl {
|
|||
|
/**
|
|||
|
* Argument numbers must be smaller than this limit.
|
|||
|
* Text segment lengths are offset by this much.
|
|||
|
* This is currently the only unused char value in compiled patterns,
|
|||
|
* 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_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_ARGUMENT_CHAR - ARG_NUM_LIMIT;
|
|||
|
|
|||
|
/** "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
|
|||
|
};
|
|||
|
|
|||
|
/** 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 sb A StringBuilder instance which may or may not be used.
|
|||
|
* @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 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
|
|||
|
int patternLength = pattern.length();
|
|||
|
sb.ensureCapacity(patternLength);
|
|||
|
// Reserve the first char for the number of arguments.
|
|||
|
sb.setLength(1);
|
|||
|
int textLength = 0;
|
|||
|
int maxArg = -1;
|
|||
|
boolean inQuote = false;
|
|||
|
for (int i = 0; i < patternLength;) {
|
|||
|
char c = pattern.charAt(i++);
|
|||
|
if (c == '\'') {
|
|||
|
if (i < patternLength && (c = pattern.charAt(i)) == '\'') {
|
|||
|
// double apostrophe, skip the second one
|
|||
|
++i;
|
|||
|
} else if (inQuote) {
|
|||
|
// skip the quote-ending apostrophe
|
|||
|
inQuote = false;
|
|||
|
continue;
|
|||
|
} else if (c == '{' || c == '}') {
|
|||
|
// Skip the quote-starting apostrophe, find the end of the quoted literal text.
|
|||
|
++i;
|
|||
|
inQuote = true;
|
|||
|
} else {
|
|||
|
// The apostrophe is part of literal text.
|
|||
|
c = '\'';
|
|||
|
}
|
|||
|
} else if (!inQuote && c == '{') {
|
|||
|
if (textLength > 0) {
|
|||
|
sb.setCharAt(sb.length() - textLength - 1, (char)(ARG_NUM_LIMIT + textLength));
|
|||
|
textLength = 0;
|
|||
|
}
|
|||
|
int argNumber;
|
|||
|
if ((i + 1) < patternLength &&
|
|||
|
0 <= (argNumber = pattern.charAt(i) - '0') && argNumber <= 9 &&
|
|||
|
pattern.charAt(i + 1) == '}') {
|
|||
|
i += 2;
|
|||
|
} else {
|
|||
|
// Multi-digit argument number (no leading zero) or syntax error.
|
|||
|
// MessagePattern permits PatternProps.skipWhiteSpace(pattern, index)
|
|||
|
// around the number, but this class does not.
|
|||
|
int argStart = i - 1;
|
|||
|
argNumber = -1;
|
|||
|
if (i < patternLength && '1' <= (c = pattern.charAt(i++)) && c <= '9') {
|
|||
|
argNumber = c - '0';
|
|||
|
while (i < patternLength && '0' <= (c = pattern.charAt(i++)) && c <= '9') {
|
|||
|
argNumber = argNumber * 10 + (c - '0');
|
|||
|
if (argNumber >= ARG_NUM_LIMIT) {
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
if (argNumber < 0 || c != '}') {
|
|||
|
throw new IllegalArgumentException(
|
|||
|
"Argument syntax error in pattern \"" + pattern +
|
|||
|
"\" at index " + argStart +
|
|||
|
": " + pattern.subSequence(argStart, i));
|
|||
|
}
|
|||
|
}
|
|||
|
if (argNumber > maxArg) {
|
|||
|
maxArg = argNumber;
|
|||
|
}
|
|||
|
sb.append((char)argNumber);
|
|||
|
continue;
|
|||
|
} // else: c is part of literal text
|
|||
|
// 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_ARGUMENT_CHAR);
|
|||
|
}
|
|||
|
sb.append(c);
|
|||
|
if (++textLength == MAX_SEGMENT_LENGTH) {
|
|||
|
textLength = 0;
|
|||
|
}
|
|||
|
}
|
|||
|
if (textLength > 0) {
|
|||
|
sb.setCharAt(sb.length() - textLength - 1, (char)(ARG_NUM_LIMIT + textLength));
|
|||
|
}
|
|||
|
int argCount = maxArg + 1;
|
|||
|
if (argCount < min) {
|
|||
|
throw new IllegalArgumentException(
|
|||
|
"Fewer than minimum " + min + " arguments in pattern \"" + pattern + "\"");
|
|||
|
}
|
|||
|
if (argCount > max) {
|
|||
|
throw new IllegalArgumentException(
|
|||
|
"More than maximum " + max + " arguments in pattern \"" + pattern + "\"");
|
|||
|
}
|
|||
|
sb.setCharAt(0, (char)argCount);
|
|||
|
return sb.toString();
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* @param compiledPattern Compiled form of a pattern string.
|
|||
|
* @return The max argument number + 1.
|
|||
|
*/
|
|||
|
public static int getArgumentLimit(String compiledPattern) {
|
|||
|
return compiledPattern.charAt(0);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Formats the given values.
|
|||
|
*
|
|||
|
* @param compiledPattern Compiled form of a pattern string.
|
|||
|
*/
|
|||
|
public static String formatCompiledPattern(String compiledPattern, CharSequence... values) {
|
|||
|
return formatAndAppend(compiledPattern, new StringBuilder(), null, values).toString();
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Formats the not-compiled pattern with the given values.
|
|||
|
* Equivalent to compileToStringMinMaxArguments() followed by formatCompiledPattern().
|
|||
|
* 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 Not-compiled form of a 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 compiled-pattern string.
|
|||
|
* @throws IllegalArgumentException for bad argument syntax and too few or too many arguments.
|
|||
|
*/
|
|||
|
public static String formatRawPattern(String pattern, int min, int max, CharSequence... values) {
|
|||
|
StringBuilder sb = new StringBuilder();
|
|||
|
String compiledPattern = compileToStringMinMaxArguments(pattern, sb, min, max);
|
|||
|
sb.setLength(0);
|
|||
|
return formatAndAppend(compiledPattern, sb, null, values).toString();
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Formats the given values, appending to the appendTo builder.
|
|||
|
*
|
|||
|
* @param compiledPattern Compiled form of a pattern string.
|
|||
|
* @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
|
|||
|
*/
|
|||
|
public static StringBuilder formatAndAppend(
|
|||
|
String compiledPattern, StringBuilder appendTo, int[] offsets, CharSequence... values) {
|
|||
|
int valuesLength = values != null ? values.length : 0;
|
|||
|
if (valuesLength < getArgumentLimit(compiledPattern)) {
|
|||
|
throw new IllegalArgumentException("Too few values.");
|
|||
|
}
|
|||
|
return format(compiledPattern, values, appendTo, null, true, offsets);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 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 compiledPattern Compiled form of a pattern string.
|
|||
|
* @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
|
|||
|
*/
|
|||
|
public static StringBuilder formatAndReplace(
|
|||
|
String compiledPattern, StringBuilder result, int[] offsets, CharSequence... values) {
|
|||
|
int valuesLength = values != null ? values.length : 0;
|
|||
|
if (valuesLength < getArgumentLimit(compiledPattern)) {
|
|||
|
throw new IllegalArgumentException("Too few values.");
|
|||
|
}
|
|||
|
|
|||
|
// If the pattern starts with an argument whose value is the same object
|
|||
|
// as the result, then we keep the result contents and append to it.
|
|||
|
// Otherwise we replace its contents.
|
|||
|
int firstArg = -1;
|
|||
|
// 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 (getArgumentLimit(compiledPattern) > 0) {
|
|||
|
for (int i = 1; i < compiledPattern.length();) {
|
|||
|
int n = compiledPattern.charAt(i++);
|
|||
|
if (n < ARG_NUM_LIMIT) {
|
|||
|
if (values[n] == result) {
|
|||
|
if (i == 2) {
|
|||
|
firstArg = n;
|
|||
|
} else if (resultCopy == null) {
|
|||
|
resultCopy = result.toString();
|
|||
|
}
|
|||
|
}
|
|||
|
} else {
|
|||
|
i += n - ARG_NUM_LIMIT;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
if (firstArg < 0) {
|
|||
|
result.setLength(0);
|
|||
|
}
|
|||
|
return format(compiledPattern, values, result, resultCopy, false, offsets);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 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 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;
|
|||
|
if (segmentLength > 0) {
|
|||
|
int limit = i + segmentLength;
|
|||
|
sb.append(compiledPattern, i, limit);
|
|||
|
i = limit;
|
|||
|
}
|
|||
|
}
|
|||
|
return sb.toString();
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Returns the length of the pattern text with none of the arguments.
|
|||
|
* @param compiledPattern Compiled form of a pattern string.
|
|||
|
* @param codePoints true to count code points; false to count code units.
|
|||
|
* @return The number of code points or code units.
|
|||
|
*/
|
|||
|
public static int getLength(String compiledPattern, boolean codePoints) {
|
|||
|
int result = 0;
|
|||
|
for (int i = 1; i < compiledPattern.length();) {
|
|||
|
int segmentLength = compiledPattern.charAt(i++) - ARG_NUM_LIMIT;
|
|||
|
if (segmentLength > 0) {
|
|||
|
int limit = i + segmentLength;
|
|||
|
if (codePoints) {
|
|||
|
result += Character.codePointCount(compiledPattern, i, limit);
|
|||
|
} else {
|
|||
|
result += (limit - i);
|
|||
|
}
|
|||
|
i = limit;
|
|||
|
}
|
|||
|
}
|
|||
|
return result;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Returns the length in code units of the pattern text up until the first argument.
|
|||
|
* @param compiledPattern Compiled form of a pattern string.
|
|||
|
* @return The number of code units.
|
|||
|
*/
|
|||
|
public static int getPrefixLength(String compiledPattern) {
|
|||
|
if (compiledPattern.length() == 1) {
|
|||
|
return 0;
|
|||
|
} else if (compiledPattern.charAt(0) == 0) {
|
|||
|
return compiledPattern.length() - 2;
|
|||
|
} else if (compiledPattern.charAt(1) <= ARG_NUM_LIMIT) {
|
|||
|
return 0;
|
|||
|
} else {
|
|||
|
return compiledPattern.charAt(1) - ARG_NUM_LIMIT;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Special case for using FormattedStringBuilder with patterns with 0 or 1 argument.
|
|||
|
*
|
|||
|
* With 1 argument, treat the current contents of the FormattedStringBuilder between
|
|||
|
* start and end as the argument {0}. Insert the extra strings from compiledPattern
|
|||
|
* to surround the argument in the output.
|
|||
|
*
|
|||
|
* With 0 arguments, overwrite the entire contents of the FormattedStringBuilder
|
|||
|
* between start and end.
|
|||
|
*
|
|||
|
* @param compiledPattern Compiled form of a pattern string.
|
|||
|
* @param field Field to use when adding chars to the output.
|
|||
|
* @param start The start index of the argument already in the output string.
|
|||
|
* @param end The end index of the argument already in the output string.
|
|||
|
* @param output Destination for formatted output.
|
|||
|
* @return Net number of characters added to the formatted string.
|
|||
|
*/
|
|||
|
public static int formatPrefixSuffix(
|
|||
|
String compiledPattern,
|
|||
|
Format.Field field,
|
|||
|
int start,
|
|||
|
int end,
|
|||
|
FormattedStringBuilder output) {
|
|||
|
int argLimit = getArgumentLimit(compiledPattern);
|
|||
|
if (argLimit == 0) {
|
|||
|
// No arguments in compiled pattern; overwrite the entire segment with our string.
|
|||
|
return output.splice(start, end, compiledPattern, 2, compiledPattern.length(), field);
|
|||
|
} else {
|
|||
|
assert argLimit == 1;
|
|||
|
int suffixOffset;
|
|||
|
int length = 0;
|
|||
|
if (compiledPattern.charAt(1) != '\u0000') {
|
|||
|
int prefixLength = compiledPattern.charAt(1) - ARG_NUM_LIMIT;
|
|||
|
length = output.insert(start, compiledPattern, 2, 2 + prefixLength, field);
|
|||
|
suffixOffset = 3 + prefixLength;
|
|||
|
} else {
|
|||
|
suffixOffset = 2;
|
|||
|
}
|
|||
|
if (suffixOffset < compiledPattern.length()) {
|
|||
|
int suffixLength = compiledPattern.charAt(suffixOffset) - ARG_NUM_LIMIT;
|
|||
|
length += output.insert(end + length, compiledPattern, 1 + suffixOffset,
|
|||
|
1 + suffixOffset + suffixLength, field);
|
|||
|
}
|
|||
|
return length;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/** Internal iterator interface for maximum efficiency.
|
|||
|
*
|
|||
|
* Usage boilerplate:
|
|||
|
*
|
|||
|
* <pre>
|
|||
|
* long state = 0;
|
|||
|
* while (true) {
|
|||
|
* state = IterInternal.step(state, compiledPattern, output);
|
|||
|
* if (state == IterInternal.DONE) {
|
|||
|
* break;
|
|||
|
* }
|
|||
|
* int argIndex = IterInternal.getArgIndex(state);
|
|||
|
* // Append the string corresponding to argIndex to output
|
|||
|
* }
|
|||
|
* </pre>
|
|||
|
* @hide Only a subset of ICU is exposed in Android
|
|||
|
*
|
|||
|
*/
|
|||
|
public static class IterInternal {
|
|||
|
public static final long DONE = -1;
|
|||
|
|
|||
|
public static long step(long state, CharSequence compiledPattern, Appendable output) {
|
|||
|
int i = (int) (state >>> 32);
|
|||
|
assert i < compiledPattern.length();
|
|||
|
i++;
|
|||
|
while (i < compiledPattern.length() && compiledPattern.charAt(i) > ARG_NUM_LIMIT) {
|
|||
|
int limit = i + compiledPattern.charAt(i) + 1 - ARG_NUM_LIMIT;
|
|||
|
try {
|
|||
|
output.append(compiledPattern, i + 1, limit);
|
|||
|
} catch (IOException e) {
|
|||
|
throw new ICUUncheckedIOException(e);
|
|||
|
}
|
|||
|
i = limit;
|
|||
|
}
|
|||
|
if (i == compiledPattern.length()) {
|
|||
|
return DONE;
|
|||
|
}
|
|||
|
return (((long) i) << 32) | compiledPattern.charAt(i);
|
|||
|
}
|
|||
|
|
|||
|
public static int getArgIndex(long state) {
|
|||
|
return (int) state;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private static StringBuilder format(
|
|||
|
String compiledPattern, CharSequence[] values,
|
|||
|
StringBuilder result, String resultCopy, boolean forbidResultAsValue,
|
|||
|
int[] offsets) {
|
|||
|
int offsetsLength;
|
|||
|
if (offsets == null) {
|
|||
|
offsetsLength = 0;
|
|||
|
} else {
|
|||
|
offsetsLength = offsets.length;
|
|||
|
for (int i = 0; i < offsetsLength; i++) {
|
|||
|
offsets[i] = -1;
|
|||
|
}
|
|||
|
}
|
|||
|
for (int i = 1; i < compiledPattern.length();) {
|
|||
|
int n = compiledPattern.charAt(i++);
|
|||
|
if (n < ARG_NUM_LIMIT) {
|
|||
|
CharSequence value = values[n];
|
|||
|
if (value == result) {
|
|||
|
if (forbidResultAsValue) {
|
|||
|
throw new IllegalArgumentException("Value must not be same object as result");
|
|||
|
}
|
|||
|
if (i == 2) {
|
|||
|
// We are appending to result which is also the first value object.
|
|||
|
if (n < offsetsLength) {
|
|||
|
offsets[n] = 0;
|
|||
|
}
|
|||
|
} else {
|
|||
|
if (n < offsetsLength) {
|
|||
|
offsets[n] = result.length();
|
|||
|
}
|
|||
|
result.append(resultCopy);
|
|||
|
}
|
|||
|
} else {
|
|||
|
if (n < offsetsLength) {
|
|||
|
offsets[n] = result.length();
|
|||
|
}
|
|||
|
result.append(value);
|
|||
|
}
|
|||
|
} else {
|
|||
|
int limit = i + (n - ARG_NUM_LIMIT);
|
|||
|
result.append(compiledPattern, i, limit);
|
|||
|
i = limit;
|
|||
|
}
|
|||
|
}
|
|||
|
return result;
|
|||
|
}
|
|||
|
}
|