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

206 lines
8.4 KiB
Java
Raw Permalink Normal View History

2025-01-20 15:15:20 +00:00
/* GENERATED SOURCE. DO NOT MODIFY. */
// © 2024 and later: Unicode, Inc. and others.
// License & terms of use: https://www.unicode.org/copyright.html
package android.icu.message2;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.StringJoiner;
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.PatternMessage;
import android.icu.message2.MFDataModel.SelectMessage;
import android.icu.message2.MFDataModel.VariableExpression;
import android.icu.message2.MFDataModel.VariableRef;
import android.icu.message2.MFDataModel.Variant;
// I can merge all this in the MFDataModel class and make it private
class MFDataModelValidator {
private final MFDataModel.Message message;
private final Set<String> declaredVars = new HashSet<>();
MFDataModelValidator(MFDataModel.Message message) {
this.message = message;
}
boolean validate() throws MFParseException {
if (message instanceof PatternMessage) {
validateDeclarations(((PatternMessage) message).declarations);
} else if (message instanceof SelectMessage) {
SelectMessage sm = (SelectMessage) message;
validateDeclarations(sm.declarations);
validateSelectors(sm.selectors);
int selectorCount = sm.selectors.size();
validateVariants(sm.variants, selectorCount);
}
return true;
}
private boolean validateVariants(List<Variant> variants, int selectorCount)
throws MFParseException {
if (variants == null || variants.isEmpty()) {
error("Selection messages must have at least one variant");
}
// Look for an entry with all keys = '*'
boolean hasUltimateFallback = false;
Set<String> fakeKeys = new HashSet<>();
for (Variant variant : variants) {
if (variant.keys == null || variant.keys.isEmpty()) {
error("Selection variants must have at least one key");
}
if (variant.keys.size() != selectorCount) {
error("Selection variants must have the same number of variants as the selectors.");
}
int catchAllCount = 0;
StringJoiner fakeKey = new StringJoiner("<<::>>");
for (LiteralOrCatchallKey key : variant.keys) {
if (key instanceof CatchallKey) {
catchAllCount++;
fakeKey.add("*");
} else if (key instanceof Literal) {
fakeKey.add(((Literal) key).value);
}
}
if (fakeKeys.contains(fakeKey.toString())) {
error("Dumplicate combination of keys");
} else {
fakeKeys.add(fakeKey.toString());
}
if (catchAllCount == selectorCount) {
hasUltimateFallback = true;
}
}
if (!hasUltimateFallback) {
error("There must be one variant with all the keys being '*'");
}
return true;
}
private boolean validateSelectors(List<Expression> selectors) throws MFParseException {
if (selectors == null || selectors.isEmpty()) {
error("Selection messages must have selectors");
}
return true;
}
/*
* .input {$foo :number} .input {$foo} => ERROR
* .input {$foo :number} .local $foo={$bar} => ERROR, local foo overrides an input
* .local $foo={...} .local $foo={...} => ERROR, foo declared twice
* .local $a={$foo} .local $b={$foo} => NOT AN ERROR (foo is used, not declared)
* .local $a={:f opt=$foo} .local $foo={$foo} => ERROR, foo declared after beeing used in opt
*/
private boolean validateDeclarations(List<Declaration> declarations) throws MFParseException {
if (declarations == null || declarations.isEmpty()) {
return true;
}
for (Declaration declaration : declarations) {
if (declaration instanceof LocalDeclaration) {
LocalDeclaration ld = (LocalDeclaration) declaration;
validateExpression(ld.value, false);
addVariableDeclaration(ld.name);
} else if (declaration instanceof InputDeclaration) {
InputDeclaration id = (InputDeclaration) declaration;
validateExpression(id.value, true);
}
}
return true;
}
/*
* One might also consider checking if the same variable is used with more than one type:
* .local $a = {$foo :number}
* .local $b = {$foo :string}
* .local $c = {$foo :datetime}
*
* But this is not necesarily an error.
* If $foo is a number, then it might be formatter as a number, or as date (epoch time),
* or something else.
*
* So it is not safe to complain. Especially with custom functions:
* # get the first name from a `Person` object
* .local $b = {$person :getField fieldName=firstName}
* # get formats a `Person` object
* .local $b = {$person :person}
*/
private void validateExpression(Expression expression, boolean fromInput)
throws MFParseException {
String argName = null;
Annotation annotation = null;
if (expression instanceof Literal) {
// ...{foo}... or ...{|foo|}... or ...{123}...
// does not declare anything
} else if (expression instanceof LiteralExpression) {
LiteralExpression le = (LiteralExpression) expression;
argName = le.arg.value;
annotation = le.annotation;
} else if (expression instanceof VariableExpression) {
VariableExpression ve = (VariableExpression) expression;
// ...{$foo :bar opt1=|str| opt2=$x opt3=$y}...
// .input {$foo :number} => declares `foo`, if already declared is an error
// .local $a={$foo} => declares `a`, but only used `foo`, does not declare it
argName = ve.arg.name;
annotation = ve.annotation;
} else if (expression instanceof FunctionExpression) {
// ...{$foo :bar opt1=|str| opt2=$x opt3=$y}...
FunctionExpression fe = (FunctionExpression) expression;
annotation = fe.annotation;
}
if (annotation instanceof FunctionAnnotation) {
FunctionAnnotation fa = (FunctionAnnotation) annotation;
if (fa.options != null) {
for (Option opt : fa.options.values()) {
LiteralOrVariableRef val = opt.value;
if (val instanceof VariableRef) {
// We had something like {:f option=$val}, it means we's seen `val`
// It is not a declaration, so not an error.
addVariableDeclaration(((VariableRef) val).name);
}
}
}
}
// We chech the argument name after options to prevent errors like this:
// .local $foo = {$a :b option=$foo}
if (argName != null) {
// if we come from `.input {$foo :function}` then `varName` is null
// and `argName` is `foo`
if (fromInput) {
addVariableDeclaration(argName);
} else {
// Remember that we've seen it, to complain if there is a declaration later
declaredVars.add(argName);
}
}
}
private boolean addVariableDeclaration(String varName) throws MFParseException {
if (declaredVars.contains(varName)) {
error("Variable '" + varName + "' already declared");
return false;
}
declaredVars.add(varName);
return true;
}
private void error(String text) throws MFParseException {
throw new MFParseException(text, -1);
}
}