001/*
002 * Portions of this software was developed by employees of the National Institute
003 * of Standards and Technology (NIST), an agency of the Federal Government and is
004 * being made available as a public service. Pursuant to title 17 United States
005 * Code Section 105, works of NIST employees are not subject to copyright
006 * protection in the United States. This software may be subject to foreign
007 * copyright. Permission in the United States and in foreign countries, to the
008 * extent that NIST may hold copyright, to use, copy, modify, create derivative
009 * works, and distribute this software and its documentation without fee is hereby
010 * granted on a non-exclusive basis, provided that this notice and disclaimer
011 * of warranty appears in all copies.
012 *
013 * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER
014 * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY
015 * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF
016 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM
017 * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE
018 * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE.  IN NO EVENT
019 * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT,
020 * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM,
021 * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY,
022 * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR
023 * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT
024 * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER.
025 */
026
027package gov.nist.secauto.metaschema.core.metapath.function; // NOPMD - intentional
028
029import gov.nist.secauto.metaschema.core.metapath.InvalidTypeMetapathException;
030import gov.nist.secauto.metaschema.core.metapath.item.atomic.IBase64BinaryItem;
031import gov.nist.secauto.metaschema.core.metapath.item.atomic.IBooleanItem;
032import gov.nist.secauto.metaschema.core.metapath.item.atomic.IDateItem;
033import gov.nist.secauto.metaschema.core.metapath.item.atomic.IDateTimeItem;
034import gov.nist.secauto.metaschema.core.metapath.item.atomic.IDayTimeDurationItem;
035import gov.nist.secauto.metaschema.core.metapath.item.atomic.IDecimalItem;
036import gov.nist.secauto.metaschema.core.metapath.item.atomic.IDurationItem;
037import gov.nist.secauto.metaschema.core.metapath.item.atomic.IIntegerItem;
038import gov.nist.secauto.metaschema.core.metapath.item.atomic.INumericItem;
039import gov.nist.secauto.metaschema.core.metapath.item.atomic.IYearMonthDurationItem;
040
041import java.math.BigDecimal;
042import java.math.BigInteger;
043import java.time.Duration;
044import java.time.Period;
045import java.time.ZonedDateTime;
046import java.time.temporal.TemporalAmount;
047
048import edu.umd.cs.findbugs.annotations.NonNull;
049import edu.umd.cs.findbugs.annotations.Nullable;
050
051public final class OperationFunctions { // NOPMD - intentional
052  private OperationFunctions() {
053    // disable
054  }
055
056  @NonNull
057  public static IDateItem opAddYearMonthDurationToDate(@NonNull IDateItem arg1, @NonNull IYearMonthDurationItem arg2) {
058    return addDurationToDate(arg1.asZonedDateTime(), arg2.getValue());
059  }
060
061  @NonNull
062  public static IDateItem opAddDayTimeDurationToDate(@NonNull IDateItem arg1, @NonNull IDayTimeDurationItem arg2) {
063    return addDurationToDate(arg1.asZonedDateTime(), arg2.getValue());
064  }
065
066  @NonNull
067  private static IDateItem addDurationToDate(@NonNull ZonedDateTime dateTime, @NonNull TemporalAmount duration) {
068    ZonedDateTime result;
069    try {
070      result = dateTime.plus(duration);
071    } catch (ArithmeticException ex) {
072      throw new ArithmeticFunctionException(ArithmeticFunctionException.OVERFLOW_UNDERFLOW_ERROR, ex);
073    }
074    assert result != null;
075    return IDateItem.valueOf(result);
076  }
077
078  @NonNull
079  public static IYearMonthDurationItem opAddYearMonthDurations(@NonNull IYearMonthDurationItem arg1,
080      IYearMonthDurationItem arg2) {
081    Period duration1 = arg1.getValue();
082    Period duration2 = arg2.getValue();
083
084    Period result;
085    try {
086      result = duration1.plus(duration2);
087    } catch (ArithmeticException ex) {
088      throw new ArithmeticFunctionException(ArithmeticFunctionException.OVERFLOW_UNDERFLOW_ERROR, ex);
089    }
090    assert result != null;
091    return IYearMonthDurationItem.valueOf(result);
092  }
093
094  @NonNull
095  public static IDayTimeDurationItem opAddDayTimeDurations(@NonNull IDayTimeDurationItem arg1,
096      @NonNull IDayTimeDurationItem arg2) {
097    Duration duration1 = arg1.getValue();
098    Duration duration2 = arg2.getValue();
099
100    Duration result;
101    try {
102      result = duration1.plus(duration2);
103    } catch (ArithmeticException ex) {
104      throw new ArithmeticFunctionException(ArithmeticFunctionException.OVERFLOW_UNDERFLOW_ERROR, ex);
105    }
106    assert result != null;
107    return IDayTimeDurationItem.valueOf(result);
108  }
109
110  @NonNull
111  public static IDateTimeItem opAddYearMonthDurationToDateTime(@NonNull IDateTimeItem arg1,
112      @NonNull IYearMonthDurationItem arg2) {
113    ZonedDateTime result;
114    try {
115      result = arg1.asZonedDateTime().plus(arg2.getValue());
116    } catch (ArithmeticException ex) {
117      throw new ArithmeticFunctionException(ArithmeticFunctionException.OVERFLOW_UNDERFLOW_ERROR, ex);
118    }
119    assert result != null;
120    return IDateTimeItem.valueOf(result);
121  }
122
123  @NonNull
124  public static IDateTimeItem opAddDayTimeDurationToDateTime(@NonNull IDateTimeItem arg1,
125      @NonNull IDayTimeDurationItem arg2) {
126    ZonedDateTime result;
127    try {
128      result = arg1.asZonedDateTime().plus(arg2.getValue());
129    } catch (ArithmeticException ex) {
130      throw new ArithmeticFunctionException(ArithmeticFunctionException.OVERFLOW_UNDERFLOW_ERROR, ex);
131    }
132    assert result != null;
133    return IDateTimeItem.valueOf(result);
134  }
135
136  @NonNull
137  public static IDayTimeDurationItem opSubtractDates(@NonNull IDateItem arg1, @NonNull IDateItem arg2) {
138    return between(arg1.asZonedDateTime(), arg2.asZonedDateTime());
139  }
140
141  @NonNull
142  public static IDateItem opSubtractYearMonthDurationFromDate(@NonNull IDateItem arg1,
143      @NonNull IYearMonthDurationItem arg2) {
144    return subtractDurationFromDate(arg1.asZonedDateTime(), arg2.getValue());
145  }
146
147  @NonNull
148  public static IDateItem opSubtractDayTimeDurationFromDate(@NonNull IDateItem arg1,
149      @NonNull IDayTimeDurationItem arg2) {
150    return subtractDurationFromDate(arg1.asZonedDateTime(), arg2.getValue());
151  }
152
153  @NonNull
154  private static IDateItem subtractDurationFromDate(@NonNull ZonedDateTime dateTime,
155      @NonNull TemporalAmount duration) {
156    @SuppressWarnings("null")
157    @NonNull ZonedDateTime result = dateTime.minus(duration);
158    return IDateItem.valueOf(result);
159  }
160
161  @NonNull
162  public static IYearMonthDurationItem opSubtractYearMonthDurations(@NonNull IYearMonthDurationItem arg1,
163      IYearMonthDurationItem arg2) {
164    Period duration1 = arg1.getValue();
165    Period duration2 = arg2.getValue();
166
167    @SuppressWarnings("null")
168    @NonNull Period duration = duration1.minus(duration2);
169    return IYearMonthDurationItem.valueOf(duration);
170  }
171
172  @NonNull
173  public static IDayTimeDurationItem opSubtractDayTimeDurations(@NonNull IDayTimeDurationItem arg1,
174      @NonNull IDayTimeDurationItem arg2) {
175    Duration duration1 = arg1.getValue();
176    Duration duration2 = arg2.getValue();
177
178    @SuppressWarnings("null")
179    @NonNull Duration duration = duration1.minus(duration2);
180    return IDayTimeDurationItem.valueOf(duration);
181  }
182
183  @NonNull
184  public static IDayTimeDurationItem opSubtractDateTimes(@NonNull IDateTimeItem arg1, @NonNull IDateTimeItem arg2) {
185    return between(arg1.asZonedDateTime(), arg2.asZonedDateTime());
186  }
187
188  @NonNull
189  private static IDayTimeDurationItem between(@NonNull ZonedDateTime time1, @NonNull ZonedDateTime time2) {
190    @SuppressWarnings("null")
191    @NonNull Duration between = Duration.between(time1, time2);
192    return IDayTimeDurationItem.valueOf(between);
193  }
194
195  @NonNull
196  public static IDateTimeItem opSubtractYearMonthDurationFromDateTime(@NonNull IDateTimeItem arg1,
197      @NonNull IYearMonthDurationItem arg2) {
198    @SuppressWarnings("null")
199    @NonNull ZonedDateTime dateTime = arg1.asZonedDateTime().minus(arg2.getValue());
200    return IDateTimeItem.valueOf(dateTime);
201  }
202
203  @NonNull
204  public static IDateTimeItem opSubtractDayTimeDurationFromDateTime(@NonNull IDateTimeItem arg1,
205      @NonNull IDayTimeDurationItem arg2) {
206
207    @SuppressWarnings("null")
208    @NonNull ZonedDateTime dateTime = arg1.asZonedDateTime().plus(arg2.getValue());
209    return IDateTimeItem.valueOf(dateTime);
210  }
211
212  @NonNull
213  public static IYearMonthDurationItem opMultiplyYearMonthDuration(@NonNull IYearMonthDurationItem arg1,
214      @NonNull INumericItem arg2)
215      throws ArithmeticFunctionException {
216    int arg2Int;
217    try {
218      arg2Int = FunctionUtils.asInteger(arg2.round());
219    } catch (ArithmeticException ex) {
220      throw new ArithmeticFunctionException(ArithmeticFunctionException.OVERFLOW_UNDERFLOW_ERROR, ex);
221    }
222
223    @SuppressWarnings("null")
224    @NonNull Period period = arg1.getValue().multipliedBy(arg2Int);
225    return IYearMonthDurationItem.valueOf(period);
226  }
227
228  @NonNull
229  public static IDayTimeDurationItem opMultiplyDayTimeDuration(@NonNull IDayTimeDurationItem arg1,
230      @NonNull INumericItem arg2)
231      throws ArithmeticFunctionException {
232    long arg2Long;
233    try {
234      arg2Long = FunctionUtils.asLong(arg2.round());
235    } catch (ArithmeticException ex) {
236      throw new ArithmeticFunctionException(ArithmeticFunctionException.OVERFLOW_UNDERFLOW_ERROR, ex);
237    }
238
239    @SuppressWarnings("null")
240    @NonNull Duration duration = arg1.getValue().multipliedBy(arg2Long);
241    return IDayTimeDurationItem.valueOf(duration);
242  }
243
244  @NonNull
245  public static IYearMonthDurationItem opDivideYearMonthDuration(@NonNull IYearMonthDurationItem arg1,
246      @NonNull INumericItem arg2)
247      throws DateTimeFunctionException {
248    IIntegerItem totalMonths = IIntegerItem.valueOf(arg1.getValue().toTotalMonths());
249    IIntegerItem result = opNumericIntegerDivide(totalMonths, arg2);
250    int months;
251    try {
252      months = FunctionUtils.asInteger(result.asInteger());
253    } catch (ArithmeticException ex) {
254      throw new DateTimeFunctionException(DateTimeFunctionException.DURATION_OVERFLOW_UNDERFLOW_ERROR,
255          "Overflow/underflow in duration operation.", ex);
256    }
257    int years = months / 12;
258    months = months % 12;
259    return IYearMonthDurationItem.valueOf(years, months, 0);
260  }
261
262  @NonNull
263  public static IDayTimeDurationItem opDivideDayTimeDuration(@NonNull IDayTimeDurationItem arg1,
264      @NonNull INumericItem arg2)
265      throws ArithmeticFunctionException {
266    try {
267      @SuppressWarnings("null")
268      @NonNull Duration duration = arg1.getValue().dividedBy(FunctionUtils.asLong(arg2.round()));
269      return IDayTimeDurationItem
270          .valueOf(duration);
271    } catch (ArithmeticException ex) {
272      throw new ArithmeticFunctionException(ArithmeticFunctionException.DIVISION_BY_ZERO, "Division by zero", ex);
273    }
274  }
275
276  @NonNull
277  public static IDecimalItem opDivideDayTimeDurationByDayTimeDuration(@NonNull IDayTimeDurationItem arg1,
278      IDayTimeDurationItem arg2) {
279    return IDecimalItem.cast(
280        opNumericDivide(
281            IDecimalItem.valueOf(arg1.getValue().toSeconds()),
282            IDecimalItem.valueOf(arg2.getValue().toSeconds())));
283  }
284
285  @NonNull
286  public static IBooleanItem opDateEqual(@NonNull IDateItem arg1, @NonNull IDateItem arg2) {
287    return opDateTimeEqual(IDateTimeItem.cast(arg1), IDateTimeItem.cast(arg2));
288  }
289
290  @NonNull
291  public static IBooleanItem opDateTimeEqual(@NonNull IDateTimeItem arg1, @NonNull IDateTimeItem arg2) {
292    return IBooleanItem.valueOf(arg1.asZonedDateTime().equals(arg2.asZonedDateTime()));
293  }
294
295  @NonNull
296  public static IBooleanItem opDurationEqual(@NonNull IDurationItem arg1, @NonNull IDurationItem arg2) {
297    return IBooleanItem.valueOf(arg1.getValue().equals(arg2.getValue()));
298  }
299
300  @NonNull
301  public static IBooleanItem opBase64BinaryEqual(@NonNull IBase64BinaryItem arg1, @NonNull IBase64BinaryItem arg2) {
302    return IBooleanItem.valueOf(arg1.getValue().equals(arg2.getValue()));
303  }
304
305  @NonNull
306  public static IBooleanItem opDateGreaterThan(@NonNull IDateItem arg1, @NonNull IDateItem arg2) {
307    return opDateTimeGreaterThan(IDateTimeItem.cast(arg1), IDateTimeItem.cast(arg2));
308  }
309
310  @NonNull
311  public static IBooleanItem opDateTimeGreaterThan(@NonNull IDateTimeItem arg1, @NonNull IDateTimeItem arg2) {
312    return IBooleanItem.valueOf(arg1.asZonedDateTime().compareTo(arg2.asZonedDateTime()) > 0);
313  }
314
315  @NonNull
316  public static IBooleanItem opYearMonthDurationGreaterThan(@NonNull IYearMonthDurationItem arg1,
317      @NonNull IYearMonthDurationItem arg2) {
318    Period p1 = arg1.getValue();
319    Period p2 = arg2.getValue();
320
321    // this is only an approximation
322    return IBooleanItem.valueOf(p1.toTotalMonths() > p2.toTotalMonths());
323  }
324
325  @NonNull
326  public static IBooleanItem opDayTimeDurationGreaterThan(
327      @NonNull IDayTimeDurationItem arg1,
328      @NonNull IDayTimeDurationItem arg2) {
329    return IBooleanItem.valueOf(arg1.getValue().compareTo(arg2.getValue()) > 0);
330  }
331
332  @NonNull
333  public static IBooleanItem opBase64BinaryGreaterThan(
334      @NonNull IBase64BinaryItem arg1,
335      @NonNull IBase64BinaryItem arg2) {
336    return IBooleanItem.valueOf(arg1.getValue().compareTo(arg2.getValue()) > 0);
337  }
338
339  @NonNull
340  public static IBooleanItem opDateLessThan(
341      @NonNull IDateItem arg1,
342      @NonNull IDateItem arg2) {
343    return opDateTimeLessThan(IDateTimeItem.cast(arg1), IDateTimeItem.cast(arg2));
344  }
345
346  @NonNull
347  public static IBooleanItem opDateTimeLessThan(
348      @NonNull IDateTimeItem arg1,
349      @NonNull IDateTimeItem arg2) {
350    return IBooleanItem.valueOf(arg1.asZonedDateTime().compareTo(arg2.asZonedDateTime()) < 0);
351  }
352
353  @NonNull
354  public static IBooleanItem opYearMonthDurationLessThan(@NonNull IYearMonthDurationItem arg1,
355      @NonNull IYearMonthDurationItem arg2) {
356    Period p1 = arg1.getValue();
357    Period p2 = arg2.getValue();
358
359    // this is only an approximation
360    return IBooleanItem.valueOf(p1.toTotalMonths() < p2.toTotalMonths());
361  }
362
363  @NonNull
364  public static IBooleanItem opDayTimeDurationLessThan(
365      @NonNull IDayTimeDurationItem arg1,
366      @NonNull IDayTimeDurationItem arg2) {
367    return IBooleanItem.valueOf(arg1.getValue().compareTo(arg2.getValue()) < 0);
368  }
369
370  @NonNull
371  public static IBooleanItem opBase64BinaryLessThan(
372      @NonNull IBase64BinaryItem arg1,
373      @NonNull IBase64BinaryItem arg2) {
374    return IBooleanItem.valueOf(arg1.getValue().compareTo(arg2.getValue()) < 0);
375  }
376
377  @NonNull
378  public static INumericItem opNumericAdd(@NonNull INumericItem left, @NonNull INumericItem right) {
379    INumericItem retval;
380    if (left instanceof IDecimalItem || right instanceof IDecimalItem) {
381      // create a decimal result
382      BigDecimal decimalLeft = left.asDecimal();
383      BigDecimal decimalRight = right.asDecimal();
384
385      @SuppressWarnings("null")
386      @NonNull BigDecimal result = decimalLeft.add(decimalRight, FunctionUtils.MATH_CONTEXT);
387      retval = IDecimalItem.valueOf(result);
388    } else {
389      // create an integer result
390      BigInteger integerLeft = left.asInteger();
391      BigInteger integerRight = right.asInteger();
392
393      @SuppressWarnings("null")
394      @NonNull BigInteger result = integerLeft.add(integerRight);
395      retval = IIntegerItem.valueOf(result);
396    }
397    return retval;
398  }
399
400  @NonNull
401  public static INumericItem opNumericSubtract(@NonNull INumericItem left, @NonNull INumericItem right) {
402    INumericItem retval;
403    if (left instanceof IDecimalItem || right instanceof IDecimalItem) {
404      // create a decimal result
405      BigDecimal decimalLeft = left.asDecimal();
406      BigDecimal decimalRight = right.asDecimal();
407
408      @SuppressWarnings("null")
409      @NonNull BigDecimal result = decimalLeft.subtract(decimalRight, FunctionUtils.MATH_CONTEXT);
410      retval = IDecimalItem.valueOf(result);
411    } else {
412      // create an integer result
413      BigInteger integerLeft = left.asInteger();
414      BigInteger integerRight = right.asInteger();
415
416      @SuppressWarnings("null")
417      @NonNull BigInteger result = integerLeft.subtract(integerRight);
418      retval = IIntegerItem.valueOf(result);
419    }
420    return retval;
421  }
422
423  @NonNull
424  public static INumericItem opNumericMultiply(@NonNull INumericItem left, @NonNull INumericItem right) {
425    INumericItem retval;
426    if (left instanceof IDecimalItem || right instanceof IDecimalItem) {
427      // create a decimal result
428      BigDecimal decimalLeft = left.asDecimal();
429      BigDecimal decimalRight = right.asDecimal();
430
431      @SuppressWarnings("null")
432      @NonNull BigDecimal result = decimalLeft.multiply(decimalRight, FunctionUtils.MATH_CONTEXT);
433      retval = IDecimalItem.valueOf(result);
434    } else {
435      // create an integer result
436      @SuppressWarnings("null")
437      @NonNull BigInteger result = left.asInteger().multiply(right.asInteger());
438      retval = IIntegerItem.valueOf(result);
439    }
440    return retval;
441  }
442
443  @NonNull
444  public static INumericItem opNumericDivide(@NonNull INumericItem dividend, @NonNull INumericItem divisor) {
445    INumericItem retval;
446    if (dividend instanceof IDecimalItem || divisor instanceof IDecimalItem) {
447      // create a decimal result
448      BigDecimal decimalDivisor = divisor.asDecimal();
449
450      if (BigDecimal.ZERO.equals(decimalDivisor)) {
451        throw new ArithmeticFunctionException(ArithmeticFunctionException.DIVISION_BY_ZERO,
452            ArithmeticFunctionException.DIVISION_BY_ZERO_MESSAGE);
453      }
454
455      BigDecimal decimalDividend = dividend.asDecimal();
456
457      @SuppressWarnings("null")
458      @NonNull BigDecimal result = decimalDividend.divide(decimalDivisor, FunctionUtils.MATH_CONTEXT);
459      retval = IDecimalItem.valueOf(result);
460    } else {
461      // create an integer result
462      BigInteger integerDivisor = divisor.asInteger();
463
464      if (BigInteger.ZERO.equals(integerDivisor)) {
465        throw new ArithmeticFunctionException(ArithmeticFunctionException.DIVISION_BY_ZERO,
466            ArithmeticFunctionException.DIVISION_BY_ZERO_MESSAGE);
467      }
468
469      BigInteger integerDividend = dividend.asInteger();
470
471      @SuppressWarnings("null")
472      @NonNull BigInteger result = integerDividend.divide(integerDivisor);
473      retval = IIntegerItem.valueOf(result);
474    }
475    return retval;
476  }
477
478  @NonNull
479  public static IIntegerItem opNumericIntegerDivide(@NonNull INumericItem dividend, @NonNull INumericItem divisor) {
480    IIntegerItem retval;
481    if (dividend instanceof IDecimalItem || divisor instanceof IDecimalItem) {
482      // create a decimal result
483      BigDecimal decimalDivisor = divisor.asDecimal();
484
485      if (BigDecimal.ZERO.equals(decimalDivisor)) {
486        throw new ArithmeticFunctionException(ArithmeticFunctionException.DIVISION_BY_ZERO,
487            ArithmeticFunctionException.DIVISION_BY_ZERO_MESSAGE);
488      }
489
490      BigDecimal decimalDividend = dividend.asDecimal();
491
492      @SuppressWarnings("null")
493      @NonNull BigInteger result
494          = decimalDividend.divideToIntegralValue(decimalDivisor, FunctionUtils.MATH_CONTEXT).toBigInteger();
495      retval = IIntegerItem.valueOf(result);
496    } else {
497      // create an integer result
498      BigInteger integerDivisor = divisor.asInteger();
499
500      if (BigInteger.ZERO.equals(integerDivisor)) {
501        throw new ArithmeticFunctionException(ArithmeticFunctionException.DIVISION_BY_ZERO,
502            ArithmeticFunctionException.DIVISION_BY_ZERO_MESSAGE);
503      }
504
505      @SuppressWarnings("null")
506      @NonNull BigInteger result = dividend.asInteger().divide(integerDivisor);
507      retval = IIntegerItem.valueOf(result);
508    }
509    return retval;
510  }
511
512  /**
513   * Based on XPath 3.1 <a href=
514   * "https://www.w3.org/TR/xpath-functions-31/#func-numeric-mod">func:numeric-mod</a>.
515   *
516   * @param dividend
517   *          the number to be divided
518   * @param divisor
519   *          the number to divide by
520   * @return the remainder
521   */
522  @NonNull
523  public static INumericItem opNumericMod(@NonNull INumericItem dividend, @NonNull INumericItem divisor) {
524    BigDecimal decimalDivisor = divisor.asDecimal();
525
526    if (BigDecimal.ZERO.equals(decimalDivisor)) {
527      throw new ArithmeticFunctionException(ArithmeticFunctionException.DIVISION_BY_ZERO,
528          ArithmeticFunctionException.DIVISION_BY_ZERO_MESSAGE);
529    }
530
531    BigDecimal decimalDividend = dividend.asDecimal();
532
533    INumericItem retval;
534    if (BigDecimal.ZERO.equals(decimalDividend)) {
535      retval = dividend;
536    } else {
537      @SuppressWarnings("null")
538      @NonNull BigDecimal result = decimalDividend.remainder(decimalDivisor, FunctionUtils.MATH_CONTEXT);
539      retval = IDecimalItem.valueOf(result);
540    }
541    return retval;
542  }
543
544  @NonNull
545  public static INumericItem opNumericUnaryMinus(@NonNull INumericItem item) {
546    INumericItem retval;
547    if (item instanceof IDecimalItem) {
548      // create a decimal result
549      BigDecimal decimal = item.asDecimal();
550
551      @SuppressWarnings("null")
552      @NonNull BigDecimal result = decimal.negate(FunctionUtils.MATH_CONTEXT);
553      retval = IDecimalItem.valueOf(result);
554    } else if (item instanceof IIntegerItem) {
555      // create a decimal result
556      BigInteger integer = item.asInteger();
557
558      @SuppressWarnings("null")
559      @NonNull BigInteger result = integer.negate();
560      retval = IIntegerItem.valueOf(result);
561    } else {
562      throw new InvalidTypeMetapathException(item);
563    }
564    return retval;
565  }
566
567  @NonNull
568  public static IBooleanItem opNumericEqual(@Nullable INumericItem arg1, @Nullable INumericItem arg2) {
569    IBooleanItem retval;
570    if (arg1 == null || arg2 == null) {
571      retval = IBooleanItem.FALSE;
572    } else if (arg1 instanceof IDecimalItem || arg2 instanceof IDecimalItem) {
573      retval = IBooleanItem.valueOf(arg1.asDecimal().equals(arg2.asDecimal()));
574    } else {
575      retval = IBooleanItem.valueOf(arg1.asInteger().equals(arg2.asInteger()));
576    }
577    return retval;
578  }
579
580  @NonNull
581  public static IBooleanItem opNumericGreaterThan(@Nullable INumericItem arg1, @Nullable INumericItem arg2) {
582    IBooleanItem retval;
583    if (arg1 == null || arg2 == null) {
584      retval = IBooleanItem.FALSE;
585    } else if (arg1 instanceof IDecimalItem || arg2 instanceof IDecimalItem) {
586      int result = arg1.asDecimal().compareTo(arg2.asDecimal());
587      retval = IBooleanItem.valueOf(result > 0);
588    } else {
589      int result = arg1.asInteger().compareTo(arg2.asInteger());
590      retval = IBooleanItem.valueOf(result > 0);
591    }
592    return retval;
593  }
594
595  @NonNull
596  public static IBooleanItem opNumericLessThan(@Nullable INumericItem arg1, @Nullable INumericItem arg2) {
597    IBooleanItem retval;
598    if (arg1 == null || arg2 == null) {
599      retval = IBooleanItem.FALSE;
600    } else if (arg1 instanceof IDecimalItem || arg2 instanceof IDecimalItem) {
601      int result = arg1.asDecimal().compareTo(arg2.asDecimal());
602      retval = IBooleanItem.valueOf(result < 0);
603    } else {
604      int result = arg1.asInteger().compareTo(arg2.asInteger());
605      retval = IBooleanItem.valueOf(result < 0);
606    }
607    return retval;
608  }
609
610  @NonNull
611  public static IBooleanItem opBooleanEqual(@Nullable IBooleanItem arg1, @Nullable IBooleanItem arg2) {
612    boolean left = arg1 != null && arg1.toBoolean();
613    boolean right = arg2 != null && arg2.toBoolean();
614
615    return IBooleanItem.valueOf(left == right);
616  }
617
618  @NonNull
619  public static IBooleanItem opBooleanGreaterThan(@Nullable IBooleanItem arg1, @Nullable IBooleanItem arg2) {
620    boolean left = arg1 != null && arg1.toBoolean();
621    boolean right = arg2 != null && arg2.toBoolean();
622
623    return IBooleanItem.valueOf(left && !right);
624  }
625
626  @NonNull
627  public static IBooleanItem opBooleanLessThan(@Nullable IBooleanItem arg1, @Nullable IBooleanItem arg2) {
628    boolean left = arg1 != null && arg1.toBoolean();
629    boolean right = arg2 != null && arg2.toBoolean();
630
631    return IBooleanItem.valueOf(!left && right);
632  }
633}