OperationFunctions.java

/*
 * Portions of this software was developed by employees of the National Institute
 * of Standards and Technology (NIST), an agency of the Federal Government and is
 * being made available as a public service. Pursuant to title 17 United States
 * Code Section 105, works of NIST employees are not subject to copyright
 * protection in the United States. This software may be subject to foreign
 * copyright. Permission in the United States and in foreign countries, to the
 * extent that NIST may hold copyright, to use, copy, modify, create derivative
 * works, and distribute this software and its documentation without fee is hereby
 * granted on a non-exclusive basis, provided that this notice and disclaimer
 * of warranty appears in all copies.
 *
 * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER
 * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY
 * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM
 * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE
 * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE.  IN NO EVENT
 * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT,
 * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM,
 * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY,
 * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR
 * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT
 * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER.
 */

package gov.nist.secauto.metaschema.core.metapath.function; // NOPMD - intentional

import gov.nist.secauto.metaschema.core.metapath.InvalidTypeMetapathException;
import gov.nist.secauto.metaschema.core.metapath.item.atomic.IBase64BinaryItem;
import gov.nist.secauto.metaschema.core.metapath.item.atomic.IBooleanItem;
import gov.nist.secauto.metaschema.core.metapath.item.atomic.IDateItem;
import gov.nist.secauto.metaschema.core.metapath.item.atomic.IDateTimeItem;
import gov.nist.secauto.metaschema.core.metapath.item.atomic.IDayTimeDurationItem;
import gov.nist.secauto.metaschema.core.metapath.item.atomic.IDecimalItem;
import gov.nist.secauto.metaschema.core.metapath.item.atomic.IDurationItem;
import gov.nist.secauto.metaschema.core.metapath.item.atomic.IIntegerItem;
import gov.nist.secauto.metaschema.core.metapath.item.atomic.INumericItem;
import gov.nist.secauto.metaschema.core.metapath.item.atomic.IYearMonthDurationItem;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.Duration;
import java.time.Period;
import java.time.ZonedDateTime;
import java.time.temporal.TemporalAmount;

import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;

public final class OperationFunctions { // NOPMD - intentional
  private OperationFunctions() {
    // disable
  }

  @NonNull
  public static IDateItem opAddYearMonthDurationToDate(@NonNull IDateItem arg1, @NonNull IYearMonthDurationItem arg2) {
    return addDurationToDate(arg1.asZonedDateTime(), arg2.getValue());
  }

  @NonNull
  public static IDateItem opAddDayTimeDurationToDate(@NonNull IDateItem arg1, @NonNull IDayTimeDurationItem arg2) {
    return addDurationToDate(arg1.asZonedDateTime(), arg2.getValue());
  }

  @NonNull
  private static IDateItem addDurationToDate(@NonNull ZonedDateTime dateTime, @NonNull TemporalAmount duration) {
    ZonedDateTime result;
    try {
      result = dateTime.plus(duration);
    } catch (ArithmeticException ex) {
      throw new ArithmeticFunctionException(ArithmeticFunctionException.OVERFLOW_UNDERFLOW_ERROR, ex);
    }
    assert result != null;
    return IDateItem.valueOf(result);
  }

  @NonNull
  public static IYearMonthDurationItem opAddYearMonthDurations(@NonNull IYearMonthDurationItem arg1,
      IYearMonthDurationItem arg2) {
    Period duration1 = arg1.getValue();
    Period duration2 = arg2.getValue();

    Period result;
    try {
      result = duration1.plus(duration2);
    } catch (ArithmeticException ex) {
      throw new ArithmeticFunctionException(ArithmeticFunctionException.OVERFLOW_UNDERFLOW_ERROR, ex);
    }
    assert result != null;
    return IYearMonthDurationItem.valueOf(result);
  }

  @NonNull
  public static IDayTimeDurationItem opAddDayTimeDurations(@NonNull IDayTimeDurationItem arg1,
      @NonNull IDayTimeDurationItem arg2) {
    Duration duration1 = arg1.getValue();
    Duration duration2 = arg2.getValue();

    Duration result;
    try {
      result = duration1.plus(duration2);
    } catch (ArithmeticException ex) {
      throw new ArithmeticFunctionException(ArithmeticFunctionException.OVERFLOW_UNDERFLOW_ERROR, ex);
    }
    assert result != null;
    return IDayTimeDurationItem.valueOf(result);
  }

