View Javadoc
1   /*
2    * Portions of this software was developed by employees of the National Institute
3    * of Standards and Technology (NIST), an agency of the Federal Government and is
4    * being made available as a public service. Pursuant to title 17 United States
5    * Code Section 105, works of NIST employees are not subject to copyright
6    * protection in the United States. This software may be subject to foreign
7    * copyright. Permission in the United States and in foreign countries, to the
8    * extent that NIST may hold copyright, to use, copy, modify, create derivative
9    * works, and distribute this software and its documentation without fee is hereby
10   * granted on a non-exclusive basis, provided that this notice and disclaimer
11   * of warranty appears in all copies.
12   *
13   * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER
14   * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY
15   * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF
16   * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM
17   * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE
18   * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE.  IN NO EVENT
19   * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT,
20   * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM,
21   * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY,
22   * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR
23   * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT
24   * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER.
25   */
26  
27  package gov.nist.secauto.metaschema.core.metapath.function; // NOPMD - intentional
28  
29  import gov.nist.secauto.metaschema.core.metapath.InvalidTypeMetapathException;
30  import gov.nist.secauto.metaschema.core.metapath.item.atomic.IBase64BinaryItem;
31  import gov.nist.secauto.metaschema.core.metapath.item.atomic.IBooleanItem;
32  import gov.nist.secauto.metaschema.core.metapath.item.atomic.IDateItem;
33  import gov.nist.secauto.metaschema.core.metapath.item.atomic.IDateTimeItem;
34  import gov.nist.secauto.metaschema.core.metapath.item.atomic.IDayTimeDurationItem;
35  import gov.nist.secauto.metaschema.core.metapath.item.atomic.IDecimalItem;
36  import gov.nist.secauto.metaschema.core.metapath.item.atomic.IDurationItem;
37  import gov.nist.secauto.metaschema.core.metapath.item.atomic.IIntegerItem;
38  import gov.nist.secauto.metaschema.core.metapath.item.atomic.INumericItem;
39  import gov.nist.secauto.metaschema.core.metapath.item.atomic.IYearMonthDurationItem;
40  
41  import java.math.BigDecimal;
42  import java.math.BigInteger;
43  import java.time.Duration;
44  import java.time.Period;
45  import java.time.ZonedDateTime;
46  import java.time.temporal.TemporalAmount;
47  
48  import edu.umd.cs.findbugs.annotations.NonNull;
49  import edu.umd.cs.findbugs.annotations.Nullable;
50  
51  public final class OperationFunctions { // NOPMD - intentional
52    private OperationFunctions() {
53      // disable
54    }
55  
56    @NonNull
57    public static IDateItem opAddYearMonthDurationToDate(@NonNull IDateItem arg1, @NonNull IYearMonthDurationItem arg2) {
58      return addDurationToDate(arg1.asZonedDateTime(), arg2.getValue());
59    }
60  
61    @NonNull
62    public static IDateItem opAddDayTimeDurationToDate(@NonNull IDateItem arg1, @NonNull IDayTimeDurationItem arg2) {
63      return addDurationToDate(arg1.asZonedDateTime(), arg2.getValue());
64    }
65  
66    @NonNull
67    private static IDateItem addDurationToDate(@NonNull ZonedDateTime dateTime, @NonNull TemporalAmount duration) {
68      ZonedDateTime result;
69      try {
70        result = dateTime.plus(duration);
71      } catch (ArithmeticException ex) {
72        throw new ArithmeticFunctionException(ArithmeticFunctionException.OVERFLOW_UNDERFLOW_ERROR, ex);
73      }
74      assert result != null;
75      return IDateItem.valueOf(result);
76    }
77  
78    @NonNull
79    public static IYearMonthDurationItem opAddYearMonthDurations(@NonNull IYearMonthDurationItem arg1,
80        IYearMonthDurationItem arg2) {
81      Period duration1 = arg1.getValue();
82      Period duration2 = arg2.getValue();
83  
84      Period result;
85      try {
86        result = duration1.plus(duration2);
87      } catch (ArithmeticException ex) {
88        throw new ArithmeticFunctionException(ArithmeticFunctionException.OVERFLOW_UNDERFLOW_ERROR, ex);
89      }
90      assert result != null;
91      return IYearMonthDurationItem.valueOf(result);
92    }
93  
94    @NonNull
95    public static IDayTimeDurationItem opAddDayTimeDurations(@NonNull IDayTimeDurationItem arg1,
96        @NonNull IDayTimeDurationItem arg2) {
97      Duration duration1 = arg1.getValue();
98      Duration duration2 = arg2.getValue();
99  
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 }