com.xlrit.gears.base.function.DefaultFunctions Maven / Gradle / Ivy
package com.xlrit.gears.base.function;
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.text.ParseException;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.*;
import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.LongStream;
import java.util.stream.Stream;
import ch.obermuhlner.math.big.BigDecimalMath;
import com.google.common.base.Preconditions;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.xlrit.gears.base.content.ContentRef;
import com.xlrit.gears.base.exception.SpecException;
import com.xlrit.gears.base.execution.Execution;
import com.xlrit.gears.base.id.IdGenerator;
import com.xlrit.gears.base.id.SequenceValueProducer;
import com.xlrit.gears.base.model.Document;
import com.xlrit.gears.base.model.User;
import com.xlrit.gears.base.util.PasswordEncoderHolder;
import com.xlrit.gears.base.util.PeriodDurationHelper;
import com.xlrit.gears.base.util.StringUtils;
import com.xlrit.gears.base.util.TemporalUtils;
import jakarta.annotation.Nullable;
import lombok.SneakyThrows;
import org.apache.commons.text.StringEscapeUtils;
import org.apache.commons.text.WordUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.threeten.extra.Months;
import org.threeten.extra.PeriodDuration;
import org.threeten.extra.YearWeek;
import static com.xlrit.gears.base.function.Optional.optional;
import static com.xlrit.gears.base.util.ErrorUtils.inputError;
import static com.xlrit.gears.base.util.ErrorUtils.languageError;
import static com.xlrit.gears.base.util.StringUtils.toLocale;
import static com.xlrit.gears.base.util.StringUtils.toSnakeCase;
import static com.xlrit.gears.base.util.TemporalUtils.*;
public class DefaultFunctions {
private static final Logger LOG = LoggerFactory.getLogger(DefaultFunctions.class);
// === type conversion functions === //
// locales
private static final String defaultLanguageTag = "en";
private static final Locale defaultLocale = toLocale(defaultLanguageTag);
// numbers
private static final String defaultNumberPattern = "###0.###";
private static final DecimalFormat defaultNumberFormatter = new DecimalFormat(defaultNumberPattern, DecimalFormatSymbols.getInstance(defaultLocale));
// date/times
public static final String defaultDatePattern = "uuuu-MM-dd";
public static final String defaultTimePattern = "HH:mm:ss";
public static final String defaultDateTimePattern = defaultDatePattern + "'T'" + defaultTimePattern + "X";
private static final DateTimeFormatter defaultDateFormatter = DateTimeFormatter.ofPattern(defaultDatePattern);
private static final DateTimeFormatter defaultTimeFormatter = DateTimeFormatter.ofPattern(defaultTimePattern);
private static final DateTimeFormatter defaultDateTimeFormatter = DateTimeFormatter.ofPattern(defaultDateTimePattern);
@FunctionDef(category = "conversion")
public static boolean isBoolean(String text) {
if (text == null) return false;
String upperText = text.toUpperCase();
return "TRUE".equals(upperText) || "FALSE".equals(upperText);
}
@FunctionDef(category = "conversion")
public static Boolean toBoolean(Boolean optionalPredicate) {
// TODO: this should break when optionalPredicate is null.
return Objects.requireNonNullElse(optionalPredicate, false);
}
@FunctionDef(category = "conversion")
public static Boolean toBoolean(String value) {
return toBoolean(value, defaultLanguageTag);
}
@FunctionDef(category = "conversion")
public static Boolean toBoolean(String value, String languageTag) {
return toBoolean(value, toLocale(languageTag));
}
private static Boolean toBoolean(String value, Locale locale) {
return switch (locale.getLanguage()) {
case "en" -> toBoolean(value, "true", "false");
case "nl" -> toBoolean(value, "waar", "onwaar");
default -> throw languageError("toBoolean", locale.getLanguage());
};
}
private static Boolean toBoolean(String value, String trueString, String falseString) {
return toBoolean(value, trueString::equalsIgnoreCase, falseString::equalsIgnoreCase);
}
private static Boolean toBoolean(T value, Function trueLambda, Function falseLambda) {
if (value == null)
throw inputError(value, "toBoolean", "Boolean");
else if (trueLambda.apply(value))
return true;
else if (falseLambda.apply(value))
return false;
else {
throw inputError(value, "toBoolean", "Boolean");
}
}
@FunctionDef(category = "conversion")
public static Boolean toBoolean(long value) {
return toBoolean(value, (i) -> i == 1, (i) -> i == 0);
}
@FunctionDef(category = "conversion")
public static boolean isInteger(String value) {
if (value == null) return false;
try {
Long.parseLong(value);
return true;
}
catch (NumberFormatException e) {
return false;
}
}
@FunctionDef(category = "conversion")
public static long toInteger(String value) {
return toInteger(value, defaultLanguageTag);
}
@FunctionDef(category = "conversion")
public static long toInteger(String value, String languageTag) {
try {
NumberFormat nf = NumberFormat.getInstance(toLocale(languageTag));
Number n = nf.parse(normalizeNumber(value));
if (n.doubleValue() % 1 != 0)
throw inputError(value, "toInteger", "integer");
return n.longValue();
}
catch (ParseException e) {
throw inputError(value, "toInteger", "integer", e);
}
}
@FunctionDef(category = "conversion")
public static BigDecimal toDecimal(Long value) {
return BigDecimal.valueOf(value);
}
@FunctionDef(category = "conversion")
public static boolean isDecimal(String value) {
if (value == null) return false;
try {
toDecimal_raw(value, defaultLanguageTag);
return true;
}
catch (ParseException e) {
return false;
}
}
@FunctionDef(category = "conversion")
public static BigDecimal toDecimal(String value) {
return toDecimal(value, defaultLanguageTag);
}
@FunctionDef(category = "conversion")
public static BigDecimal toDecimal(String value, String languageTag) {
try {
return toDecimal_raw(value, languageTag);
}
catch (ParseException e) {
throw inputError(value, "toDecimal", "decimal", e);
}
}
public static BigDecimal toDecimal_raw(String value, String languageTag) throws ParseException {
var df = (DecimalFormat) NumberFormat.getInstance(toLocale(languageTag));
df.setParseBigDecimal(true);
return (BigDecimal) df.parse(normalizeNumber(value));
}
@FunctionDef(category = "conversion")
public static boolean isDate(String dateText) {
if (dateText == null) return false;
try {
TemporalUtils.parseDate(dateText);
return true;
}
catch (Exception e) {
return false;
}
}
@FunctionDef(category = "conversion")
public static LocalDate toDate(String dateText) {
try {
return TemporalUtils.parseDate(dateText);
}
catch (Exception e) {
throw inputError(dateText, "toDate", "date", e);
}
}
@FunctionDef(category = "conversion")
public static LocalDate toDate(String dateText, String pattern, String languageTag) {
try {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern, toLocale(languageTag));
return TemporalUtils.parseDate(dateText, formatter);
}
catch (Exception e) {
throw inputError(dateText, "toDate", "date", e);
}
}
@FunctionDef(category = "conversion")
public static boolean isTime(String timeText) {
if (timeText == null) return false;
try {
TemporalUtils.parseTime(timeText);
return true;
}
catch (Exception e) {
return false;
}
}
@FunctionDef(category = "conversion")
public static LocalTime toTime(String timeText) {
try {
return TemporalUtils.parseTime(timeText);
}
catch (Exception e) {
throw inputError(timeText, "toTime", "time", e);
}
}
@FunctionDef(category = "conversion")
public static LocalTime toTime(String timeText, String pattern) {
try {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern, toLocale(defaultLanguageTag));
return TemporalUtils.parseTime(timeText, formatter);
}
catch (Exception e) {
throw inputError(timeText, "toTime", "time", e);
}
}
@FunctionDef(category = "conversion")
public static boolean isDatetime(String dateTimeText) {
if (dateTimeText == null) return false;
try {
TemporalUtils.parseDateTime(dateTimeText);
return true;
}
catch (Exception e) {
return false;
}
}
@FunctionDef(category = "conversion")
public static OffsetDateTime toDatetime(String dateTimeText) {
try {
return TemporalUtils.parseDateTime(dateTimeText);
}
catch (Exception e) {
throw inputError(dateTimeText, "toDatetime", "datetime", e);
}
}
@FunctionDef(category = "conversion")
public static OffsetDateTime toDatetime(String dateTimeText, String pattern, String languageTag) {
try {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern, toLocale(languageTag));
return TemporalUtils.parseDateTime(dateTimeText, formatter);
}
catch (Exception e) {
throw inputError(dateTimeText, "toDateTime", "datetime", e);
}
}
@FunctionDef(category = "conversion")
public static OffsetDateTime toDatetime(LocalDate date, LocalTime time, String zoneName) {
ZoneId zoneId = ZoneId.of(zoneName);
return date.atTime(time).atZone(zoneId).toOffsetDateTime();
}
@FunctionDef(category = "conversion")
public static boolean isPeriod(String periodText) {
if (periodText == null) return false;
return PeriodDurationHelper.forLanguageTag(defaultLanguageTag).isValid(periodText);
}
@FunctionDef(category = "conversion")
public static PeriodDuration toPeriod(String periodText) {
return toPeriod(periodText, defaultLanguageTag);
}
@FunctionDef(category = "conversion")
public static PeriodDuration toPeriod(String periodText, String languageTag) {
try {
return PeriodDurationHelper.forLanguageTag(languageTag).parse(periodText);
}
catch (Exception e) {
throw inputError(periodText, "toPeriod", "period", e);
}
}
@FunctionDef(category = "conversion")
public static String toText(PeriodDuration period) {
return toText(period, defaultLanguageTag);
}
@FunctionDef(category = "conversion")
public static String toText(PeriodDuration value, String languageTag) {
return PeriodDurationHelper.forLanguageTag(languageTag).format(value);
}
@FunctionDef(category = "conversion")
public static String toText(PeriodDuration value, String languageTag, String format) {
return PeriodDurationHelper.forLanguageTag(languageTag).format(value, format);
}
@FunctionDef(category = "conversion")
public static String toText(Boolean value) {
return toText(value, defaultLanguageTag);
}
@FunctionDef(category = "conversion")
public static String toText(Boolean value, String languageTag) {
Locale locale = toLocale(languageTag);
return switch (locale.getLanguage()) {
case "en" -> value ? "true" : "false";
case "nl" -> value ? "waar" : "onwaar";
default -> throw languageError("toText", locale.getLanguage());
};
}
@FunctionDef(category = "conversion")
public static String toText(Number value) {
return defaultNumberFormatter.format(value);
}
@FunctionDef(category = "conversion")
public static String toText(Number value, String pattern) {
return toText(value, pattern, defaultLanguageTag);
}
@FunctionDef(category = "conversion")
public static String toText(Number value, String pattern, String languageTag) {
DecimalFormat formatter = new DecimalFormat(pattern, new DecimalFormatSymbols(toLocale(languageTag)));
return formatter.format(value);
}
@FunctionDef(category = "conversion")
public static String toText(@Nullable LocalDate date) {
if (date == null) return null;
return date.format(defaultDateFormatter);
}
@FunctionDef(category = "conversion")
public static String toText(@Nullable LocalDate date, String pattern) {
if (date == null) return null;
return tryToText(date, () -> date.format(DateTimeFormatter.ofPattern(pattern)));
}
@FunctionDef(category = "conversion")
public static String toText(@Nullable LocalDate date, String pattern, String languageTag) {
if (date == null) return null;
return tryToText(date, () -> date.format(DateTimeFormatter.ofPattern(pattern, toLocale(languageTag))));
}
@FunctionDef(category = "conversion")
public static String toText(LocalTime time) {
return time.format(defaultTimeFormatter);
}
@FunctionDef(category = "conversion")
public static String toText(LocalTime time, String pattern) {
return tryToText(time, () -> time.format(DateTimeFormatter.ofPattern(pattern)));
}
@FunctionDef(category = "conversion")
public static String toText(LocalTime time, String pattern, String languageTag) {
return tryToText(time, () -> time.format(DateTimeFormatter.ofPattern(pattern, toLocale(languageTag))));
}
@FunctionDef(category = "conversion")
public static String toText(OffsetDateTime dateTime) {
return dateTime.format(defaultDateTimeFormatter);
}
@FunctionDef(category = "conversion")
public static String toText(OffsetDateTime dateTime, String pattern) {
return tryToText(dateTime, () -> dateTime.format(DateTimeFormatter.ofPattern(pattern)));
}
@FunctionDef(category = "conversion")
public static String toText(OffsetDateTime dateTime, String pattern, String languageTag) {
return tryToText(dateTime, () -> dateTime.format(DateTimeFormatter.ofPattern(pattern, toLocale(languageTag))));
}
@FunctionDef(category = "conversion")
public static String toText(Object o) { return tryToText(o, () -> String.valueOf(o)); }
/** Produce a proper error message for the toText functions. */
private static String tryToText(T value, Supplier f) {
try {
return f.get();
} catch (Exception e) {
throw inputError(value, toSnakeCase("toText"), "text value", e);
}
}
@FunctionDef(category = "conversion")
public static String filename(ContentRef contentRef) {
return contentRef.getFilename();
}
@FunctionDef(category = "conversion")
public static ContentRef toFile(Object object) {
return anyToFile(object);
}
@FunctionDef(category = "conversion")
public static List toFiles(List