  @NonNull
  public static IDateTimeItem opAddYearMonthDurationToDateTime(@NonNull IDateTimeItem arg1,
      @NonNull IYearMonthDurationItem arg2) {
    ZonedDateTime result;
    try {
      result = arg1.asZonedDateTime().plus(arg2.getValue());
    } catch (ArithmeticException ex) {
      throw new ArithmeticFunctionException(ArithmeticFunctionException.OVERFLOW_UNDERFLOW_ERROR, ex);
    }
    assert result != null;
    return IDateTimeItem.valueOf(result);
  }

  @NonNull
  public static IDateTimeItem opAddDayTimeDurationToDateTime(@NonNull IDateTimeItem arg1,
      @NonNull IDayTimeDurationItem arg2) {
    ZonedDateTime result;
    try {
      result = arg1.asZonedDateTime().plus(arg2.getValue());
    } catch (ArithmeticException ex) {
      throw new ArithmeticFunctionException(ArithmeticFunctionException.OVERFLOW_UNDERFLOW_ERROR, ex);
    }
    assert result != null;
    return IDateTimeItem.valueOf(result);
  }

  @NonNull
  public static IDayTimeDurationItem opSubtractDates(@NonNull IDateItem arg1, @NonNull IDateItem arg2) {
    return between(arg1.asZonedDateTime(), arg2.asZonedDateTime());
  }

  @NonNull
  public static IDateItem opSubtractYearMonthDurationFromDate(@NonNull IDateItem arg1,
      @NonNull IYearMonthDurationItem arg2) {
    return subtractDurationFromDate(arg1.asZonedDateTime(), arg2.getValue());
  }

  @NonNull
  public static IDateItem opSubtractDayTimeDurationFromDate(@NonNull IDateItem arg1,
      @NonNull IDayTimeDurationItem arg2) {
    return subtractDurationFromDate(arg1.asZonedDateTime(), arg2.getValue());
  }

  @NonNull
  private static IDateItem subtractDurationFromDate(@NonNull ZonedDateTime dateTime,
      @NonNull TemporalAmount duration) {
    @SuppressWarnings("null")
    @NonNull ZonedDateTime result = dateTime.minus(duration);
    return IDateItem.valueOf(result);
  }

  @NonNull
  public static IYearMonthDurationItem opSubtractYearMonthDurations(@NonNull IYearMonthDurationItem arg1,
      IYearMonthDurationItem arg2) {
    Period duration1 = arg1.getValue();
    Period duration2 = arg2.getValue();

    @SuppressWarnings("null")
    @NonNull Period duration = duration1.minus(duration2);
    return IYearMonthDurationItem.valueOf(duration);
  }

  @NonNull
  public static IDayTimeDurationItem opSubtractDayTimeDurations(@NonNull IDayTimeDurationItem arg1,
      @NonNull IDayTimeDurationItem arg2) {
    Duration duration1 = arg1.getValue();
    Duration duration2 = arg2.getValue();

    @SuppressWarnings("null")
    @NonNull Duration duration = duration1.minus(duration2);
    return IDayTimeDurationItem.valueOf(duration);
  }

  @NonNull
  public static IDayTimeDurationItem opSubtractDateTimes(@NonNull IDateTimeItem arg1, @NonNull IDateTimeItem arg2) {
    return between(arg1.asZonedDateTime(), arg2.asZonedDateTime());
  }

  @NonNull
  private static IDayTimeDurationItem between(@NonNull ZonedDateTime time1, @NonNull ZonedDateTime time2) {
    @SuppressWarnings("null")
    @NonNull Duration between = Duration.between(time1, time2);
    return IDayTimeDurationItem.valueOf(between);
  }

  @NonNull
  public static IDateTimeItem opSubtractYearMonthDurationFromDateTime(@NonNull IDateTimeItem arg1,
      @NonNull IYearMonthDurationItem arg2) {
    @SuppressWarnings("null")
    @NonNull ZonedDateTime dateTime = arg1.asZonedDateTime().minus(arg2.getValue());
    return IDateTimeItem.valueOf(dateTime);
  }

  @NonNull
  public static IDateTimeItem opSubtractDayTimeDurationFromDateTime(@NonNull IDateTimeItem arg1,
      @NonNull IDayTimeDurationItem arg2) {

    @SuppressWarnings("null")
    @NonNull ZonedDateTime dateTime = arg1.asZonedDateTime().plus(arg2.getValue());
    return IDateTimeItem.valueOf(dateTime);
  }

