OptUtils.java
// © 2022 and later: Unicode, Inc. and others.
// License & terms of use: https://www.unicode.org/copyright.html
package com.ibm.icu.message2;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
class OptUtils {
// This matches JSON number (https://www.rfc-editor.org/rfc/rfc8259#section-6)
//
// WARNING: this is not in the MF2 spec/message.abnf anymore.
// Any concept of "number literal" was removed from there.
//
// This is used to validate options and arguments, for example `maxDigits=|1.|`,
// or `{|01| :number}` and by the time it gets to the checking the string literal was extracted
// by the parser and we only see "1." and "01".
private static final Pattern RE_NUMBER_LITERAL =
Pattern.compile("^-?(0|[1-9][0-9]*)(\\.[0-9]+)?([eE][+\\-]?[0-9]+)?$");
private OptUtils() {}
static Number asNumber(Object value) {
if (value instanceof Number) {
return (Number) value;
}
if (value instanceof CharSequence) {
try {
Matcher m = RE_NUMBER_LITERAL.matcher(value.toString());
if (m.find()) {
return Double.parseDouble(value.toString());
}
} catch (NumberFormatException e) {
/* just ignore, we continue and report */
}
}
return null;
}
static Number asNumber(boolean reportErrors, String keyName, Object value) {
if (value instanceof Number) {
return (Number) value;
}
if (value instanceof CharSequence) {
try {
Matcher m = RE_NUMBER_LITERAL.matcher(value.toString());
if (m.find()) {
return Double.parseDouble(value.toString());
}
} catch (NumberFormatException e) {
/* just ignore, we continue and report */
}
}
if (reportErrors) {
throw new IllegalArgumentException("bad-operand: " + keyName + " must be numeric");
}
return null;
}
static Integer getInteger(Map<String, Object> options, boolean reportErrors, String key) {
Object value = options.get(key);
if (value == null) {
return null;
}
Number nrValue = asNumber(reportErrors, key, value);
if (nrValue != null) {
return nrValue.intValue();
}
return null;
}
static String getString(Map<String, Object> options, String key, String defaultVal) {
Object value = options.get(key);
if (value instanceof CharSequence) {
return value.toString();
}
return defaultVal;
}
static String getString(Map<String, Object> options, String key) {
return getString(options, key, null);
}
static boolean reportErrors(Map<String, Object> options) {
String reportErrors = getString(options, "icu:impl:errorPolicy");
return Objects.equals(reportErrors, "STRICT");
}
static boolean reportErrors(
Map<String, Object> fixedOptions, Map<String, Object> variableOptions) {
return reportErrors(fixedOptions) || reportErrors(variableOptions);
}
static Locale getBestLocale(Map<String, Object> options, Locale defaultValue) {
Locale result = null;
String localeOverride = getString(options, "u:locale");
if (localeOverride != null) {
try {
result = Locale.forLanguageTag(localeOverride.replace('_', '-'));
} catch (Exception e) {
if (reportErrors(options)) {
throw new IllegalArgumentException(
"bad-operand: u:locale must be a valid BCP 47 language tag");
}
}
}
if (result == null) {
if (defaultValue == null) {
result = Locale.getDefault();
} else {
result = defaultValue;
}
}
return result;
}
static Directionality getBestDirectionality(Map<String, Object> options, Locale locale) {
Directionality result = getDirectionality(options);
return result == Directionality.UNKNOWN ? Directionality.of(locale) : result;
}
static Directionality getDirectionality(Map<String, Object> options) {
String value = getString(options, "u:dir");
if (value == null) {
return Directionality.UNKNOWN;
}
Directionality result;
switch (value) {
case "rtl":
result = Directionality.RTL;
break;
case "ltr":
result = Directionality.LTR;
break;
case "auto":
result = Directionality.AUTO;
break;
case "inherit":
result = Directionality.INHERIT;
break;
default:
result = Directionality.UNKNOWN;
break;
}
return result;
}
static String getUId(Map<String, Object> options) {
return getString(options, "u:id");
}
}