com.xlrit.gears.base.function.Operators Maven / Gradle / Ivy
package com.xlrit.gears.base.function;
import java.math.BigDecimal;
import java.math.MathContext;
import java.time.LocalDate;
import java.time.OffsetDateTime;
import java.time.Period;
import java.time.temporal.Temporal;
import java.time.temporal.UnsupportedTemporalTypeException;
import java.util.*;
import java.util.function.Function;
import java.util.function.Supplier;
import ch.obermuhlner.math.big.BigDecimalMath;
import com.google.common.base.Preconditions;
import com.google.common.collect.Iterables;
import com.xlrit.gears.base.meta.HasAbsentValue;
import com.xlrit.gears.base.util.NumericUtils;
import org.threeten.extra.PeriodDuration;
import static com.google.common.base.Preconditions.checkArgument;
import static com.xlrit.gears.base.function.Optional.optional;
import static com.xlrit.gears.base.util.NumericUtils.toBigDecimal;
import static com.xlrit.gears.base.util.NumericUtils.toLong;
public class Operators {
private static final MathContext mathContext = MathContext.DECIMAL64;
// ============ converted operators ================
// case sn.Operator.EQEQ => "=="
public static Boolean eq(BigDecimal a, BigDecimal b) {
return a == null || b == null ? null : a.compareTo(b) == 0; // NOTE compareTo ignores precision, so 0.0 == 0.00
}
public static Boolean eq(Long a, Long b) {
return a == null || b == null ? null : a.longValue() == b.longValue();
}
public static Boolean eq(Number a, Number b) {
return a == null || b == null ? null : NumericUtils.compare(a, b) == 0;
}
public static Boolean eq(Object a, Object b) {
return a == null || b == null ? null : a.equals(b);
}
// case sn.Operator.NEQ => "!="
public static Boolean ne(BigDecimal a, BigDecimal b) {
return a == null || b == null ? null : a.compareTo(b) != 0; // NOTE compareTo ignores precision, so 0.0 == 0.00
}
public static Boolean ne(Long a, Long b) {
return a == null || b == null ? null : a.longValue() != b.longValue();
}
public static Boolean ne(A a, B b) {
return a == null || b == null ? null : NumericUtils.compare(a, b) != 0;
}
public static Boolean ne(Object a, Object b) {
return a == null || b == null ? null : !a.equals(b);
}
// case sn.Operator.LT => "<"
public static Boolean lt(Number a, Number b) {
return a == null || b == null ? null : NumericUtils.compare(a, b) < 0;
}
public static > Boolean lt(A a, A b) {
return optional(a, b, (x, y) -> x.compareTo(y) < 0);
}
public static Boolean lt(String a, String b) {
return optional(a, b, (x, y) -> x.compareTo(y) < 0);
}
// case sn.Operator.LE => "<="
public static Boolean le(Number a, Number b) {
return a == null || b == null ? null : NumericUtils.compare(a, b) <= 0;
}
public static > Boolean le(A a, A b) {
return optional(a, b, (x, y) -> x.compareTo(y) <= 0);
}
public static Boolean le(String a, String b) {
return optional(a, b, (x, y) -> x.compareTo(y) <= 0);
}
// case sn.Operator.GT => ">"
public static Boolean gt(Number a, Number b) {
return a == null || b == null ? null : NumericUtils.compare(a, b) > 0;
}
public static > Boolean gt(A a, A b) {
return optional(a, b, (x, y) -> x.compareTo(y) > 0);
}
public static Boolean gt(String a, String b) {
return optional(a, b, (x, y) -> x.compareTo(y) > 0);
}
// case sn.Operator.GE => ">="
public static Boolean ge(Number a, Number b) {
return a == null || b == null ? null : NumericUtils.compare(a, b) >= 0;
}
public static > Boolean ge(A a, A b) {
return optional(a, b, (x, y) -> x.compareTo(y) >= 0);
}
public static Boolean ge(String a, String b) {
return optional(a, b, (x, y) -> x.compareTo(y) >= 0);
}
// case sn.Operator.PLUS => "+"
public static BigDecimal sum(A a, B b) {
return optional(a, b, (x, y) -> sum(toBigDecimal(x), toBigDecimal(y)));
}
public static BigDecimal sum(BigDecimal a, BigDecimal b) {
return optional(a, b, BigDecimal::add);
}
public static Long sum(Integer a, Integer b) {
return optional(a, b, (x, y) -> sum(Long.valueOf(x), Long.valueOf(y)));
}
public static Long sum(Long a, Integer b) {
return optional(a, b, (x, y) -> sum(x, Long.valueOf(y)));
}
public static Long sum(Integer a, Long b) {
return optional(a, b, (x, y) -> sum(Long.valueOf(x), y));
}
public static Long sum(Long a, Long b) {
return optional(a, b, Long::sum);
}
public static A add(A a, PeriodDuration b) {
try {
return optional(a, b, (x, y) -> (A) (x.plus(y)));
} catch (UnsupportedTemporalTypeException e) {
String msg = String.format("Could not add period '%s' to %s '%s'. " + e.getMessage(), b, a.getClass().getSimpleName(), a);
throw new IllegalArgumentException(msg, e);
}
}
public static String concat(String a, String b) {
return optional(a, b, String::concat);
}
public static List add(List collection, T element) {
// TODO: what if this function is called in a loop?
List result = new ArrayList<>(collection.size() + 1);
result.addAll(collection);
result.add(element);
return result;
}
public static List concat(List collection1, List collection2) {
List result = new ArrayList<>(collection1.size() + collection2.size());
result.addAll(collection1);
result.addAll(collection2);
return result;
}
// case sn.Operator.MINUS => "-"
public static BigDecimal subtract(A a, B b) {
return subtract(toBigDecimal(a), toBigDecimal(b));
}
public static BigDecimal subtract(BigDecimal a, BigDecimal b) {
return optional(a, b, BigDecimal::subtract);
}
public static Long subtract(Integer a, Integer b) {
return optional(a, b, (x, y) -> subtract(Long.valueOf(x), Long.valueOf(y)));
}
public static Long subtract(Long a, Integer b) {
return optional(a, b, (x, y) -> subtract(x, Long.valueOf(y)));
}
public static Long subtract(Integer a, Long b) {
return optional(a, b, (x, y) -> subtract(Long.valueOf(x), y));
}
public static Long subtract(Long a, Long b) {
return optional(a, b, (x, y) -> x - y);
}
public static A subtract(A a, PeriodDuration b) {
try {
return optional(a, b, (x, y) -> (A) (x.minus(y)));
} catch (UnsupportedTemporalTypeException e) {
String msg = String.format("Could not subtract period '%s' from %s '%s'. " + e.getMessage(), b, a.getClass().getSimpleName(), a);
throw new IllegalArgumentException(msg, e);
}
}
public static PeriodDuration subtract(A a, B b) {
try {
if (a == null || b == null) return null;
return PeriodDuration.between(b, a); // NOTE the arguments order is reversed
}
catch (UnsupportedTemporalTypeException e) {
String msg = String.format("Could not subtract period '%s' from %s '%s'. " + e.getMessage(), b, a.getClass().getSimpleName(), a);
throw new IllegalArgumentException(msg, e);
}
}
public static PeriodDuration subtract(OffsetDateTime a, LocalDate b) {
if (a == null || b == null) return null;
return PeriodDuration.between(b, a); // NOTE the arguments order is reversed
}
public static PeriodDuration subtract(LocalDate a, OffsetDateTime b) {
if (a == null || b == null) return null;
return PeriodDuration.between(b, a); // NOTE the arguments order is reversed
}
// case sn.Operator.MULT => "*"
public static BigDecimal multiply(A a, B b) {
return multiply(toBigDecimal(a), toBigDecimal(b));
}
public static BigDecimal multiply(BigDecimal a, BigDecimal b) {
return optional(a, b, (x, y) -> x.multiply(y, mathContext));
}
public static Long multiply(Integer a, Integer b) {
return optional(a, b, (x, y) -> multiply(Long.valueOf(x), Long.valueOf(y)));
}
public static Long multiply(Long a, Integer b) {
return optional(a, b, (x, y) -> multiply(x, Long.valueOf(y)));
}
public static Long multiply(Integer a, Long b) {
return optional(a, b, (x, y) -> multiply(Long.valueOf(x), y));
}
public static Long multiply(Long a, Long b) {
return optional(a, b, (x, y) -> x * y);
}
public static String multiply(String a, Integer b) {
return optional(a, b, (x, y) -> multiply(x, Long.valueOf(y)));
}
public static String multiply(String a, Long b) {
return optional(a, b, (x, y) -> x.repeat(y.intValue()));
}
public static PeriodDuration multiply(PeriodDuration a, Long b) {
if (a == null || b == null) return null;
checkArgument(b >= Integer.MIN_VALUE, "Number too small to multiply a PeriodDuration with: " + b);
checkArgument(b <= Integer.MAX_VALUE, "Number too large to multiply a PeriodDuration with: " + b);
return a.multipliedBy(b.intValue());
}
// case sn.Operator.DIV => "/"
public static BigDecimal div(A a, B b) {
return div(toBigDecimal(a), toBigDecimal(b));
}
public static BigDecimal div(BigDecimal a, BigDecimal b) {
return optional(a, b, (x, y) -> x.divide(y, mathContext));
}
public static PeriodDuration div(PeriodDuration a, Long b) {
if (a == null || b == null) return null;
checkArgument(a.getPeriod().isZero(), "Division on PeriodDuration is only supported if the Period component is zero: " + a);
return PeriodDuration.of(Period.ZERO, a.getDuration().dividedBy(b));
}
// case sn.Operator.EDIV => "/"
public static BigDecimal ediv(A a, B b) {
return ediv(toBigDecimal(a), toBigDecimal(b));
}
public static BigDecimal ediv(BigDecimal a, BigDecimal b) {
return optional(a, b, BigDecimal::divideToIntegralValue);
}
public static Long ediv(Integer a, Integer b) {
return ediv(Long.valueOf(a), Long.valueOf(b));
}
public static Long ediv(Long a, Integer b) {
return ediv(a, Long.valueOf(b));
}
public static Long ediv(Integer a, Long b) {
return ediv(Long.valueOf(a), b);
}
public static Long ediv(Long a, Long b) {
return optional(a, b, (x, y) -> x / y);
}
// case sn.Operator.MOD => "%"
public static BigDecimal mod(A a, B b) {
return mod(BigDecimal.valueOf(a.doubleValue()), BigDecimal.valueOf(b.doubleValue()));
}
public static BigDecimal mod(BigDecimal a, BigDecimal b) {
return optional(a, b, BigDecimal::remainder);
}
public static Long mod(Integer a, Integer b) {
return mod(Long.valueOf(a), Long.valueOf(b));
}
public static Long mod(Long a, Integer b) {
return mod(a, Long.valueOf(b));
}
public static Long mod(Integer a, Long b) {
return mod(Long.valueOf(a), b);
}
public static Long mod(Long a, Long b) {
return optional(a, b, (x, y) -> x % y);
}
// case sn.Operator.OR => "||"
public static Boolean or(Boolean a, Boolean b) {
if (a == Boolean.TRUE) return Boolean.TRUE;
if (b == Boolean.TRUE) return Boolean.TRUE;
if (a == null || b == null) return null;
return Boolean.FALSE;
}
public static Boolean or(Boolean a, Supplier bSupplier) {
if (a == Boolean.TRUE) return Boolean.TRUE;
Boolean b = bSupplier.get();
if (b == Boolean.TRUE) return Boolean.TRUE;
if (a == null || b == null) return null;
return Boolean.FALSE;
}
// case sn.Operator.AND => "&&"
public static Boolean and(Boolean a, Boolean b) {
if (a == Boolean.FALSE) return Boolean.FALSE;
if (b == Boolean.FALSE) return Boolean.FALSE;
if (a == null || b == null) return null;
return Boolean.TRUE;
}
public static Boolean and(Boolean a, Supplier bSupplier) {
if (a == Boolean.FALSE) return Boolean.FALSE;
Boolean b = bSupplier.get();
if (b == Boolean.FALSE) return Boolean.FALSE;
if (a == null || b == null) return null;
return Boolean.TRUE;
}
// case sn.Operator.LIKE => "like"
public static Boolean like(String str, String likeRegex) {
Preconditions.checkNotNull(likeRegex);
return optional(str, DefaultFunctions.likeToRegex(likeRegex), String::matches);
}
// case sn.Operator.POW => "pow"
public static BigDecimal pow(A a, B b) {
return pow(BigDecimal.valueOf(a.doubleValue()), BigDecimal.valueOf(b.doubleValue()));
}
public static BigDecimal pow(BigDecimal a, BigDecimal b) {
return optional(a, b, (x, y) -> BigDecimalMath.pow(x, y, mathContext));
}
public static Long pow(Integer a, Integer b) {
return pow(Long.valueOf(a), Long.valueOf(b));
}
public static Long pow(Long a, Integer b) {
return pow(a, Long.valueOf(b));
}
public static Long pow(Integer a, Long b) {
return pow(Long.valueOf(a), b);
}
public static Long pow(Long a, Long b) {
return optional(a, b, (x, y) -> toLong(pow(BigDecimal.valueOf(x), y)));
}
// case sn.Operator.IN => "in"
public static Boolean in(B element, Collection collection) {
return contains(collection, element);
}
// case sn.Operator.CONTAINS => "contains"
public static Boolean contains(Collection collection, B element) {
return collection == null || element == null ? null : collection.contains(element);
}
// case sn.Operator.INTERSECTS => "intersects"
public static Boolean intersects(Collection a, Collection b) {
try {
return b.containsAll(a);
} catch (NullPointerException e) {
return null;
}
}
// case sn.Operator.OTHERWISE
public static T otherwise(T value, Supplier alternativeSupplier) {
Objects.requireNonNull(alternativeSupplier, "The alternative value supplier must not be null");
return value != null ? value : Objects.requireNonNull(alternativeSupplier.get(), "The alternative value must not be null");
}
// ========== unary functions =========
// case (MINUS, NumericType()) => getNullableMethodRef("flip")
public static BigDecimal flip(BigDecimal a) {
return optional(a, BigDecimal::negate);
}
public static Long flip(Integer a) {
return flip(Long.valueOf(a));
}
public static Long flip(Long a) {
return optional(a, x -> -1 * x);
}
// case (NOT, BooleanType) => getNullableMethodRef("not")
public static Boolean not(Boolean a) {
return optional(a, x -> !x);
}
// case (EXISTS, _) => getNullableMethodRef("exists")
public static boolean exists(Collection a) {
return a != null && !a.isEmpty();
}
public static boolean exists(A a) {
return a != null;
}
// case (NEXISTS, _) => getNullableMethodRef("nexists")
public static boolean nexists(Collection a) {
return a == null || a.isEmpty();
}
public static boolean nexists(A a) {
return a == null;
}
// ========== boolean evaluation =========
public static boolean isTrue(Boolean b) {
return b == Boolean.TRUE;
}
public static boolean isFalse(Boolean b) {
return b == Boolean.FALSE;
}
public static boolean isUndefined(Boolean b) {
return b == null;
}
// ========== kind of the-expression =========
/**
* @return null if collection is empty.
* @throws IllegalArgumentException if collection has more than 1 element.
*/
public static T only(Collection collection) {
if (collection == null || collection.isEmpty()) return null;
if (collection.size() != 1) throw new IllegalArgumentException("The collection must have exactly 1 element, but it has " + collection.size());
return collection.iterator().next();
}
/**
* @return result of alternativeSupplier if collection is empty.
* @throws IllegalArgumentException if collection has more than 1 element.
*/
public static T only(Collection collection, Supplier alternativeSupplier) {
if (collection == null || collection.isEmpty()) return alternativeSupplier.get();
if (collection.size() != 1) throw new IllegalArgumentException("The collection must have exactly 0 or 1 element, but it has " + collection.size());
return collection.iterator().next();
}
/**
* @return null if collection is empty.
*/
public static T first(Collection collection) {
if (collection == null || collection.isEmpty()) return null;
return collection.iterator().next();
}
/**
* @return result of alternativeSupplier if collection is empty.
*/
public static T first(Collection collection, Supplier alternativeSupplier) {
if (collection == null || collection.isEmpty()) return alternativeSupplier.get();
return collection.iterator().next();
}
/**
* @return null if collection is empty.
*/
public static T last(Collection collection) {
if (collection == null || collection.isEmpty()) return null;
return Iterables.getLast(collection);
}
/**
* @return result of alternativeSupplier if collection is empty.
*/
public static T last(Collection collection, Supplier alternativeSupplier) {
if (collection == null || collection.isEmpty()) return alternativeSupplier.get();
return Iterables.getLast(collection);
}
// ========== sorting supoprt =========== //
private static final Comparator> ASCENDING = Comparator.nullsLast(Comparator.naturalOrder());
private static final Comparator> DESCENDING = Comparator.nullsLast(Comparator.reverseOrder());
@SuppressWarnings("unchecked")
public static > Comparator ascending() {
return (Comparator) ASCENDING;
}
@SuppressWarnings("unchecked")
public static > Comparator descending() {
return (Comparator) DESCENDING;
}
// ========== attribute access ========= //
public static T2 access(
T1 start,
Function access) {
return chain(start, access);
}
public static T3 access(
T1 start,
Function access1,
Function access2) {
return chain(start, access1, access2);
}
public static T4 access(
T1 start,
Function access1,
Function access2,
Function access3) {
return chain(start, access1, access2, access3);
}
public static T5 access(
T1 start,
Function access1,
Function access2,
Function access3,
Function access4) {
return chain(start, access1, access2, access3, access4);
}
public static T6 access(
T1 start,
Function access1,
Function access2,
Function access3,
Function access4,
Function access5) {
return chain(start, access1, access2, access3, access4, access5);
}
public static T7 access(
T1 start,
Function access1,
Function access2,
Function access3,
Function access4,
Function access5,
Function access6) {
return chain(start, access1, access2, access3, access4, access5, access6);
}
public static T8 access(
T1 start,
Function access1,
Function access2,
Function access3,
Function access4,
Function access5,
Function access6,
Function access7) {
return chain(start, access1, access2, access3, access4, access5, access6, access7);
}
@SuppressWarnings({"rawtypes", "unchecked"})
private static T chain(Object start, Function... accessors) {
Object current = start;
for (Function accessor : accessors) {
if (current == null) break;
current = accessor.apply(current);
}
return (T)(current == null ? absentValue(getLast(accessors)) : current);
}
private static T getLast(T[] array) {
if (array == null || array.length == 0) throw new IllegalArgumentException("The array must have at least one element");
return array[array.length - 1];
}
private static T2 absentValue(Function access) {
if (access instanceof HasAbsentValue> hasAbsentValue) {
//noinspection unchecked
return (T2) hasAbsentValue.absentValue();
}
return null;
}
}