  @NonNull
  public static IYearMonthDurationItem opMultiplyYearMonthDuration(@NonNull IYearMonthDurationItem arg1,
      @NonNull INumericItem arg2)
      throws ArithmeticFunctionException {
    int arg2Int;
    try {
      arg2Int = FunctionUtils.asInteger(arg2.round());
    } catch (ArithmeticException ex) {
      throw new ArithmeticFunctionException(ArithmeticFunctionException.OVERFLOW_UNDERFLOW_ERROR, ex);
    }

    @SuppressWarnings("null")
    @NonNull Period period = arg1.getValue().multipliedBy(arg2Int);
    return IYearMonthDurationItem.valueOf(period);
  }

  @NonNull
  public static IDayTimeDurationItem opMultiplyDayTimeDuration(@NonNull IDayTimeDurationItem arg1,
      @NonNull INumericItem arg2)
      throws ArithmeticFunctionException {
    long arg2Long;
    try {
      arg2Long = FunctionUtils.asLong(arg2.round());
    } catch (ArithmeticException ex) {
      throw new ArithmeticFunctionException(ArithmeticFunctionException.OVERFLOW_UNDERFLOW_ERROR, ex);
    }

    @SuppressWarnings("null")
    @NonNull Duration duration = arg1.getValue().multipliedBy(arg2Long);
    return IDayTimeDurationItem.valueOf(duration);
  }

  @NonNull
  public static IYearMonthDurationItem opDivideYearMonthDuration(@NonNull IYearMonthDurationItem arg1,
      @NonNull INumericItem arg2)
      throws DateTimeFunctionException {
    IIntegerItem totalMonths = IIntegerItem.valueOf(arg1.getValue().toTotalMonths());
    IIntegerItem result = opNumericIntegerDivide(totalMonths, arg2);
    int months;
    try {
      months = FunctionUtils.asInteger(result.asInteger());
    } catch (ArithmeticException ex) {
      throw new DateTimeFunctionException(DateTimeFunctionException.DURATION_OVERFLOW_UNDERFLOW_ERROR,
          "Overflow/underflow in duration operation.", ex);
    }
    int years = months / 12;
    months = months % 12;
    return IYearMonthDurationItem.valueOf(years, months, 0);
  }

  @NonNull
  public static IDayTimeDurationItem opDivideDayTimeDuration(@NonNull IDayTimeDurationItem arg1,
      @NonNull INumericItem arg2)
      throws ArithmeticFunctionException {
    try {
      @SuppressWarnings("null")
      @NonNull Duration duration = arg1.getValue().dividedBy(FunctionUtils.asLong(arg2.round()));
      return IDayTimeDurationItem
          .valueOf(duration);
    } catch (ArithmeticException ex) {
      throw new ArithmeticFunctionException(ArithmeticFunctionException.DIVISION_BY_ZERO, "Division by zero", ex);
    }
  }

  @NonNull
  public static IDecimalItem opDivideDayTimeDurationByDayTimeDuration(@NonNull IDayTimeDurationItem arg1,
      IDayTimeDurationItem arg2) {
    return IDecimalItem.cast(
        opNumericDivide(
            IDecimalItem.valueOf(arg1.getValue().toSeconds()),
            IDecimalItem.valueOf(arg2.getValue().toSeconds())));
  }

  @NonNull
  public static IBooleanItem opDateEqual(@NonNull IDateItem arg1, @NonNull IDateItem arg2) {
    return opDateTimeEqual(IDateTimeItem.cast(arg1), IDateTimeItem.cast(arg2));
  }

  @NonNull
  public static IBooleanItem opDateTimeEqual(@NonNull IDateTimeItem arg1, @NonNull IDateTimeItem arg2) {
    return IBooleanItem.valueOf(arg1.asZonedDateTime().equals(arg2.asZonedDateTime()));
  }

  @NonNull
  public static IBooleanItem opDurationEqual(@NonNull IDurationItem arg1, @NonNull IDurationItem arg2) {
    return IBooleanItem.valueOf(arg1.getValue().equals(arg2.getValue()));
  }

  @NonNull
  public static IBooleanItem opBase64BinaryEqual(@NonNull IBase64BinaryItem arg1, @NonNull IBase64BinaryItem arg2) {
    return IBooleanItem.valueOf(arg1.getValue().equals(arg2.getValue()));
  }

  @NonNull
  public static IBooleanItem opDateGreaterThan(@NonNull IDateItem arg1, @NonNull IDateItem arg2) {
    return opDateTimeGreaterThan(IDateTimeItem.cast(arg1), IDateTimeItem.cast(arg2));
  }

