1
2
3
4
5
6 package dev.metaschema.core.metapath.function;
7
8 import java.util.Locale;
9
10 import dev.metaschema.core.metapath.DynamicContext;
11 import dev.metaschema.core.metapath.function.impl.OperationFunctions;
12 import dev.metaschema.core.metapath.function.library.FnNot;
13 import dev.metaschema.core.metapath.item.ISequence;
14 import dev.metaschema.core.metapath.item.atomic.IAnyAtomicItem;
15 import dev.metaschema.core.metapath.item.atomic.IBase64BinaryItem;
16 import dev.metaschema.core.metapath.item.atomic.IBooleanItem;
17 import dev.metaschema.core.metapath.item.atomic.IDateItem;
18 import dev.metaschema.core.metapath.item.atomic.IDateTimeItem;
19 import dev.metaschema.core.metapath.item.atomic.IDayTimeDurationItem;
20 import dev.metaschema.core.metapath.item.atomic.IDecimalItem;
21 import dev.metaschema.core.metapath.item.atomic.IDurationItem;
22 import dev.metaschema.core.metapath.item.atomic.INumericItem;
23 import dev.metaschema.core.metapath.item.atomic.IStringItem;
24 import dev.metaschema.core.metapath.item.atomic.ITimeItem;
25 import dev.metaschema.core.metapath.item.atomic.IUntypedAtomicItem;
26 import dev.metaschema.core.metapath.item.atomic.IYearMonthDurationItem;
27 import dev.metaschema.core.metapath.type.InvalidTypeMetapathException;
28 import edu.umd.cs.findbugs.annotations.NonNull;
29 import edu.umd.cs.findbugs.annotations.Nullable;
30
31
32
33
34
35
36
37
38
39
40 @SuppressWarnings({ "PMD.GodClass", "PMD.CyclomaticComplexity" })
41 public final class ComparisonFunctions {
42
43
44
45 public enum Operator {
46
47
48
49 EQ,
50
51
52
53 NE,
54
55
56
57 LT,
58
59
60
61 LE,
62
63
64
65 GT,
66
67
68
69 GE;
70 }
71
72 private ComparisonFunctions() {
73
74 }
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89 @NonNull
90 public static IBooleanItem valueCompairison(
91 @NonNull IAnyAtomicItem leftItem,
92 @NonNull Operator operator,
93 @NonNull IAnyAtomicItem rightItem,
94 @Nullable DynamicContext dynamicContext) {
95 return compare(leftItem, operator, rightItem, dynamicContext);
96 }
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111 @NonNull
112 public static IBooleanItem generalComparison(
113 @NonNull ISequence<? extends IAnyAtomicItem> leftItems,
114 @NonNull Operator operator,
115 @NonNull ISequence<? extends IAnyAtomicItem> rightItems,
116 @NonNull DynamicContext dynamicContext) {
117
118 IBooleanItem retval = IBooleanItem.FALSE;
119 for (IAnyAtomicItem left : leftItems) {
120 assert left != null;
121 for (IAnyAtomicItem right : rightItems) {
122 assert right != null;
123 IAnyAtomicItem leftCast;
124 IAnyAtomicItem rightCast;
125 if (left instanceof IUntypedAtomicItem) {
126 if (right instanceof IUntypedAtomicItem) {
127 leftCast = IStringItem.cast(left);
128 rightCast = IStringItem.cast(right);
129 } else {
130 leftCast = applyGeneralComparisonCast(right, left);
131 rightCast = right;
132 }
133 } else if (right instanceof IUntypedAtomicItem) {
134 leftCast = left;
135 rightCast = applyGeneralComparisonCast(left, right);
136 } else {
137 leftCast = left;
138 rightCast = right;
139 }
140
141 assert leftCast != null;
142 IBooleanItem result = compare(leftCast, operator, rightCast, dynamicContext);
143 if (IBooleanItem.TRUE.equals(result)) {
144 retval = IBooleanItem.TRUE;
145 }
146 }
147 }
148 return retval;
149 }
150
151
152
153
154
155
156
157
158
159
160
161 @NonNull
162 private static IAnyAtomicItem applyGeneralComparisonCast(
163 @NonNull IAnyAtomicItem item,
164 @NonNull IAnyAtomicItem other) {
165 IAnyAtomicItem retval;
166 if (item instanceof INumericItem) {
167 retval = IDecimalItem.cast(other);
168 } else if (item instanceof IDayTimeDurationItem) {
169 retval = IDayTimeDurationItem.cast(other);
170 } else if (item instanceof IYearMonthDurationItem) {
171 retval = IYearMonthDurationItem.cast(other);
172 } else {
173 retval = item.castAsType(other);
174 }
175 return retval;
176 }
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192 @NonNull
193 public static IBooleanItem compare(
194 @NonNull IAnyAtomicItem left,
195 @NonNull Operator operator,
196 @NonNull IAnyAtomicItem right,
197 @Nullable DynamicContext dynamicContext) {
198 @NonNull
199 IBooleanItem retval;
200 if (left instanceof IStringItem || right instanceof IStringItem) {
201 retval = stringCompare(IStringItem.cast(left), operator, IStringItem.cast(right));
202 } else if (left instanceof INumericItem && right instanceof INumericItem) {
203 retval = numericCompare((INumericItem) left, operator, (INumericItem) right);
204 } else if (left instanceof IBooleanItem && right instanceof IBooleanItem) {
205 retval = booleanCompare((IBooleanItem) left, operator, (IBooleanItem) right);
206 } else if (left instanceof IDateTimeItem && right instanceof IDateTimeItem) {
207 retval = dateTimeCompare((IDateTimeItem) left, operator, (IDateTimeItem) right, dynamicContext);
208 } else if (left instanceof IDateItem && right instanceof IDateItem) {
209 retval = dateTimeCompare(
210 ((IDateItem) left).asDateTime(),
211 operator,
212 ((IDateItem) right).asDateTime(),
213 dynamicContext);
214 } else if (left instanceof ITimeItem && right instanceof ITimeItem) {
215 retval = dateTimeCompare(
216 IDateTimeItem.valueOf(OperationFunctions.referenceDate(), (ITimeItem) left),
217 operator,
218 IDateTimeItem.valueOf(OperationFunctions.referenceDate(), (ITimeItem) right),
219 dynamicContext);
220 } else if (left instanceof IDurationItem && right instanceof IDurationItem) {
221 retval = durationCompare((IDurationItem) left, operator, (IDurationItem) right);
222 } else if (left instanceof IBase64BinaryItem && right instanceof IBase64BinaryItem) {
223 retval = binaryCompare((IBase64BinaryItem) left, operator, (IBase64BinaryItem) right);
224 } else {
225 throw new InvalidTypeMetapathException(
226 null,
227 String.format("invalid types for comparison: %s %s %s", left.getClass().getName(),
228 operator.name().toLowerCase(Locale.ROOT), right.getClass().getName()));
229 }
230 return retval;
231 }
232
233
234
235
236
237
238
239
240
241
242
243
244
245 @NonNull
246 public static IBooleanItem stringCompare(
247 @NonNull IStringItem left,
248 @NonNull Operator operator,
249 @NonNull IStringItem right) {
250 int result = left.compareTo(right);
251 Boolean retval = null;
252 switch (operator) {
253 case EQ:
254 retval = result == 0;
255 break;
256 case GE:
257 retval = result >= 0;
258 break;
259 case GT:
260 retval = result > 0;
261 break;
262 case LE:
263 retval = result <= 0;
264 break;
265 case LT:
266 retval = result < 0;
267 break;
268 case NE:
269 retval = result != 0;
270 break;
271 }
272
273 assert retval != null : String.format("Unsupported operator '%s'", operator.name());
274
275 return IBooleanItem.valueOf(retval);
276 }
277
278
279
280
281
282
283
284
285
286
287
288
289
290 @NonNull
291 public static IBooleanItem numericCompare(@NonNull INumericItem left, @NonNull Operator operator,
292 @NonNull INumericItem right) {
293 IBooleanItem retval = null;
294 switch (operator) {
295 case EQ:
296 retval = OperationFunctions.opNumericEqual(left, right);
297 break;
298 case GE: {
299 IBooleanItem gt = OperationFunctions.opNumericGreaterThan(left, right);
300 IBooleanItem eq = OperationFunctions.opNumericEqual(left, right);
301 retval = IBooleanItem.valueOf(gt.toBoolean() || eq.toBoolean());
302 break;
303 }
304 case GT:
305 retval = OperationFunctions.opNumericGreaterThan(left, right);
306 break;
307 case LE: {
308 IBooleanItem lt = OperationFunctions.opNumericLessThan(left, right);
309 IBooleanItem eq = OperationFunctions.opNumericEqual(left, right);
310 retval = IBooleanItem.valueOf(lt.toBoolean() || eq.toBoolean());
311 break;
312 }
313 case LT:
314 retval = OperationFunctions.opNumericLessThan(left, right);
315 break;
316 case NE:
317 retval = FnNot.fnNot(OperationFunctions.opNumericEqual(left, right));
318 break;
319 }
320 assert retval != null : String.format("Unsupported operator '%s'", operator.name());
321
322 return retval;
323 }
324
325
326
327
328
329
330
331
332
333
334
335
336
337 @NonNull
338 public static IBooleanItem booleanCompare(
339 @NonNull IBooleanItem left,
340 @NonNull Operator operator,
341 @NonNull IBooleanItem right) {
342 IBooleanItem retval = null;
343 switch (operator) {
344 case EQ:
345 retval = OperationFunctions.opBooleanEqual(left, right);
346 break;
347 case GE: {
348 IBooleanItem gt = OperationFunctions.opBooleanGreaterThan(left, right);
349 IBooleanItem eq = OperationFunctions.opBooleanEqual(left, right);
350 retval = IBooleanItem.valueOf(gt.toBoolean() || eq.toBoolean());
351 break;
352 }
353 case GT:
354 retval = OperationFunctions.opBooleanGreaterThan(left, right);
355 break;
356 case LE: {
357 IBooleanItem lt = OperationFunctions.opBooleanLessThan(left, right);
358 IBooleanItem eq = OperationFunctions.opBooleanEqual(left, right);
359 retval = IBooleanItem.valueOf(lt.toBoolean() || eq.toBoolean());
360 break;
361 }
362 case LT:
363 retval = OperationFunctions.opBooleanLessThan(left, right);
364 break;
365 case NE:
366 retval = FnNot.fnNot(OperationFunctions.opBooleanEqual(left, right));
367 break;
368 }
369 assert retval != null : String.format("Unsupported operator '%s'", operator.name());
370
371 return retval;
372 }
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388 @NonNull
389 public static IBooleanItem dateTimeCompare(
390 @NonNull IDateTimeItem left,
391 @NonNull Operator operator,
392 @NonNull IDateTimeItem right,
393 @Nullable DynamicContext dynamicContext) {
394 IBooleanItem retval = null;
395 switch (operator) {
396 case EQ:
397 retval = OperationFunctions.opDateTimeEqual(left, right, dynamicContext);
398 break;
399 case GE: {
400
401 IDateTimeItem leftNormalized = dynamicContext == null ? left : left.normalize(dynamicContext);
402 IDateTimeItem rightNormalized = dynamicContext == null ? right : right.normalize(dynamicContext);
403 retval = IBooleanItem.valueOf(
404 OperationFunctions.opDateTimeGreaterThan(leftNormalized, rightNormalized, null).toBoolean()
405 || OperationFunctions.opDateTimeEqual(leftNormalized, rightNormalized, null).toBoolean());
406 break;
407 }
408 case GT:
409 retval = OperationFunctions.opDateTimeGreaterThan(left, right, dynamicContext);
410 break;
411 case LE: {
412
413 IDateTimeItem leftNormalized = dynamicContext == null ? left : left.normalize(dynamicContext);
414 IDateTimeItem rightNormalized = dynamicContext == null ? right : right.normalize(dynamicContext);
415 retval = IBooleanItem.valueOf(
416 OperationFunctions.opDateTimeLessThan(leftNormalized, rightNormalized, null).toBoolean()
417 || OperationFunctions.opDateTimeEqual(leftNormalized, rightNormalized, null).toBoolean());
418 break;
419 }
420 case LT:
421 retval = OperationFunctions.opDateTimeLessThan(left, right, dynamicContext);
422 break;
423 case NE:
424 retval = FnNot.fnNot(OperationFunctions.opDateTimeEqual(left, right, dynamicContext));
425 break;
426 }
427 assert retval != null : String.format("Unsupported operator '%s'", operator.name());
428
429 return retval;
430 }
431
432
433
434
435
436
437
438
439
440
441
442
443
444 @NonNull
445 public static IBooleanItem durationCompare(
446 @NonNull IDurationItem left,
447 @NonNull Operator operator,
448 @NonNull IDurationItem right) {
449 IBooleanItem retval = null;
450 switch (operator) {
451 case EQ:
452 retval = OperationFunctions.opDurationEqual(left, right);
453 break;
454 case GE:
455 if (left instanceof IYearMonthDurationItem && right instanceof IYearMonthDurationItem) {
456 IBooleanItem gt = OperationFunctions.opYearMonthDurationGreaterThan(
457 (IYearMonthDurationItem) left,
458 (IYearMonthDurationItem) right);
459 IBooleanItem eq = OperationFunctions.opDurationEqual(left, right);
460 retval = IBooleanItem.valueOf(gt.toBoolean() || eq.toBoolean());
461 } else if (left instanceof IDayTimeDurationItem && right instanceof IDayTimeDurationItem) {
462 IBooleanItem gt = OperationFunctions.opDayTimeDurationGreaterThan(
463 (IDayTimeDurationItem) left,
464 (IDayTimeDurationItem) right);
465 IBooleanItem eq = OperationFunctions.opDurationEqual(left, right);
466 retval = IBooleanItem.valueOf(gt.toBoolean() || eq.toBoolean());
467 }
468 break;
469 case GT:
470 if (left instanceof IYearMonthDurationItem && right instanceof IYearMonthDurationItem) {
471 retval = OperationFunctions.opYearMonthDurationGreaterThan(
472 (IYearMonthDurationItem) left,
473 (IYearMonthDurationItem) right);
474 } else if (left instanceof IDayTimeDurationItem && right instanceof IDayTimeDurationItem) {
475 retval = OperationFunctions.opDayTimeDurationGreaterThan(
476 (IDayTimeDurationItem) left,
477 (IDayTimeDurationItem) right);
478 }
479 break;
480 case LE:
481 if (left instanceof IYearMonthDurationItem && right instanceof IYearMonthDurationItem) {
482 IBooleanItem lt = OperationFunctions.opYearMonthDurationLessThan(
483 (IYearMonthDurationItem) left,
484 (IYearMonthDurationItem) right);
485 IBooleanItem eq = OperationFunctions.opDurationEqual(left, right);
486 retval = IBooleanItem.valueOf(lt.toBoolean() || eq.toBoolean());
487 } else if (left instanceof IDayTimeDurationItem && right instanceof IDayTimeDurationItem) {
488 IBooleanItem lt = OperationFunctions.opDayTimeDurationLessThan(
489 (IDayTimeDurationItem) left,
490 (IDayTimeDurationItem) right);
491 IBooleanItem eq = OperationFunctions.opDurationEqual(left, right);
492 retval = IBooleanItem.valueOf(lt.toBoolean() || eq.toBoolean());
493 }
494 break;
495 case LT:
496 if (left instanceof IYearMonthDurationItem && right instanceof IYearMonthDurationItem) {
497 retval = OperationFunctions.opYearMonthDurationLessThan(
498 (IYearMonthDurationItem) left,
499 (IYearMonthDurationItem) right);
500 } else if (left instanceof IDayTimeDurationItem && right instanceof IDayTimeDurationItem) {
501 retval = OperationFunctions.opDayTimeDurationLessThan(
502 (IDayTimeDurationItem) left,
503 (IDayTimeDurationItem) right);
504 }
505 break;
506 case NE:
507 retval = FnNot.fnNot(OperationFunctions.opDurationEqual(left, right));
508 break;
509 }
510
511 if (retval == null) {
512 throw new InvalidTypeMetapathException(
513 null,
514 String.format("The item types '%s' and '%s' are not comparable",
515 left.getClass().getName(),
516 right.getClass().getName()));
517 }
518 return retval;
519 }
520
521
522
523
524
525
526
527
528
529
530
531
532
533 @NonNull
534 public static IBooleanItem binaryCompare(@NonNull IBase64BinaryItem left, @NonNull Operator operator,
535 @NonNull IBase64BinaryItem right) {
536 IBooleanItem retval = null;
537 switch (operator) {
538 case EQ:
539 retval = OperationFunctions.opBase64BinaryEqual(left, right);
540 break;
541 case GE: {
542 IBooleanItem gt = OperationFunctions.opBase64BinaryGreaterThan(left, right);
543 IBooleanItem eq = OperationFunctions.opBase64BinaryEqual(left, right);
544 retval = IBooleanItem.valueOf(gt.toBoolean() || eq.toBoolean());
545 break;
546 }
547 case GT:
548 retval = OperationFunctions.opBase64BinaryGreaterThan(left, right);
549 break;
550 case LE: {
551 IBooleanItem lt = OperationFunctions.opBase64BinaryLessThan(left, right);
552 IBooleanItem eq = OperationFunctions.opBase64BinaryEqual(left, right);
553 retval = IBooleanItem.valueOf(lt.toBoolean() || eq.toBoolean());
554 break;
555 }
556 case LT:
557 retval = OperationFunctions.opBase64BinaryLessThan(left, right);
558 break;
559 case NE:
560 retval = FnNot.fnNot(OperationFunctions.opBase64BinaryEqual(left, right));
561 break;
562 }
563 assert retval != null : String.format("Unsupported operator '%s'", operator.name());
564
565 return retval;
566 }
567 }