script-astra/Android/Sdk/sources/android-35/android/icu/impl/SimpleFormatterImpl.java
localadmin 4380f00a78 init
2025-01-20 18:15:20 +03:00

493 lines
21 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* 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;
}
}