  @NonNull
  public static IBooleanItem opDateTimeGreaterThan(@NonNull IDateTimeItem arg1, @NonNull IDateTimeItem arg2) {
    return IBooleanItem.valueOf(arg1.asZonedDateTime().compareTo(arg2.asZonedDateTime()) > 0);
  }

  @NonNull
  public static IBooleanItem opYearMonthDurationGreaterThan(@NonNull IYearMonthDurationItem arg1,
      @NonNull IYearMonthDurationItem arg2) {
    Period p1 = arg1.getValue();
    Period p2 = arg2.getValue();

    // this is only an approximation
    return IBooleanItem.valueOf(p1.toTotalMonths() > p2.toTotalMonths());
  }

  @NonNull
  public static IBooleanItem opDayTimeDurationGreaterThan(
      @NonNull IDayTimeDurationItem arg1,
      @NonNull IDayTimeDurationItem arg2) {
    return IBooleanItem.valueOf(arg1.getValue().compareTo(arg2.getValue()) > 0);
  }

  @NonNull
  public static IBooleanItem opBase64BinaryGreaterThan(
      @NonNull IBase64BinaryItem arg1,
      @NonNull IBase64BinaryItem arg2) {
    return IBooleanItem.valueOf(arg1.getValue().compareTo(arg2.getValue()) > 0);
  }

  @NonNull
  public static IBooleanItem opDateLessThan(
      @NonNull IDateItem arg1,
      @NonNull IDateItem arg2) {
    return opDateTimeLessThan(IDateTimeItem.cast(arg1), IDateTimeItem.cast(arg2));
  }

  @NonNull
  public static IBooleanItem opDateTimeLessThan(
      @NonNull IDateTimeItem arg1,
      @NonNull IDateTimeItem arg2) {
    return IBooleanItem.valueOf(arg1.asZonedDateTime().compareTo(arg2.asZonedDateTime()) < 0);
  }

  @NonNull
  public static IBooleanItem opYearMonthDurationLessThan(@NonNull IYearMonthDurationItem arg1,
      @NonNull IYearMonthDurationItem arg2) {
    Period p1 = arg1.getValue();
    Period p2 = arg2.getValue();

    // this is only an approximation
    return IBooleanItem.valueOf(p1.toTotalMonths() < p2.toTotalMonths());
  }

  @NonNull
  public static IBooleanItem opDayTimeDurationLessThan(
      @NonNull IDayTimeDurationItem arg1,
      @NonNull IDayTimeDurationItem arg2) {
    return IBooleanItem.valueOf(arg1.getValue().compareTo(arg2.getValue()) < 0);
  }

  @NonNull
  public static IBooleanItem opBase64BinaryLessThan(
      @NonNull IBase64BinaryItem arg1,
      @NonNull IBase64BinaryItem arg2) {
    return IBooleanItem.valueOf(arg1.getValue().compareTo(arg2.getValue()) < 0);
  }

  @NonNull
  public static INumericItem opNumericAdd(@NonNull INumericItem left, @NonNull INumericItem right) {
    INumericItem retval;
    if (left instanceof IDecimalItem || right instanceof IDecimalItem) {
      // create a decimal result
      BigDecimal decimalLeft = left.asDecimal();
      BigDecimal decimalRight = right.asDecimal();

      @SuppressWarnings("null")
      @NonNull BigDecimal result = decimalLeft.add(decimalRight, FunctionUtils.MATH_CONTEXT);
      retval = IDecimalItem.valueOf(result);
    } else {
      // create an integer result
      BigInteger integerLeft = left.asInteger();
      BigInteger integerRight = right.asInteger();

      @SuppressWarnings("null")
      @NonNull BigInteger result = integerLeft.add(integerRight);
      retval = IIntegerItem.valueOf(result);
    }
    return retval;
  }

  @NonNull
  public static INumericItem opNumericSubtract(@NonNull INumericItem left, @NonNull INumericItem right) {
    INumericItem retval;
    if (left instanceof IDecimalItem || right instanceof IDecimalItem) {
      // create a decimal result
      BigDecimal decimalLeft = left.asDecimal();
      BigDecimal decimalRight = right.asDecimal();

      @SuppressWarnings("null")
      @NonNull BigDecimal result = decimalLeft.subtract(decimalRight, FunctionUtils.MATH_CONTEXT);
      retval = IDecimalItem.valueOf(result);
    } else {
      // create an integer result
      BigInteger integerLeft = left.asInteger();
      BigInteger integerRight = right.asInteger();

      @SuppressWarnings("null")
      @NonNull BigInteger result = integerLeft.subtract(integerRight);
      retval = IIntegerItem.valueOf(result);
    }
    return retval;
  }

