script-astra/Android/Sdk/sources/android-35/android/icu/message2/MFDataModelFormatter.java

603 lines
27 KiB
Java
Raw Permalink Normal View History

2025-01-20 15:15:20 +00:00
/* GENERATED SOURCE. DO NOT MODIFY. */
// © 2022 and later: Unicode, Inc. and others.
// License & terms of use: https://www.unicode.org/copyright.html
package android.icu.message2;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import android.icu.message2.MFDataModel.Annotation;
import android.icu.message2.MFDataModel.CatchallKey;
import android.icu.message2.MFDataModel.Declaration;
import android.icu.message2.MFDataModel.Expression;
import android.icu.message2.MFDataModel.FunctionAnnotation;
import android.icu.message2.MFDataModel.FunctionExpression;
import android.icu.message2.MFDataModel.InputDeclaration;
import android.icu.message2.MFDataModel.Literal;
import android.icu.message2.MFDataModel.LiteralExpression;
import android.icu.message2.MFDataModel.LiteralOrCatchallKey;
import android.icu.message2.MFDataModel.LiteralOrVariableRef;
import android.icu.message2.MFDataModel.LocalDeclaration;
import android.icu.message2.MFDataModel.Option;
import android.icu.message2.MFDataModel.Pattern;
import android.icu.message2.MFDataModel.SelectMessage;
import android.icu.message2.MFDataModel.StringPart;
import android.icu.message2.MFDataModel.UnsupportedAnnotation;
import android.icu.message2.MFDataModel.UnsupportedExpression;
import android.icu.message2.MFDataModel.VariableRef;
import android.icu.message2.MFDataModel.Variant;
import android.icu.util.Calendar;
import android.icu.util.CurrencyAmount;
/**
* Takes an {@link MFDataModel} and formats it to a {@link String}
* (and later on we will also implement formatting to a {@code FormattedMessage}).
*/
// TODO: move this in the MessageFormatter?
class MFDataModelFormatter {
private final Locale locale;
private final MFDataModel.Message dm;
private final MFFunctionRegistry standardFunctions;
private final MFFunctionRegistry customFunctions;
private static final MFFunctionRegistry EMPTY_REGISTY = MFFunctionRegistry.builder().build();
MFDataModelFormatter(
MFDataModel.Message dm, Locale locale, MFFunctionRegistry customFunctionRegistry) {
this.locale = locale;
this.dm = dm;
this.customFunctions =
customFunctionRegistry == null ? EMPTY_REGISTY : customFunctionRegistry;
standardFunctions =
MFFunctionRegistry.builder()
// Date/time formatting
.setFormatter("datetime", new DateTimeFormatterFactory("datetime"))
.setFormatter("date", new DateTimeFormatterFactory("date"))
.setFormatter("time", new DateTimeFormatterFactory("time"))
.setDefaultFormatterNameForType(Date.class, "datetime")
.setDefaultFormatterNameForType(Calendar.class, "datetime")
.setDefaultFormatterNameForType(java.util.Calendar.class, "datetime")
// Number formatting
.setFormatter("number", new NumberFormatterFactory("number"))
.setFormatter("integer", new NumberFormatterFactory("integer"))
.setDefaultFormatterNameForType(Integer.class, "number")
.setDefaultFormatterNameForType(Double.class, "number")
.setDefaultFormatterNameForType(Number.class, "number")
.setDefaultFormatterNameForType(CurrencyAmount.class, "number")
// Format that returns "to string"
.setFormatter("string", new IdentityFormatterFactory())
.setDefaultFormatterNameForType(String.class, "string")
.setDefaultFormatterNameForType(CharSequence.class, "string")
// Register the standard selectors
.setSelector("number", new NumberFormatterFactory("number"))
.setSelector("integer", new NumberFormatterFactory("integer"))
.setSelector("string", new TextSelectorFactory())
.setSelector("icu:gender", new TextSelectorFactory())
.build();
}
String format(Map<String, Object> arguments) {
MFDataModel.Pattern patternToRender = null;
if (arguments == null) {
arguments = new HashMap<>();
}
Map<String, Object> variables;
if (dm instanceof MFDataModel.PatternMessage) {
MFDataModel.PatternMessage pm = (MFDataModel.PatternMessage) dm;
variables = resolveDeclarations(pm.declarations, arguments);
patternToRender = pm.pattern;
} else if (dm instanceof MFDataModel.SelectMessage) {
MFDataModel.SelectMessage sm = (MFDataModel.SelectMessage) dm;
variables = resolveDeclarations(sm.declarations, arguments);
patternToRender = findBestMatchingPattern(sm, variables, arguments);
} else {
formattingError("");
return "ERROR!";
}
if (patternToRender == null) {
return "ERROR!";
}
StringBuilder result = new StringBuilder();
for (MFDataModel.PatternPart part : patternToRender.parts) {
if (part instanceof MFDataModel.StringPart) {
MFDataModel.StringPart strPart = (StringPart) part;
result.append(strPart.value);
} else if (part instanceof MFDataModel.Expression) {
FormattedPlaceholder formattedExpression =
formatExpression((Expression) part, variables, arguments);
result.append(formattedExpression.getFormattedValue().toString());
} else if (part instanceof MFDataModel.Markup) {
// Ignore
} else if (part instanceof MFDataModel.UnsupportedExpression) {
// Ignore
} else {
formattingError("Unknown part type: " + part);
}
}
return result.toString();
}
private Pattern findBestMatchingPattern(
SelectMessage sm, Map<String, Object> variables, Map<String, Object> arguments) {
Pattern patternToRender = null;
// ====================================
// spec: ### Resolve Selectors
// ====================================
// Collect all the selector functions in an array, to reuse
List<Expression> selectors = sm.selectors;
// spec: Let `res` be a new empty list of resolved values that support selection.
List<ResolvedSelector> res = new ArrayList<>(selectors.size());
// spec: For each _selector_ `sel`, in source order,
for (Expression sel : selectors) {
// spec: Let `rv` be the resolved value of `sel`.
FormattedPlaceholder fph = formatExpression(sel, variables, arguments);
String functionName = null;
Object argument = null;
Map<String, Object> options = new HashMap<>();
if (fph.getInput() instanceof ResolvedExpression) {
ResolvedExpression re = (ResolvedExpression) fph.getInput();
argument = re.argument;
functionName = re.functionName;
options.putAll(re.options);
} else if (fph.getInput() instanceof MFDataModel.VariableExpression) {
MFDataModel.VariableExpression ve = (MFDataModel.VariableExpression) fph.getInput();
argument = resolveLiteralOrVariable(ve.arg, variables, arguments);
if (ve.annotation instanceof FunctionAnnotation) {
functionName = ((FunctionAnnotation) ve.annotation).name;
}
} else if (fph.getInput() instanceof LiteralExpression) {
LiteralExpression le = (LiteralExpression) fph.getInput();
argument = le.arg;
if (le.annotation instanceof FunctionAnnotation) {
functionName = ((FunctionAnnotation) le.annotation).name;
}
}
SelectorFactory funcFactory = standardFunctions.getSelector(functionName);
if (funcFactory == null) {
funcFactory = customFunctions.getSelector(functionName);
}
// spec: If selection is supported for `rv`:
if (funcFactory != null) {
Selector selectorFunction = funcFactory.createSelector(locale, options);
ResolvedSelector rs = new ResolvedSelector(argument, options, selectorFunction);
// spec: Append `rv` as the last element of the list `res`.
res.add(rs);
} else {
throw new IllegalArgumentException("Unknown selector type: " + functionName);
}
}
// This should not be possible, we added one function for each selector,
// or we have thrown an exception.
// But just in case someone removes the throw above?
if (res.size() != selectors.size()) {
throw new IllegalArgumentException(
"Something went wrong, not enough selector functions, "
+ res.size() + " vs. " + selectors.size());
}
// ====================================
// spec: ### Resolve Preferences
// ====================================
// spec: Let `pref` be a new empty list of lists of strings.
List<List<String>> pref = new ArrayList<>();
// spec: For each index `i` in `res`:
for (int i = 0; i < res.size(); i++) {
// spec: Let `keys` be a new empty list of strings.
List<String> keys = new ArrayList<>();
// spec: For each _variant_ `var` of the message:
for (Variant var : sm.variants) {
// spec: Let `key` be the `var` key at position `i`.
LiteralOrCatchallKey key = var.keys.get(i);
// spec: If `key` is not the catch-all key `'*'`:
if (key instanceof CatchallKey) {
keys.add("*");
} else if (key instanceof Literal) {
// spec: Assert that `key` is a _literal_.
// spec: Let `ks` be the resolved value of `key`.
String ks = ((Literal) key).value;
// spec: Append `ks` as the last element of the list `keys`.
keys.add(ks);
} else {
formattingError("Literal expected, but got " + key);
}
}
// spec: Let `rv` be the resolved value at index `i` of `res`.
ResolvedSelector rv = res.get(i);
// spec: Let `matches` be the result of calling the method MatchSelectorKeys(`rv`, `keys`)
List<String> matches = matchSelectorKeys(rv, keys);
// spec: Append `matches` as the last element of the list `pref`.
pref.add(matches);
}
// ====================================
// spec: ### Filter Variants
// ====================================
// spec: Let `vars` be a new empty list of _variants_.
List<Variant> vars = new ArrayList<>();
// spec: For each _variant_ `var` of the message:
for (Variant var : sm.variants) {
// spec: For each index `i` in `pref`:
int found = 0;
for (int i = 0; i < pref.size(); i++) {
// spec: Let `key` be the `var` key at position `i`.
LiteralOrCatchallKey key = var.keys.get(i);
// spec: If `key` is the catch-all key `'*'`:
if (key instanceof CatchallKey) {
// spec: Continue the inner loop on `pref`.
found++;
continue;
}
// spec: Assert that `key` is a _literal_.
if (!(key instanceof Literal)) {
formattingError("Literal expected");
}
// spec: Let `ks` be the resolved value of `key`.
String ks = ((Literal) key).value;
// spec: Let `matches` be the list of strings at index `i` of `pref`.
List<String> matches = pref.get(i);
// spec: If `matches` includes `ks`:
if (matches.contains(ks)) {
// spec: Continue the inner loop on `pref`.
found++;
continue;
} else {
// spec: Else:
// spec: Continue the outer loop on message _variants_.
break;
}
}
if (found == pref.size()) {
// spec: Append `var` as the last element of the list `vars`.
vars.add(var);
}
}
// ====================================
// spec: ### Sort Variants
// ====================================
// spec: Let `sortable` be a new empty list of (integer, _variant_) tuples.
List<IntVarTuple> sortable = new ArrayList<>();
// spec: For each _variant_ `var` of `vars`:
for (Variant var : vars) {
// spec: Let `tuple` be a new tuple (-1, `var`).
IntVarTuple tuple = new IntVarTuple(-1, var);
// spec: Append `tuple` as the last element of the list `sortable`.
sortable.add(tuple);
}
// spec: Let `len` be the integer count of items in `pref`.
int len = pref.size();
// spec: Let `i` be `len` - 1.
int i = len - 1;
// spec: While `i` >= 0:
while (i >= 0) {
// spec: Let `matches` be the list of strings at index `i` of `pref`.
List<String> matches = pref.get(i);
// spec: Let `minpref` be the integer count of items in `matches`.
int minpref = matches.size();
// spec: For each tuple `tuple` of `sortable`:
for (IntVarTuple tuple : sortable) {
// spec: Let `matchpref` be an integer with the value `minpref`.
int matchpref = minpref;
// spec: Let `key` be the `tuple` _variant_ key at position `i`.
LiteralOrCatchallKey key = tuple.variant.keys.get(i);
// spec: If `key` is not the catch-all key `'*'`:
if (!(key instanceof CatchallKey)) {
// spec: Assert that `key` is a _literal_.
if (!(key instanceof Literal)) {
formattingError("Literal expected");
}
// spec: Let `ks` be the resolved value of `key`.
String ks = ((Literal) key).value;
// spec: Let `matchpref` be the integer position of `ks` in `matches`.
matchpref = matches.indexOf(ks);
}
// spec: Set the `tuple` integer value as `matchpref`.
tuple.integer = matchpref;
}
// spec: Set `sortable` to be the result of calling the method `SortVariants(sortable)`.
sortable.sort(MFDataModelFormatter::sortVariants);
// spec: Set `i` to be `i` - 1.
i--;
}
// spec: Let `var` be the _variant_ element of the first element of `sortable`.
IntVarTuple var = sortable.get(0);
// spec: Select the _pattern_ of `var`.
patternToRender = var.variant.value;
// And should do that only once, when building the data model.
if (patternToRender == null) {
// If there was a case with all entries in the keys `*` this should not happen
throw new IllegalArgumentException(
"The selection went wrong, cannot select any option.");
}
return patternToRender;
}
/* spec:
* `SortVariants` is a method whose single argument is
* a list of (integer, _variant_) tuples.
* It returns a list of (integer, _variant_) tuples.
* Any implementation of `SortVariants` is acceptable
* as long as it satisfies the following requirements:
*
* 1. Let `sortable` be an arbitrary list of (integer, _variant_) tuples.
* 1. Let `sorted` be `SortVariants(sortable)`.
* 1. `sorted` is the result of sorting `sortable` using the following comparator:
* 1. `(i1, v1)` <= `(i2, v2)` if and only if `i1 <= i2`.
* 1. The sort is stable (pairs of tuples from `sortable` that are equal
* in their first element have the same relative order in `sorted`).
*/
private static int sortVariants(IntVarTuple o1, IntVarTuple o2) {
int result = Integer.compare(o1.integer, o2.integer);
if (result != 0) {
return result;
}
List<LiteralOrCatchallKey> v1 = o1.variant.keys;
List<LiteralOrCatchallKey> v2 = o1.variant.keys;
if (v1.size() != v2.size()) {
formattingError("The number of keys is not equal.");
}
for (int i = 0; i < v1.size(); i++) {
LiteralOrCatchallKey k1 = v1.get(i);
LiteralOrCatchallKey k2 = v2.get(i);
String s1 = k1 instanceof Literal ? ((Literal) k1).value : "*";
String s2 = k2 instanceof Literal ? ((Literal) k2).value : "*";
int cmp = s1.compareTo(s2);
if (cmp != 0) {
return cmp;
}
}
return 0;
}
/**
* spec:
* The method MatchSelectorKeys is determined by the implementation.
* It takes as arguments a resolved _selector_ value `rv` and a list of string keys `keys`,
* and returns a list of string keys in preferential order.
* The returned list MUST contain only unique elements of the input list `keys`.
* The returned list MAY be empty.
* The most-preferred key is first,
* with each successive key appearing in order by decreasing preference.
*/
@SuppressWarnings("static-method")
private List<String> matchSelectorKeys(ResolvedSelector rv, List<String> keys) {
return rv.selectorFunction.matches(rv.argument, keys, rv.options);
}
private static class ResolvedSelector {
final Object argument;
final Map<String, Object> options;
final Selector selectorFunction;
public ResolvedSelector(
Object argument, Map<String, Object> options, Selector selectorFunction) {
this.argument = argument;
this.options = new HashMap<>(options);
this.selectorFunction = selectorFunction;
}
}
private static void formattingError(String message) {
throw new IllegalArgumentException(message);
}
private FormatterFactory getFormattingFunctionFactoryByName(
Object toFormat, String functionName) {
// Get a function name from the type of the object to format
if (functionName == null || functionName.isEmpty()) {
if (toFormat == null) {
// The object to format is null, and no function provided.
return null;
}
Class<?> clazz = toFormat.getClass();
functionName = standardFunctions.getDefaultFormatterNameForType(clazz);
if (functionName == null) {
functionName = customFunctions.getDefaultFormatterNameForType(clazz);
}
if (functionName == null) {
throw new IllegalArgumentException(
"Object to format without a function, and unknown type: "
+ toFormat.getClass().getName());
}
}
FormatterFactory func = standardFunctions.getFormatter(functionName);
if (func == null) {
func = customFunctions.getFormatter(functionName);
}
return func;
}
private static Object resolveLiteralOrVariable(
LiteralOrVariableRef value,
Map<String, Object> localVars,
Map<String, Object> arguments) {
if (value instanceof Literal) {
String val = ((Literal) value).value;
Number nr = OptUtils.asNumber(val);
if (nr != null) {
return nr;
}
return val;
} else if (value instanceof VariableRef) {
String varName = ((VariableRef) value).name;
Object val = localVars.get(varName);
if (val == null) {
val = localVars.get(varName);
}
if (val == null) {
val = arguments.get(varName);
}
return val;
}
return value;
}
private static Map<String, Object> convertOptions(
Map<String, Option> options,
Map<String, Object> localVars,
Map<String, Object> arguments) {
Map<String, Object> result = new HashMap<>();
for (Option option : options.values()) {
result.put(option.name, resolveLiteralOrVariable(option.value, localVars, arguments));
}
return result;
}
/**
* Formats an expression.
*
* @param expression the expression to format
* @param variables local variables, created from declarations (`.input` and `.local`)
* @param arguments the arguments passed at runtime to be formatted (`mf.format(arguments)`)
*/
private FormattedPlaceholder formatExpression(
Expression expression, Map<String, Object> variables, Map<String, Object> arguments) {
Annotation annotation = null; // function name
String functionName = null;
Object toFormat = null;
Map<String, Object> options = new HashMap<>();
String fallbackString = "{\uFFFD}";
if (expression instanceof MFDataModel.VariableExpression) {
MFDataModel.VariableExpression varPart = (MFDataModel.VariableExpression) expression;
fallbackString = "{$" + varPart.arg.name + "}";
annotation = varPart.annotation; // function name & options
Object resolved = resolveLiteralOrVariable(varPart.arg, variables, arguments);
if (resolved instanceof FormattedPlaceholder) {
Object input = ((FormattedPlaceholder) resolved).getInput();
if (input instanceof ResolvedExpression) {
ResolvedExpression re = (ResolvedExpression) input;
toFormat = re.argument;
functionName = re.functionName;
options.putAll(re.options);
} else {
toFormat = input;
}
} else {
toFormat = resolved;
}
} else if (expression
instanceof MFDataModel.FunctionExpression) { // Function without arguments
MFDataModel.FunctionExpression fe = (FunctionExpression) expression;
fallbackString = "{:" + fe.annotation.name + "}";
annotation = fe.annotation;
} else if (expression instanceof MFDataModel.LiteralExpression) {
MFDataModel.LiteralExpression le = (LiteralExpression) expression;
annotation = le.annotation;
fallbackString = "{|" + le.arg.value + "|}";
toFormat = resolveLiteralOrVariable(le.arg, variables, arguments);
} else if (expression instanceof MFDataModel.Markup) {
// No output on markup, for now (we only format to string)
return new FormattedPlaceholder(expression, new PlainStringFormattedValue(""));
} else {
UnsupportedExpression ue = (UnsupportedExpression) expression;
char sigil = ue.annotation.source.charAt(0);
return new FormattedPlaceholder(
expression, new PlainStringFormattedValue("{" + sigil + "}"));
}
if (annotation instanceof FunctionAnnotation) {
FunctionAnnotation fa = (FunctionAnnotation) annotation;
if (functionName != null && !functionName.equals(fa.name)) {
formattingError(
"invalid function overrides, '" + functionName + "' <> '" + fa.name + "'");
}
functionName = fa.name;
Map<String, Object> newOptions = convertOptions(fa.options, variables, arguments);
options.putAll(newOptions);
} else if (annotation instanceof UnsupportedAnnotation) {
// We don't know how to format unsupported annotations
return new FormattedPlaceholder(expression, new PlainStringFormattedValue(fallbackString));
}
FormatterFactory funcFactory = getFormattingFunctionFactoryByName(toFormat, functionName);
if (funcFactory == null) {
return new FormattedPlaceholder(expression, new PlainStringFormattedValue(fallbackString));
}
Formatter ff = funcFactory.createFormatter(locale, options);
String res = ff.formatToString(toFormat, arguments);
if (res == null) {
res = fallbackString;
}
ResolvedExpression resExpression = new ResolvedExpression(toFormat, functionName, options);
return new FormattedPlaceholder(resExpression, new PlainStringFormattedValue(res));
}
static class ResolvedExpression implements Expression {
final Object argument;
final String functionName;
final Map<String, Object> options;
public ResolvedExpression(
Object argument, String functionName, Map<String, Object> options) {
this.argument = argument;
this.functionName = functionName;
this.options = options;
}
}
private Map<String, Object> resolveDeclarations(
List<MFDataModel.Declaration> declarations, Map<String, Object> arguments) {
Map<String, Object> variables = new HashMap<>();
String name;
Expression value;
if (declarations != null) {
for (Declaration declaration : declarations) {
if (declaration instanceof InputDeclaration) {
name = ((InputDeclaration) declaration).name;
value = ((InputDeclaration) declaration).value;
} else if (declaration instanceof LocalDeclaration) {
name = ((LocalDeclaration) declaration).name;
value = ((LocalDeclaration) declaration).value;
} else {
continue;
}
try {
// There it no need to succeed in solving everything.
// For example there is no problem is `$b` is not defined below:
// .local $a = {$b :number}
// {{ Hello {$user}! }}
FormattedPlaceholder fmt = formatExpression(value, variables, arguments);
// If it works, all good
variables.put(name, fmt);
} catch (Exception e) {
// It's OK to ignore the failure in this context, see comment above.
}
}
}
return variables;
}
private static class IntVarTuple {
int integer;
final Variant variant;
public IntVarTuple(int integer, Variant variant) {
this.integer = integer;
this.variant = variant;
}
}
}