1 /*
2 * SPDX-FileCopyrightText: none
3 * SPDX-License-Identifier: CC0-1.0
4 */
5
6 package dev.metaschema.core.metapath.function.impl;
7
8 import java.time.Duration;
9 import java.time.LocalDate;
10 import java.time.OffsetTime;
11 import java.time.Period;
12 import java.time.ZonedDateTime;
13 import java.time.temporal.Temporal;
14 import java.util.Set;
15
16 import dev.metaschema.core.metapath.DynamicContext;
17 import dev.metaschema.core.metapath.function.ArithmeticFunctionException;
18 import dev.metaschema.core.metapath.function.CastFunctionException;
19 import dev.metaschema.core.metapath.function.DateTimeFunctionException;
20 import dev.metaschema.core.metapath.function.library.FnDateTime;
21 import dev.metaschema.core.metapath.item.atomic.IAnyAtomicItem;
22 import dev.metaschema.core.metapath.item.atomic.IAnyUriItem;
23 import dev.metaschema.core.metapath.item.atomic.IBase64BinaryItem;
24 import dev.metaschema.core.metapath.item.atomic.IBooleanItem;
25 import dev.metaschema.core.metapath.item.atomic.IDateItem;
26 import dev.metaschema.core.metapath.item.atomic.IDateTimeItem;
27 import dev.metaschema.core.metapath.item.atomic.IDayTimeDurationItem;
28 import dev.metaschema.core.metapath.item.atomic.IDecimalItem;
29 import dev.metaschema.core.metapath.item.atomic.IDurationItem;
30 import dev.metaschema.core.metapath.item.atomic.IIntegerItem;
31 import dev.metaschema.core.metapath.item.atomic.INumericItem;
32 import dev.metaschema.core.metapath.item.atomic.IStringItem;
33 import dev.metaschema.core.metapath.item.atomic.ITimeItem;
34 import dev.metaschema.core.metapath.item.atomic.IUntypedAtomicItem;
35 import dev.metaschema.core.metapath.item.atomic.IYearMonthDurationItem;
36 import dev.metaschema.core.metapath.type.InvalidTypeMetapathException;
37 import dev.metaschema.core.util.ObjectUtils;
38 import edu.umd.cs.findbugs.annotations.NonNull;
39 import edu.umd.cs.findbugs.annotations.Nullable;
40 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
41
42 /**
43 * Implementations of the XPath 3.1 operation functions.
44 */
45 @SuppressWarnings({
46 // FIXME: break out methods into separate classes organized as in XPath
47 "PMD.CouplingBetweenObjects",
48 "PMD.ExcessivePublicCount",
49 "PMD.CyclomaticComplexity"
50 })
51 public final class OperationFunctions {
52 @SuppressFBWarnings(value = "MS_EXPOSE_REP", justification = "Value is immutable")
53 @NonNull
54 private static final IDateItem DATE_1972_12_31 = IDateItem.valueOf(ObjectUtils.notNull(LocalDate.of(1972, 12, 31)));
55 /**
56 * Identifies the types and substypes that support aggregation.
57 */
58 @SuppressFBWarnings(value = "MS_EXPOSE_REP", justification = "Value is immutable")
59 @NonNull
60 private static final Set<Class<? extends IAnyAtomicItem>> AGGREGATE_MATH_TYPES = ObjectUtils.notNull(Set.of(
61 IDayTimeDurationItem.class,
62 IYearMonthDurationItem.class,
63 INumericItem.class));
64
65 /**
66 * Get a reference date for use in date-based operations.
67 *
68 * @return the reference date
69 */
70 @NonNull
71 public static IDateItem referenceDate() {
72 return DATE_1972_12_31;
73 }
74
75 /**
76 * Get the types supported by aggregation operations.
77 * <p>
78 * Any associated subtypes are also supported.
79 *
80 * @return the set of supported types
81 */
82 @NonNull
83 public static Set<Class<? extends IAnyAtomicItem>> aggregateMathTypes() {
84 return AGGREGATE_MATH_TYPES;
85 }
86
87 private OperationFunctions() {
88 // disable
89 }
90
91 /**
92 * Based on XPath 3.1 <a href=
93 * "https://www.w3.org/TR/xpath-functions-31/#func-add-yearMonthDuration-to-date">op:add-yearMonthDuration-to-date</a>.
94 *
95 * @param instant
96 * a point in time
97 * @param duration
98 * the duration to add
99 * @return the result of adding the duration to the date
100 */
101 @NonNull
102 public static IDateItem opAddYearMonthDurationToDate(@NonNull IDateItem instant,
103 @NonNull IYearMonthDurationItem duration) {
104 return opAddYearMonthDurationToDateTime(instant.asDateTime(), duration).asDate();
105 }
106
107 /**
108 * Based on XPath 3.1 <a href=
109 * "https://www.w3.org/TR/xpath-functions-31/#func-add-dayTimeDuration-to-date">op:add-dayTimeDuration-to-date</a>.
110 *
111 * @param instant
112 * a point in time
113 * @param duration
114 * the duration to add
115 * @return the result of adding the duration to the date
116 */
117 @NonNull
118 public static IDateItem opAddDayTimeDurationToDate(
119 @NonNull IDateItem instant,
120 @NonNull IDayTimeDurationItem duration) {
121 return opAddDayTimeDurationToDateTime(instant.asDateTime(), duration).asDate();
122 }
123
124 /**
125 * Based on XPath 3.1 <a href=
126 * "https://www.w3.org/TR/xpath-functions-31/#func-add-yearMonthDurations">op:add-yearMonthDurations</a>.
127 *
128 * @param arg1
129 * the first duration
130 * @param arg2
131 * the second duration
132 * @return the sum of two duration values
133 */
134 @NonNull
135 public static IYearMonthDurationItem opAddYearMonthDurations(
136 @NonNull IYearMonthDurationItem arg1,
137 @NonNull IYearMonthDurationItem arg2) {
138 Period duration1 = arg1.asPeriod();
139 Period duration2 = arg2.asPeriod();
140
141 Period result;
142 try {
143 result = duration1.plus(duration2);
144 } catch (ArithmeticException ex) {
145 throw new DateTimeFunctionException(DateTimeFunctionException.DURATION_OVERFLOW_UNDERFLOW_ERROR, ex);
146 }
147 assert result != null;
148 return IYearMonthDurationItem.valueOf(result);
149 }
150
151 /**
152 * Based on XPath 3.1 <a href=
153 * "https://www.w3.org/TR/xpath-functions-31/#func-add-dayTimeDurations">op:add-dayTimeDurations</a>.
154 *
155 * @param arg1
156 * the first duration
157 * @param arg2
158 * the second duration
159 * @return the sum of two duration values
160 */
161 @NonNull
162 public static IDayTimeDurationItem opAddDayTimeDurations(
163 @NonNull IDayTimeDurationItem arg1,
164 @NonNull IDayTimeDurationItem arg2) {
165 Duration duration1 = arg1.asDuration();
166 Duration duration2 = arg2.asDuration();
167
168 Duration result;
169 try {
170 result = duration1.plus(duration2);
171 } catch (ArithmeticException ex) {
172 throw new DateTimeFunctionException(DateTimeFunctionException.DURATION_OVERFLOW_UNDERFLOW_ERROR, ex);
173 }
174 assert result != null;
175 return IDayTimeDurationItem.valueOf(result);
176 }
177
178 /**
179 * Based on XPath 3.1 <a href=
180 * "https://www.w3.org/TR/xpath-functions-31/#func-add-yearMonthDuration-to-dateTime">op:add-yearMonthDuration-to-dateTime</a>.
181 *
182 * @param instant
183 * a point in time
184 * @param duration
185 * the duration to add
186 * @return the result of adding the duration to the date
187 */
188 @NonNull
189 public static IDateTimeItem opAddYearMonthDurationToDateTime(
190 @NonNull IDateTimeItem instant,
191 @NonNull IYearMonthDurationItem duration) {
192 ZonedDateTime result;
193 try {
194 result = instant.asZonedDateTime().plus(duration.asPeriod());
195 } catch (ArithmeticException ex) {
196 throw new ArithmeticFunctionException(ArithmeticFunctionException.OVERFLOW_UNDERFLOW_ERROR, ex);
197 }
198 assert result != null;
199 // preserve the same timezone "presence"
200 return IDateTimeItem.valueOf(result, instant.hasTimezone());
201 }
202
203 /**
204 * Based on XPath 3.1 <a href=
205 * "https://www.w3.org/TR/xpath-functions-31/#func-add-dayTimeDuration-to-dateTime">op:add-dayTimeDuration-to-dateTime</a>.
206 *
207 * @param instant
208 * a point in time
209 * @param duration
210 * the duration to add
211 * @return the result of adding the duration to the date
212 */
213 @NonNull
214 public static IDateTimeItem opAddDayTimeDurationToDateTime(
215 @NonNull IDateTimeItem instant,
216 @NonNull IDayTimeDurationItem duration) {
217 ZonedDateTime result;
218 try {
219 result = instant.asZonedDateTime().plus(duration.asDuration());
220 } catch (ArithmeticException ex) {
221 throw new ArithmeticFunctionException(ArithmeticFunctionException.OVERFLOW_UNDERFLOW_ERROR, ex);
222 }
223 assert result != null;
224 // preserve the same timezone "presence"
225 return IDateTimeItem.valueOf(result, instant.hasTimezone());
226 }
227
228 /**
229 * Based on XPath 3.1 <a href=
230 * "https://www.w3.org/TR/xpath-functions-31/#func-add-dayTimeDuration-to-time">op:add-dayTimeDuration-to-time</a>.
231 *
232 * @param instant
233 * a point in time
234 * @param duration
235 * the duration to add
236 * @return the result of adding the duration to the date
237 */
238 @NonNull
239 public static ITimeItem opAddDayTimeDurationToTime(
240 @NonNull ITimeItem instant,
241 @NonNull IDayTimeDurationItem duration) {
242 long seconds = duration.asDuration().toSeconds() % 86_400;
243 int nano = duration.asDuration().getNano();
244
245 OffsetTime result;
246 try {
247 result = instant.asOffsetTime().plus(Duration.ofSeconds(seconds, nano));
248 } catch (ArithmeticException ex) {
249 throw new ArithmeticFunctionException(ArithmeticFunctionException.OVERFLOW_UNDERFLOW_ERROR, ex);
250 }
251 assert result != null;
252 // preserve the same timezone "presence"
253 return ITimeItem.valueOf(result, instant.hasTimezone());
254 }
255
256 /**
257 * Based on XPath 3.1 <a href=
258 * "https://www.w3.org/TR/xpath-functions-31/#func-subtract-dates">op:subtract-dates</a>.
259 *
260 * @param date1
261 * the first point in time
262 * @param date2
263 * the second point in time
264 * @param dynamicContext
265 * the dynamic context used to provide the implicit timezone
266 * @return the elapsed time between the starting instant and ending instant
267 */
268 @NonNull
269 public static IDayTimeDurationItem opSubtractDates(
270 @NonNull IDateItem date1,
271 @NonNull IDateItem date2,
272 @NonNull DynamicContext dynamicContext) {
273 return opSubtractDateTimes(
274 date1.asDateTime(),
275 date2.asDateTime(),
276 dynamicContext);
277 }
278
279 /**
280 * Based on XPath 3.1 <a href=
281 * "https://www.w3.org/TR/xpath-functions-31/#func-subtract-yearMonthDuration-from-date">op:subtract-yearMonthDuration-from-date</a>.
282 *
283 * @param date
284 * a point in time
285 * @param duration
286 * the duration to subtract
287 * @return the result of subtracting the duration from the date
288 */
289 @NonNull
290 public static IDateItem opSubtractYearMonthDurationFromDate(
291 @NonNull IDateItem date,
292 @NonNull IYearMonthDurationItem duration) {
293 return opAddYearMonthDurationToDate(date, duration.negate());
294 }
295
296 /**
297 * Based on XPath 3.1 <a href=
298 * "https://www.w3.org/TR/xpath-functions-31/#func-subtract-dayTimeDuration-from-date">op:subtract-dayTimeDuration-from-date</a>.
299 *
300 * @param date
301 * a point in time
302 * @param duration
303 * the duration to subtract
304 * @return the result of subtracting the duration from the date
305 */
306 @NonNull
307 public static IDateItem opSubtractDayTimeDurationFromDate(
308 @NonNull IDateItem date,
309 @NonNull IDayTimeDurationItem duration) {
310 return opAddDayTimeDurationToDate(date, duration.negate());
311 }
312
313 /**
314 * Based on XPath 3.1 <a href=
315 * "https://www.w3.org/TR/xpath-functions-31/#func-subtract-dayTimeDuration-from-time">op:subtract-dayTimeDuration-from-time</a>.
316 *
317 * @param time
318 * a point in time
319 * @param duration
320 * the duration to subtract
321 * @return the result of subtracting the duration from the time
322 */
323 @NonNull
324 public static ITimeItem opSubtractDayTimeDurationFromTime(
325 @NonNull ITimeItem time,
326 @NonNull IDayTimeDurationItem duration) {
327 return opAddDayTimeDurationToTime(time, duration.negate());
328 }
329
330 /**
331 * Based on XPath 3.1 <a href=
332 * "https://www.w3.org/TR/xpath-functions-31/#func-subtract-times">op:subtract-times</a>.
333 *
334 * @param arg1
335 * the first duration
336 * @param arg2
337 * the second duration
338 * @param dynamicContext
339 * the dynamic context used to provide the implicit timezone
340 * @return the result of subtracting the second duration from the first
341 */
342 @NonNull
343 public static IDayTimeDurationItem opSubtractTimes(
344 @NonNull ITimeItem arg1,
345 @NonNull ITimeItem arg2,
346 @NonNull DynamicContext dynamicContext) {
347 return opSubtractDateTimes(
348 FnDateTime.fnDateTime(DATE_1972_12_31, arg1),
349 FnDateTime.fnDateTime(DATE_1972_12_31, arg2),
350 dynamicContext);
351 }
352
353 /**
354 * Based on XPath 3.1 <a href=
355 * "https://www.w3.org/TR/xpath-functions-31/#func-subtract-yearMonthDurations">op:subtract-yearMonthDurations</a>.
356 *
357 * @param arg1
358 * the first duration
359 * @param arg2
360 * the second duration
361 * @return the result of subtracting the second duration from the first
362 */
363 @NonNull
364 public static IYearMonthDurationItem opSubtractYearMonthDurations(
365 @NonNull IYearMonthDurationItem arg1,
366 @NonNull IYearMonthDurationItem arg2) {
367 Period duration1 = arg1.asPeriod();
368 Period duration2 = arg2.asPeriod();
369
370 Period result;
371 try {
372 result = ObjectUtils.notNull(duration1.minus(duration2));
373 } catch (ArithmeticException ex) {
374 throw new DateTimeFunctionException(DateTimeFunctionException.DURATION_OVERFLOW_UNDERFLOW_ERROR, ex);
375 }
376 return IYearMonthDurationItem.valueOf(result);
377 }
378
379 /**
380 * Based on XPath 3.1 <a href=
381 * "https://www.w3.org/TR/xpath-functions-31/#func-subtract-dayTimeDurations">op:subtract-dayTimeDurations</a>.
382 *
383 * @param arg1
384 * the first duration
385 * @param arg2
386 * the second duration
387 * @return the result of subtracting the second duration from the first
388 */
389 @NonNull
390 public static IDayTimeDurationItem opSubtractDayTimeDurations(
391 @NonNull IDayTimeDurationItem arg1,
392 @NonNull IDayTimeDurationItem arg2) {
393 Duration duration1 = arg1.asDuration();
394 Duration duration2 = arg2.asDuration();
395
396 Duration result;
397 try {
398 result = ObjectUtils.notNull(duration1.minus(duration2));
399 } catch (ArithmeticException ex) {
400 throw new DateTimeFunctionException(DateTimeFunctionException.DURATION_OVERFLOW_UNDERFLOW_ERROR, ex);
401 }
402 return IDayTimeDurationItem.valueOf(result);
403 }
404
405 /**
406 * Based on XPath 3.1 <a href=
407 * "https://www.w3.org/TR/xpath-functions-31/#func-subtract-dateTimes">op:subtract-dateTimes</a>.
408 *
409 * @param instant1
410 * the first point in time
411 * @param instant2
412 * the second point in time
413 * @param dynamicContext
414 * the dynamic context used to provide the implicit timezone
415 * @return the duration the occurred between the two points in time
416 */
417 @NonNull
418 public static IDayTimeDurationItem opSubtractDateTimes(
419 @NonNull IDateTimeItem instant1,
420 @NonNull IDateTimeItem instant2,
421 @NonNull DynamicContext dynamicContext) {
422 return between(
423 instant2.normalize(dynamicContext).asZonedDateTime(),
424 instant1.normalize(dynamicContext).asZonedDateTime());
425 }
426
427 /**
428 * Based on XPath 3.1 <a href=
429 * "https://www.w3.org/TR/xpath-functions-31/#func-subtract-dateTimes">op:subtract-dateTimes</a>
430 * and <a href=
431 * "https://www.w3.org/TR/xpath-functions-31/#func-subtract-times">op:subtract-times</a>.
432 *
433 * @param time1
434 * the first point in time
435 * @param time2
436 * the second point in time
437 * @return the duration the occurred between the two points in time
438 */
439 @NonNull
440 private static IDayTimeDurationItem between(@NonNull Temporal time1, @NonNull Temporal time2) {
441 return IDayTimeDurationItem.valueOf(ObjectUtils.notNull(Duration.between(time1, time2)));
442 }
443
444 /**
445 * Based on XPath 3.1 <a href=
446 * "https://www.w3.org/TR/xpath-functions-31/#func-subtract-yearMonthDuration-from-dateTime">op:subtract-yearMonthDuration-from-dateTime</a>.
447 *
448 * @param moment
449 * a point in time
450 * @param duration
451 * the duration to subtract
452 * @return the result of subtracting the duration from a point in time
453 */
454 @NonNull
455 public static IDateTimeItem opSubtractYearMonthDurationFromDateTime(
456 @NonNull IDateTimeItem moment,
457 @NonNull IYearMonthDurationItem duration) {
458 return opAddYearMonthDurationToDateTime(moment, duration.negate());
459 }
460
461 /**
462 * Based on XPath 3.1 <a href=
463 * "https://www.w3.org/TR/xpath-functions-31/#func-subtract-dayTimeDuration-from-dateTime">op:subtract-dayTimeDuration-from-dateTime</a>.
464 *
465 * @param moment
466 * a point in time
467 * @param duration
468 * the duration to subtract
469 * @return the result of subtracting the duration from a point in time
470 */
471 @NonNull
472 public static IDateTimeItem opSubtractDayTimeDurationFromDateTime(
473 @NonNull IDateTimeItem moment,
474 @NonNull IDayTimeDurationItem duration) {
475 // preserve the same timezone "presence"
476 return opAddDayTimeDurationToDateTime(moment, duration.negate());
477 }
478
479 /**
480 * Based on XPath 3.1 <a href=
481 * "https://www.w3.org/TR/xpath-functions-31/#func-multiply-yearMonthDuration">op:multiply-yearMonthDuration</a>.
482 *
483 * @param arg1
484 * the duration value
485 * @param arg2
486 * the number to multiply by
487 * @return the result of multiplying a {@link IYearMonthDurationItem} by a
488 * number
489 * @throws DateTimeFunctionException
490 * with the code
491 * {@link DateTimeFunctionException#DURATION_OVERFLOW_UNDERFLOW_ERROR}
492 * if arithmetic overflow occurs
493 */
494 @NonNull
495 public static IYearMonthDurationItem opMultiplyYearMonthDuration(
496 @NonNull IYearMonthDurationItem arg1,
497 @NonNull INumericItem arg2) {
498 IDecimalItem months = IDecimalItem.valueOf(arg1.asTotalMonths());
499 INumericItem result = months.multiply(arg2);
500 Period period;
501 try {
502 period = Period.ofMonths(result.round().toIntValueExact());
503 } catch (CastFunctionException ex) {
504 throw new DateTimeFunctionException(DateTimeFunctionException.DURATION_OVERFLOW_UNDERFLOW_ERROR, ex);
505 }
506 return IYearMonthDurationItem.valueOf(ObjectUtils.notNull(period));
507 }
508
509 /**
510 * Based on XPath 3.1 <a href=
511 * "https://www.w3.org/TR/xpath-functions-31/#func-multiply-dayTimeDuration">op:multiply-dayTimeDuration</a>.
512 *
513 * @param arg1
514 * the duration value
515 * @param arg2
516 * the number to multiply by
517 * @return the result of multiplying a {@link IDayTimeDurationItem} by a number
518 */
519 @NonNull
520 public static IDayTimeDurationItem opMultiplyDayTimeDuration(
521 @NonNull IDayTimeDurationItem arg1,
522 @NonNull INumericItem arg2) {
523 IDecimalItem seconds = IDecimalItem.valueOf(arg1.asSeconds());
524 INumericItem result = seconds.multiply(arg2);
525 Duration duration;
526 try {
527 duration = Duration.ofSeconds(result.round().toIntValueExact());
528 } catch (CastFunctionException ex) {
529 throw new DateTimeFunctionException(DateTimeFunctionException.DURATION_OVERFLOW_UNDERFLOW_ERROR, ex);
530 }
531 return IDayTimeDurationItem.valueOf(ObjectUtils.notNull(duration));
532 }
533
534 /**
535 * Based on XPath 3.1 <a href=
536 * "https://www.w3.org/TR/xpath-functions-31/#func-divide-yearMonthDuration">op:divide-yearMonthDuration</a>.
537 *
538 * @param arg1
539 * the duration value
540 * @param arg2
541 * the number to divide by
542 * @return the result of dividing a {@link IYearMonthDurationItem} by a number
543 */
544 @NonNull
545 public static IYearMonthDurationItem opDivideYearMonthDuration(
546 @NonNull IYearMonthDurationItem arg1,
547 @NonNull INumericItem arg2) {
548 IDecimalItem months = IDecimalItem.valueOf(arg1.asTotalMonths());
549 INumericItem result = months.divide(arg2);
550
551 Period period = Period.ofMonths(result.round().toIntValueExact());
552 return IYearMonthDurationItem.valueOf(ObjectUtils.notNull(period));
553 }
554
555 /**
556 * Based on XPath 3.1 <a href=
557 * "https://www.w3.org/TR/xpath-functions-31/#func-divide-yearMonthDuration-by-yearMonthDuration">op:divide-yearMonthDuration-by-yearMonthDuration</a>.
558 *
559 * @param arg1
560 * the first duration value
561 * @param arg2
562 * the second duration value
563 * @return the result of dividing a the first duration value by the second
564 * duration value
565 */
566 @NonNull
567 public static IDecimalItem opDivideYearMonthDurationByYearMonthDuration(
568 @NonNull IYearMonthDurationItem arg1,
569 @NonNull IYearMonthDurationItem arg2) {
570 IIntegerItem totalMonths1 = IIntegerItem.valueOf(arg1.asTotalMonths());
571 IIntegerItem totalMonths2 = IIntegerItem.valueOf(arg2.asTotalMonths());
572
573 return totalMonths1.divide(totalMonths2);
574 }
575
576 /**
577 * Based on XPath 3.1 <a href=
578 * "https://www.w3.org/TR/xpath-functions-31/#func-divide-dayTimeDuration">op:divide-dayTimeDuration</a>.
579 *
580 * @param arg1
581 * the duration value
582 * @param arg2
583 * the number to divide by
584 * @return the result of dividing a {@link IDayTimeDurationItem} by a number
585 */
586 @NonNull
587 public static IDayTimeDurationItem opDivideDayTimeDuration(
588 @NonNull IDayTimeDurationItem arg1,
589 @NonNull INumericItem arg2) {
590 IDecimalItem seconds = IDecimalItem.valueOf(arg1.asSeconds());
591 INumericItem result = seconds.divide(arg2);
592
593 Duration duration = Duration.ofSeconds(result.round().toIntValueExact());
594 return IDayTimeDurationItem.valueOf(ObjectUtils.notNull(duration));
595 }
596
597 /**
598 * Based on XPath 3.1 <a href=
599 * "https://www.w3.org/TR/xpath-functions-31/#func-divide-dayTimeDuration-by-dayTimeDuration">op:divide-dayTimeDuration-by-dayTimeDuration</a>.
600 *
601 * @param arg1
602 * the first duration value
603 * @param arg2
604 * the second duration value
605 * @return the ratio of two {@link IDayTimeDurationItem} values, as a decimal
606 * number
607 */
608 @NonNull
609 public static IDecimalItem opDivideDayTimeDurationByDayTimeDuration(
610 @NonNull IDayTimeDurationItem arg1,
611 @NonNull IDayTimeDurationItem arg2) {
612 return IDecimalItem.cast(
613 opNumericDivide(
614 IDecimalItem.valueOf(arg1.asSeconds()),
615 IDecimalItem.valueOf(arg2.asSeconds())));
616 }
617
618 /**
619 * Based on XPath 3.1 <a href=
620 * "https://www.w3.org/TR/xpath-functions-31/#func-date-equal">op:date-equal</a>.
621 *
622 * @param arg1
623 * the first value
624 * @param arg2
625 * the second value
626 * @param dynamicContext
627 * used to get the implicit timezone from the evaluation context
628 * @return {@code true} if the first argument is the same instant in time as the
629 * second, or {@code false} otherwise
630 */
631 @NonNull
632 public static IBooleanItem opDateEqual(
633 @NonNull IDateItem arg1,
634 @NonNull IDateItem arg2,
635 @NonNull DynamicContext dynamicContext) {
636 return opDateTimeEqual(arg1.asDateTime(), arg2.asDateTime(), dynamicContext);
637 }
638
639 /**
640 * Based on XPath 3.1 <a href=
641 * "https://www.w3.org/TR/xpath-functions-31/#func-dateTime-equal">op:dateTime-equal</a>.
642 *
643 * @param arg1
644 * the first value
645 * @param arg2
646 * the second value
647 * @param dynamicContext
648 * used to get the implicit timezone from the evaluation context
649 * @return {@code true} if the first argument is the same instant in time as the
650 * second, or {@code false} otherwise
651 */
652 @NonNull
653 public static IBooleanItem opDateTimeEqual(
654 @NonNull IDateTimeItem arg1,
655 @NonNull IDateTimeItem arg2,
656 @Nullable DynamicContext dynamicContext) {
657 IDateTimeItem arg1Normalized = dynamicContext == null ? arg1 : arg1.normalize(dynamicContext);
658 IDateTimeItem arg2Normalized = dynamicContext == null ? arg2 : arg2.normalize(dynamicContext);
659 return IBooleanItem.valueOf(arg1Normalized.asZonedDateTime().isEqual(arg2Normalized.asZonedDateTime()));
660 }
661
662 /**
663 * Based on XPath 3.1 <a href=
664 * "https://www.w3.org/TR/xpath-functions-31/#func-time-equal">op:time-equal</a>.
665 *
666 * @param arg1
667 * the first value
668 * @param arg2
669 * the second value
670 * @param dynamicContext
671 * used to get the implicit timezone from the evaluation context
672 * @return {@code true} if the first argument is the same instant in time as the
673 * second, or {@code false} otherwise
674 */
675 @NonNull
676 public static IBooleanItem opTimeEqual(
677 @NonNull ITimeItem arg1,
678 @NonNull ITimeItem arg2,
679 @NonNull DynamicContext dynamicContext) {
680 IDateTimeItem time1 = IDateTimeItem.valueOf(DATE_1972_12_31, arg1);
681 IDateTimeItem time2 = IDateTimeItem.valueOf(DATE_1972_12_31, arg2);
682 return opDateTimeEqual(time1, time2, dynamicContext);
683 }
684
685 /**
686 * Based on XPath 3.1 <a href=
687 * "https://www.w3.org/TR/xpath-functions-31/#func-duration-equal">op:duration-equal</a>.
688 *
689 * @param arg1
690 * the first value
691 * @param arg2
692 * the second value
693 * @return {@code true} if the first argument is the same duration as the
694 * second, or {@code false} otherwise
695 */
696 @NonNull
697 public static IBooleanItem opDurationEqual(@NonNull IDurationItem arg1, @NonNull IDurationItem arg2) {
698 return IBooleanItem.valueOf(arg1.compareTo(arg2) == 0);
699 }
700
701 /**
702 * Based on XPath 3.1 <a href=
703 * "https://www.w3.org/TR/xpath-functions-31/#func-base64Binary-equal">op:base64Binary-equal</a>.
704 *
705 * @param arg1
706 * the first value
707 * @param arg2
708 * the second value
709 * @return {@code true} if the first argument is equal to the second, or
710 * {@code false} otherwise
711 */
712 @NonNull
713 public static IBooleanItem opBase64BinaryEqual(@NonNull IBase64BinaryItem arg1, @NonNull IBase64BinaryItem arg2) {
714 return IBooleanItem.valueOf(arg1.asByteBuffer().equals(arg2.asByteBuffer()));
715 }
716
717 /**
718 * Based on XPath 3.1 <a href=
719 * "https://www.w3.org/TR/xpath-functions-31/#func-date-greater-than">op:date-greater-than</a>.
720 *
721 * @param arg1
722 * the first value
723 * @param arg2
724 * the second value
725 * @param dynamicContext
726 * used to get the implicit timezone from the evaluation context
727 * @return {@code true} if the first argument is a later instant in time than
728 * the second, or {@code false} otherwise
729 */
730 @NonNull
731 public static IBooleanItem opDateGreaterThan(
732 @NonNull IDateItem arg1,
733 @NonNull IDateItem arg2,
734 @NonNull DynamicContext dynamicContext) {
735 return opDateLessThan(arg2, arg1, dynamicContext);
736 }
737
738 /**
739 * Based on XPath 3.1 <a href=
740 * "https://www.w3.org/TR/xpath-functions-31/#func-dateTime-greater-than">op:dateTime-greater-than</a>.
741 *
742 * @param arg1
743 * the first value
744 * @param arg2
745 * the second value
746 * @param dynamicContext
747 * used to get the implicit timezone from the evaluation context
748 * @return {@code true} if the first argument is a later instant in time than
749 * the second, or {@code false} otherwise
750 */
751 @NonNull
752 public static IBooleanItem opDateTimeGreaterThan(
753 @NonNull IDateTimeItem arg1,
754 @NonNull IDateTimeItem arg2,
755 @Nullable DynamicContext dynamicContext) {
756 return opDateTimeLessThan(arg2, arg1, dynamicContext);
757 }
758
759 /**
760 * Based on XPath 3.1 <a href=
761 * "https://www.w3.org/TR/xpath-functions-31/#func-time-greater-than">op:time-greater-than</a>.
762 *
763 * @param arg1
764 * the first value
765 * @param arg2
766 * the second value
767 * @param dynamicContext
768 * used to get the implicit timezone from the evaluation context
769 * @return {@code true} if the first argument is a later instant in time than
770 * the second, or {@code false} otherwise
771 */
772 @NonNull
773 public static IBooleanItem opTimeGreaterThan(
774 @NonNull ITimeItem arg1,
775 @NonNull ITimeItem arg2,
776 @NonNull DynamicContext dynamicContext) {
777 return opTimeLessThan(arg2, arg1, dynamicContext);
778 }
779
780 /**
781 * Based on XPath 3.1 <a href=
782 * "https://www.w3.org/TR/xpath-functions-31/#func-yearMonthDuration-greater-than">op:yearMonthDuration-greater-than</a>.
783 *
784 * @param arg1
785 * the first value
786 * @param arg2
787 * the second value
788 * @return {@code true} if the first argument is a longer duration than the
789 * second, or {@code false} otherwise
790 */
791 @NonNull
792 public static IBooleanItem opYearMonthDurationGreaterThan(
793 @NonNull IYearMonthDurationItem arg1,
794 @NonNull IYearMonthDurationItem arg2) {
795 return opYearMonthDurationLessThan(arg2, arg1);
796 }
797
798 /**
799 * Based on XPath 3.1 <a href=
800 * "https://www.w3.org/TR/xpath-functions-31/#func-dayTimeDuration-greater-than">op:dayTimeDuration-greater-than</a>.
801 *
802 * @param arg1
803 * the first value
804 * @param arg2
805 * the second value
806 * @return {@code true} if the first argument is a longer duration than the
807 * second, or {@code false} otherwise
808 */
809 @NonNull
810 public static IBooleanItem opDayTimeDurationGreaterThan(
811 @NonNull IDayTimeDurationItem arg1,
812 @NonNull IDayTimeDurationItem arg2) {
813 return opDayTimeDurationLessThan(arg2, arg1);
814 }
815
816 /**
817 * Based on XPath 3.1 <a href=
818 * "https://www.w3.org/TR/xpath-functions-31/#func-base64Binary-greater-than">op:base64Binary-greater-than</a>.
819 *
820 * @param arg1
821 * the first value
822 * @param arg2
823 * the second value
824 * @return {@code true} if the first argument is greater than the second, or
825 * {@code false} otherwise
826 */
827 @NonNull
828 public static IBooleanItem opBase64BinaryGreaterThan(
829 @NonNull IBase64BinaryItem arg1,
830 @NonNull IBase64BinaryItem arg2) {
831 return opBase64BinaryLessThan(arg2, arg1);
832 }
833
834 /**
835 * Based on XPath 3.1 <a href=
836 * "https://www.w3.org/TR/xpath-functions-31/#func-date-less-than">op:date-less-than</a>.
837 *
838 * @param arg1
839 * the first value
840 * @param arg2
841 * the second value
842 * @param dynamicContext
843 * used to get the implicit timezone from the evaluation context
844 * @return {@code true} if the first argument is an earlier instant in time than
845 * the second, or {@code false} otherwise
846 */
847 @NonNull
848 public static IBooleanItem opDateLessThan(
849 @NonNull IDateItem arg1,
850 @NonNull IDateItem arg2,
851 @NonNull DynamicContext dynamicContext) {
852 return opDateTimeLessThan(arg1.asDateTime(), arg2.asDateTime(), dynamicContext);
853 }
854
855 /**
856 * Based on XPath 3.1 <a href=
857 * "https://www.w3.org/TR/xpath-functions-31/#func-dateTime-less-than">op:dateTime-less-than</a>.
858 *
859 * @param arg1
860 * the first value
861 * @param arg2
862 * the second value
863 * @param dynamicContext
864 * used to get the implicit timezone from the evaluation context
865 * @return {@code true} if the first argument is an earlier instant in time than
866 * the second, or {@code false} otherwise
867 */
868 @NonNull
869 public static IBooleanItem opTimeLessThan(
870 @NonNull ITimeItem arg1,
871 @NonNull ITimeItem arg2,
872 @NonNull DynamicContext dynamicContext) {
873 return opDateTimeLessThan(
874 IDateTimeItem.valueOf(DATE_1972_12_31, arg1),
875 IDateTimeItem.valueOf(DATE_1972_12_31, arg2),
876 dynamicContext);
877 }
878
879 /**
880 * Based on XPath 3.1 <a href=
881 * "https://www.w3.org/TR/xpath-functions-31/#func-time-less-than">op:time-less-than</a>.
882 *
883 * @param arg1
884 * the first value
885 * @param arg2
886 * the second value
887 * @param dynamicContext
888 * used to get the implicit timezone from the evaluation context
889 * @return {@code true} if the first argument is an earlier instant in time than
890 * the second, or {@code false} otherwise
891 */
892 @NonNull
893 public static IBooleanItem opDateTimeLessThan(
894 @NonNull IDateTimeItem arg1,
895 @NonNull IDateTimeItem arg2,
896 @Nullable DynamicContext dynamicContext) {
897 IDateTimeItem arg1Normalized = dynamicContext == null ? arg1 : arg1.normalize(dynamicContext);
898 IDateTimeItem arg2Normalized = dynamicContext == null ? arg2 : arg2.normalize(dynamicContext);
899 return IBooleanItem.valueOf(arg1Normalized.asZonedDateTime().isBefore(arg2Normalized.asZonedDateTime()));
900 }
901
902 /**
903 * Based on XPath 3.1 <a href=
904 * "https://www.w3.org/TR/xpath-functions-31/#func-yearMonthDuration-less-than">op:yearMonthDuration-less-than</a>.
905 *
906 * @param arg1
907 * the first value
908 * @param arg2
909 * the second value
910 * @return {@code true} if the first argument is a shorter duration than the
911 * second, or {@code false} otherwise
912 */
913 @NonNull
914 public static IBooleanItem opYearMonthDurationLessThan(
915 @NonNull IYearMonthDurationItem arg1,
916 @NonNull IYearMonthDurationItem arg2) {
917 return IBooleanItem.valueOf(arg1.asTotalMonths() < arg2.asTotalMonths());
918 }
919
920 /**
921 * Based on XPath 3.1 <a href=
922 * "https://www.w3.org/TR/xpath-functions-31/#func-dayTimeDuration-less-than">op:dayTimeDuration-less-than</a>.
923 *
924 * @param arg1
925 * the first value
926 * @param arg2
927 * the second value
928 * @return {@code true} if the first argument is a shorter duration than the
929 * second, or {@code false} otherwise
930 */
931 @NonNull
932 public static IBooleanItem opDayTimeDurationLessThan(
933 @NonNull IDayTimeDurationItem arg1,
934 @NonNull IDayTimeDurationItem arg2) {
935 return IBooleanItem.valueOf(arg1.asSeconds() < arg2.asSeconds());
936 }
937
938 /**
939 * Based on XPath 3.1 <a href=
940 * "https://www.w3.org/TR/xpath-functions-31/#func-base64Binary-less-than">op:base64Binary-less-than</a>.
941 *
942 * @param arg1
943 * the first value
944 * @param arg2
945 * the second value
946 * @return {@code true} if the first argument is less than the second, or
947 * {@code false} otherwise
948 */
949 @NonNull
950 public static IBooleanItem opBase64BinaryLessThan(
951 @NonNull IBase64BinaryItem arg1,
952 @NonNull IBase64BinaryItem arg2) {
953 return IBooleanItem.valueOf(arg1.compareTo(arg2) < 0);
954 }
955
956 @NonNull
957 private static <R extends INumericItem> R performArithmeticOperation(
958 @NonNull INumericItem left,
959 @NonNull INumericItem right,
960 @NonNull ArithmeticOperation<IIntegerItem, IIntegerItem, R> integerOp,
961 @NonNull ArithmeticOperation<IDecimalItem, IDecimalItem, R> decimalOp) {
962 R retval;
963 if (left instanceof IIntegerItem && right instanceof IIntegerItem) {
964 retval = integerOp.apply((IIntegerItem) left, (IIntegerItem) right);
965 } else {
966 retval = decimalOp.apply((IDecimalItem) left, (IDecimalItem) right);
967 }
968 return retval;
969 }
970
971 /**
972 * Represents an arithmetic operation performed using two arguments.
973 *
974 * @param <T>
975 * the Java type of the first argument to the function
976 * @param <U>
977 * the Java type of the second argument to the function
978 * @param <R>
979 * the Java type of the result of the function
980 */
981 @FunctionalInterface
982 private interface ArithmeticOperation<T, U, R> {
983 /**
984 * Perform the operation.
985 *
986 * @param left
987 * the left side of the operation
988 * @param right
989 * the right side of the operation
990 * @return the operation result
991 */
992 @NonNull
993 R apply(@NonNull T left, @NonNull U right);
994 }
995
996 /**
997 * Create a new sum by adding first provided addend value to the second provided
998 * addend value.
999 * <p>
1000 * Based on XPath 3.1 <a href=
1001 * "https://www.w3.org/TR/xpath-functions-31/#func-numeric-add">op:numeric-add</a>.
1002 *
1003 * @param addend1
1004 * the first addend
1005 * @param addend2
1006 * the second addend
1007 * @return the result of adding the second number to the first number
1008 */
1009 @NonNull
1010 public static INumericItem opNumericAdd(@NonNull INumericItem addend1, @NonNull INumericItem addend2) {
1011 return performArithmeticOperation(
1012 addend1,
1013 addend2,
1014 IIntegerItem::add,
1015 IDecimalItem::add);
1016 }
1017
1018 /**
1019 * Determine the difference by subtracting the provided subtrahend value from
1020 * the provided minuend value.
1021 * <p>
1022 * Based on XPath 3.1 <a href=
1023 * "https://www.w3.org/TR/xpath-functions-31/#func-numeric-subtract">op:numeric-subtract</a>.
1024 *
1025 * @param minuend
1026 * the value to subtract from
1027 * @param subtrahend
1028 * the value to subtract
1029 * @return a new value resulting from subtracting the subtrahend from the
1030 * minuend
1031 */
1032 @NonNull
1033 public static INumericItem opNumericSubtract(
1034 @NonNull INumericItem minuend,
1035 @NonNull INumericItem subtrahend) {
1036 return performArithmeticOperation(
1037 minuend,
1038 subtrahend,
1039 IIntegerItem::subtract,
1040 IDecimalItem::subtract);
1041 }
1042
1043 /**
1044 * Multiply the provided multiplicand value by the provided multiplier value.
1045 * <p>
1046 * Based on XPath 3.1 <a href=
1047 * "https://www.w3.org/TR/xpath-functions-31/#func-numeric-multiply">op:numeric-multiply</a>.
1048 *
1049 * @param multiplicand
1050 * the value to multiply
1051 * @param multiplier
1052 * the value to multiply by
1053 * @return a new value resulting from multiplying the multiplicand by the
1054 * multiplier
1055 */
1056 @NonNull
1057 public static INumericItem opNumericMultiply(
1058 @NonNull INumericItem multiplicand,
1059 @NonNull INumericItem multiplier) {
1060 return performArithmeticOperation(
1061 multiplicand,
1062 multiplier,
1063 IIntegerItem::multiply,
1064 IDecimalItem::multiply);
1065 }
1066
1067 /**
1068 * Based on XPath 3.1 <a href=
1069 * "https://www.w3.org/TR/xpath-functions-31/#func-numeric-divide">op:numeric-divide</a>.
1070 *
1071 * @param dividend
1072 * the number to be divided
1073 * @param divisor
1074 * the number to divide by
1075 * @return the quotient
1076 * @throws ArithmeticFunctionException
1077 * with the code {@link ArithmeticFunctionException#DIVISION_BY_ZERO}
1078 * if the divisor is zero
1079 */
1080 @NonNull
1081 public static IDecimalItem opNumericDivide(@NonNull INumericItem dividend, @NonNull INumericItem divisor) {
1082 return ((IDecimalItem) dividend).divide((IDecimalItem) divisor);
1083 }
1084
1085 /**
1086 * Based on XPath 3.1 <a href=
1087 * "https://www.w3.org/TR/xpath-functions-31/#func-numeric-integer-divide">op:numeric-integer-divide</a>.
1088 *
1089 * @param dividend
1090 * the number to be divided
1091 * @param divisor
1092 * the number to divide by
1093 * @return the quotient
1094 */
1095 @NonNull
1096 public static IIntegerItem opNumericIntegerDivide(
1097 @NonNull INumericItem dividend,
1098 @NonNull INumericItem divisor) {
1099 return performArithmeticOperation(
1100 dividend,
1101 divisor,
1102 IIntegerItem::integerDivide,
1103 IDecimalItem::integerDivide);
1104 }
1105
1106 /**
1107 * Based on XPath 3.1 <a href=
1108 * "https://www.w3.org/TR/xpath-functions-31/#func-numeric-mod">op:numeric-mod</a>.
1109 *
1110 * @param dividend
1111 * the number to be divided
1112 * @param divisor
1113 * the number to divide by
1114 * @return the remainder
1115 */
1116 @NonNull
1117 public static INumericItem opNumericMod(@NonNull INumericItem dividend, @NonNull INumericItem divisor) {
1118 return performArithmeticOperation(
1119 dividend,
1120 divisor,
1121 IIntegerItem::mod,
1122 IDecimalItem::mod);
1123 }
1124
1125 /**
1126 * Based on XPath 3.1 <a href=
1127 * "https://www.w3.org/TR/xpath-functions-31/#func-numeric-unary-minus">op:numeric-unary-minus</a>.
1128 *
1129 * @param item
1130 * the number whose sign is to be reversed
1131 * @return the number with a reversed sign
1132 */
1133 @NonNull
1134 public static INumericItem opNumericUnaryMinus(
1135 @NonNull INumericItem item) {
1136 INumericItem retval;
1137 if (item instanceof IIntegerItem) {
1138 retval = ((IIntegerItem) item).negate();
1139 } else if (item instanceof IDecimalItem) {
1140 retval = ((IDecimalItem) item).negate();
1141 } else {
1142 throw new InvalidTypeMetapathException(
1143 item,
1144 String.format("Unsupported numeric type '%s'.", item.getClass()));
1145 }
1146 return retval;
1147 }
1148
1149 /**
1150 * Based on XPath 3.1 <a href=
1151 * "https://www.w3.org/TR/xpath-functions-31/#func-numeric-equal">op:numeric-equal</a>.
1152 *
1153 * @param arg1
1154 * the first number to check for equality
1155 * @param arg2
1156 * the second number to check for equality
1157 * @return {@code true} if the numbers are numerically equal or {@code false}
1158 * otherwise
1159 */
1160 @NonNull
1161 public static IBooleanItem opNumericEqual(@Nullable INumericItem arg1, @Nullable INumericItem arg2) {
1162 IBooleanItem retval;
1163 if (arg1 == null || arg2 == null) {
1164 retval = IBooleanItem.FALSE;
1165 } else if (arg1 instanceof IIntegerItem && arg2 instanceof IIntegerItem) {
1166 retval = IBooleanItem.valueOf(arg1.asInteger().equals(arg2.asInteger()));
1167 } else {
1168 retval = IBooleanItem.valueOf(arg1.asDecimal().compareTo(arg2.asDecimal()) == 0);
1169 }
1170 return retval;
1171 }
1172
1173 /**
1174 * Based on XPath 3.1 <a href=
1175 * "https://www.w3.org/TR/xpath-functions-31/#func-numeric-greater-than">op:numeric-greater-than</a>.
1176 *
1177 * @param arg1
1178 * the first number to check
1179 * @param arg2
1180 * the second number to check
1181 * @return {@code true} if the first number is greater than or equal to the
1182 * second, or {@code false} otherwise
1183 */
1184 @NonNull
1185 public static IBooleanItem opNumericGreaterThan(@Nullable INumericItem arg1, @Nullable INumericItem arg2) {
1186 return opNumericLessThan(arg2, arg1);
1187 }
1188
1189 /**
1190 * Based on XPath 3.1 <a href=
1191 * "https://www.w3.org/TR/xpath-functions-31/#func-numeric-less-than">op:numeric-less-than</a>.
1192 *
1193 * @param arg1
1194 * the first number to check
1195 * @param arg2
1196 * the second number to check
1197 * @return {@code true} if the first number is less than or equal to the second,
1198 * or {@code false} otherwise
1199 */
1200 @NonNull
1201 public static IBooleanItem opNumericLessThan(@Nullable INumericItem arg1, @Nullable INumericItem arg2) {
1202 IBooleanItem retval;
1203 if (arg1 == null || arg2 == null) {
1204 retval = IBooleanItem.FALSE;
1205 } else if (arg1 instanceof IIntegerItem && arg2 instanceof IIntegerItem) {
1206 int result = arg1.asInteger().compareTo(arg2.asInteger());
1207 retval = IBooleanItem.valueOf(result < 0);
1208 } else {
1209 int result = arg1.asDecimal().compareTo(arg2.asDecimal());
1210 retval = IBooleanItem.valueOf(result < 0);
1211 }
1212 return retval;
1213 }
1214
1215 /**
1216 * Based on XPath 3.1 <a href=
1217 * "https://www.w3.org/TR/xpath-functions-31/#func-boolean-equal">op:boolean-equal</a>.
1218 *
1219 * @param arg1
1220 * the first boolean to check
1221 * @param arg2
1222 * the second boolean to check
1223 * @return {@code true} if the first boolean is equal to the second, or
1224 * {@code false} otherwise
1225 */
1226 @NonNull
1227 public static IBooleanItem opBooleanEqual(@Nullable IBooleanItem arg1, @Nullable IBooleanItem arg2) {
1228 boolean left = arg1 != null && arg1.toBoolean();
1229 boolean right = arg2 != null && arg2.toBoolean();
1230
1231 return IBooleanItem.valueOf(left == right);
1232 }
1233
1234 /**
1235 * Based on XPath 3.1 <a href=
1236 * "https://www.w3.org/TR/xpath-functions-31/#func-boolean-greater-than">op:boolean-greater-than</a>.
1237 *
1238 * @param arg1
1239 * the first boolean to check
1240 * @param arg2
1241 * the second boolean to check
1242 * @return {@code true} if the first argument is {@link IBooleanItem#TRUE} and
1243 * the second is {@link IBooleanItem#FALSE}, or {@code false} otherwise
1244 */
1245 @NonNull
1246 public static IBooleanItem opBooleanGreaterThan(@Nullable IBooleanItem arg1, @Nullable IBooleanItem arg2) {
1247 return opBooleanLessThan(arg2, arg1);
1248 }
1249
1250 /**
1251 * Based on XPath 3.1 <a href=
1252 * "https://www.w3.org/TR/xpath-functions-31/#func-boolean-less-than">op:boolean-less-than</a>.
1253 *
1254 * @param arg1
1255 * the first boolean to check
1256 * @param arg2
1257 * the second boolean to check
1258 * @return {@code true} if the first argument is {@link IBooleanItem#FALSE} and
1259 * the second is {@link IBooleanItem#TRUE}, or {@code false} otherwise
1260 */
1261 @NonNull
1262 public static IBooleanItem opBooleanLessThan(@Nullable IBooleanItem arg1, @Nullable IBooleanItem arg2) {
1263 boolean left = arg1 != null && arg1.toBoolean();
1264 boolean right = arg2 != null && arg2.toBoolean();
1265
1266 return IBooleanItem.valueOf(!left && right);
1267 }
1268
1269 /**
1270 * Based on XPath 3.1 <a href=
1271 * "https://www.w3.org/TR/xpath-functions-31/#func-same-key">op:same-key</a>.
1272 *
1273 * @param k1
1274 * the first key to compare
1275 * @param k2
1276 * the second key to compare
1277 * @param dynamicContext
1278 * used to get the implicit timezone from the evaluation context
1279 * @return {@code true} if the compared keys are the same, or {@code false}
1280 * otherwise
1281 */
1282 public static boolean opSameKey(
1283 @NonNull IAnyAtomicItem k1,
1284 @NonNull IAnyAtomicItem k2,
1285 @NonNull DynamicContext dynamicContext) {
1286 boolean retval;
1287 if ((k1 instanceof IStringItem || k1 instanceof IAnyUriItem || k1 instanceof IUntypedAtomicItem)
1288 && (k2 instanceof IStringItem || k2 instanceof IAnyUriItem || k2 instanceof IUntypedAtomicItem)) {
1289 retval = k1.asString().equals(k2.asString());
1290 } else if (k1 instanceof IDecimalItem && k2 instanceof IDecimalItem) {
1291 retval = ((IDecimalItem) k1).asDecimal().equals(((IDecimalItem) k2).asDecimal());
1292 } else {
1293 retval = k1.deepEquals(k2, dynamicContext);
1294 }
1295 return retval;
1296 }
1297 }