  @NonNull
  public static INumericItem opNumericMultiply(@NonNull INumericItem left, @NonNull INumericItem right) {
    INumericItem retval;
    if (left instanceof IDecimalItem || right instanceof IDecimalItem) {
      // create a decimal result
      BigDecimal decimalLeft = left.asDecimal();
      BigDecimal decimalRight = right.asDecimal();

      @SuppressWarnings("null")
      @NonNull BigDecimal result = decimalLeft.multiply(decimalRight, FunctionUtils.MATH_CONTEXT);
      retval = IDecimalItem.valueOf(result);
    } else {
      // create an integer result
      @SuppressWarnings("null")
      @NonNull BigInteger result = left.asInteger().multiply(right.asInteger());
      retval = IIntegerItem.valueOf(result);
    }
    return retval;
  }

  @NonNull
  public static INumericItem opNumericDivide(@NonNull INumericItem dividend, @NonNull INumericItem divisor) {
    INumericItem retval;
    if (dividend instanceof IDecimalItem || divisor instanceof IDecimalItem) {
      // create a decimal result
      BigDecimal decimalDivisor = divisor.asDecimal();

      if (BigDecimal.ZERO.equals(decimalDivisor)) {
        throw new ArithmeticFunctionException(ArithmeticFunctionException.DIVISION_BY_ZERO,
            ArithmeticFunctionException.DIVISION_BY_ZERO_MESSAGE);
      }

      BigDecimal decimalDividend = dividend.asDecimal();

      @SuppressWarnings("null")
      @NonNull BigDecimal result = decimalDividend.divide(decimalDivisor, FunctionUtils.MATH_CONTEXT);
      retval = IDecimalItem.valueOf(result);
    } else {
      // create an integer result
      BigInteger integerDivisor = divisor.asInteger();

      if (BigInteger.ZERO.equals(integerDivisor)) {
        throw new ArithmeticFunctionException(ArithmeticFunctionException.DIVISION_BY_ZERO,
            ArithmeticFunctionException.DIVISION_BY_ZERO_MESSAGE);
      }

      BigInteger integerDividend = dividend.asInteger();

      @SuppressWarnings("null")
      @NonNull BigInteger result = integerDividend.divide(integerDivisor);
      retval = IIntegerItem.valueOf(result);
    }
    return retval;
  }

  @NonNull
  public static IIntegerItem opNumericIntegerDivide(@NonNull INumericItem dividend, @NonNull INumericItem divisor) {
    IIntegerItem retval;
    if (dividend instanceof IDecimalItem || divisor instanceof IDecimalItem) {
      // create a decimal result
      BigDecimal decimalDivisor = divisor.asDecimal();

      if (BigDecimal.ZERO.equals(decimalDivisor)) {
        throw new ArithmeticFunctionException(ArithmeticFunctionException.DIVISION_BY_ZERO,
            ArithmeticFunctionException.DIVISION_BY_ZERO_MESSAGE);
      }

      BigDecimal decimalDividend = dividend.asDecimal();

      @SuppressWarnings("null")
      @NonNull BigInteger result
          = decimalDividend.divideToIntegralValue(decimalDivisor, FunctionUtils.MATH_CONTEXT).toBigInteger();
      retval = IIntegerItem.valueOf(result);
    } else {
      // create an integer result
      BigInteger integerDivisor = divisor.asInteger();

      if (BigInteger.ZERO.equals(integerDivisor)) {
        throw new ArithmeticFunctionException(ArithmeticFunctionException.DIVISION_BY_ZERO,
            ArithmeticFunctionException.DIVISION_BY_ZERO_MESSAGE);
      }

      @SuppressWarnings("null")
      @NonNull BigInteger result = dividend.asInteger().divide(integerDivisor);
      retval = IIntegerItem.valueOf(result);
    }
    return retval;
  }

  /**
   * Based on XPath 3.1 <a href=
   * "https://www.w3.org/TR/xpath-functions-31/#func-numeric-mod">func:numeric-mod</a>.
   *
   * @param dividend
   *          the number to be divided
   * @param divisor
   *          the number to divide by
   * @return the remainder
   */
  @NonNull
  public static INumericItem opNumericMod(@NonNull INumericItem dividend, @NonNull INumericItem divisor) {
    BigDecimal decimalDivisor = divisor.asDecimal();

    if (BigDecimal.ZERO.equals(decimalDivisor)) {
      throw new ArithmeticFunctionException(ArithmeticFunctionException.DIVISION_BY_ZERO,
          ArithmeticFunctionException.DIVISION_BY_ZERO_MESSAGE);
    }

    BigDecimal decimalDividend = dividend.asDecimal();

    INumericItem retval;
    if (BigDecimal.ZERO.equals(decimalDividend)) {
      retval = dividend;
    } else {
      @SuppressWarnings("null")
      @NonNull BigDecimal result = decimalDividend.remainder(decimalDivisor, FunctionUtils.MATH_CONTEXT);
      retval = IDecimalItem.valueOf(result);
    }
    return retval;
  }

  @NonNull
  public static INumericItem opNumericUnaryMinus(@NonNull INumericItem item) {
    INumericItem retval;
    if (item instanceof IDecimalItem) {
      // create a decimal result
      BigDecimal decimal = item.asDecimal();

      @SuppressWarnings("null")
      @NonNull BigDecimal result = decimal.negate(FunctionUtils.MATH_CONTEXT);
      retval = IDecimalItem.valueOf(result);
    } else if (item instanceof IIntegerItem) {
      // create a decimal result
      BigInteger integer = item.asInteger();

      @SuppressWarnings("null")
      @NonNull BigInteger result = integer.negate();
      retval = IIntegerItem.valueOf(result);
    } else {
      throw new InvalidTypeMetapathException(item);
    }
    return retval;
  }

  @NonNull
  public static IBooleanItem opNumericEqual(@Nullable INumericItem arg1, @Nullable INumericItem arg2) {
    IBooleanItem retval;
    if (arg1 == null || arg2 == null) {
      retval = IBooleanItem.FALSE;
    } else if (arg1 instanceof IDecimalItem || arg2 instanceof IDecimalItem) {
      retval = IBooleanItem.valueOf(arg1.asDecimal().equals(arg2.asDecimal()));
    } else {
      retval = IBooleanItem.valueOf(arg1.asInteger().equals(arg2.asInteger()));
    }
    return retval;
  }

  @NonNull
  public static IBooleanItem opNumericGreaterThan(@Nullable INumericItem arg1, @Nullable INumericItem arg2) {
    IBooleanItem retval;
    if (arg1 == null || arg2 == null) {
      retval = IBooleanItem.FALSE;
    } else if (arg1 instanceof IDecimalItem || arg2 instanceof IDecimalItem) {
      int result = arg1.asDecimal().compareTo(arg2.asDecimal());
      retval = IBooleanItem.valueOf(result > 0);
    } else {
      int result = arg1.asInteger().compareTo(arg2.asInteger());
      retval = IBooleanItem.valueOf(result > 0);
    }
    return retval;
  }

  @NonNull
  public static IBooleanItem opNumericLessThan(@Nullable INumericItem arg1, @Nullable INumericItem arg2) {
    IBooleanItem retval;
    if (arg1 == null || arg2 == null) {
      retval = IBooleanItem.FALSE;
    } else if (arg1 instanceof IDecimalItem || arg2 instanceof IDecimalItem) {
      int result = arg1.asDecimal().compareTo(arg2.asDecimal());
      retval = IBooleanItem.valueOf(result < 0);
    } else {
      int result = arg1.asInteger().compareTo(arg2.asInteger());
      retval = IBooleanItem.valueOf(result < 0);
    }
    return retval;
  }

  @NonNull
  public static IBooleanItem opBooleanEqual(@Nullable IBooleanItem arg1, @Nullable IBooleanItem arg2) {
    boolean left = arg1 != null && arg1.toBoolean();
    boolean right = arg2 != null && arg2.toBoolean();

    return IBooleanItem.valueOf(left == right);
  }

  @NonNull
  public static IBooleanItem opBooleanGreaterThan(@Nullable IBooleanItem arg1, @Nullable IBooleanItem arg2) {
    boolean left = arg1 != null && arg1.toBoolean();
    boolean right = arg2 != null && arg2.toBoolean();

    return IBooleanItem.valueOf(left && !right);
  }

  @NonNull
  public static IBooleanItem opBooleanLessThan(@Nullable IBooleanItem arg1, @Nullable IBooleanItem arg2) {
    boolean left = arg1 != null && arg1.toBoolean();
    boolean right = arg2 != null && arg2.toBoolean();

    return IBooleanItem.valueOf(!left && right);
  }
}