1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.commons.jexl3;
19
20 import static org.junit.jupiter.api.Assertions.assertEquals;
21 import static org.junit.jupiter.api.Assertions.assertFalse;
22 import static org.junit.jupiter.api.Assertions.assertInstanceOf;
23 import static org.junit.jupiter.api.Assertions.assertNotNull;
24 import static org.junit.jupiter.api.Assertions.assertThrows;
25 import static org.junit.jupiter.api.Assertions.assertTrue;
26 import static org.junit.jupiter.api.Assertions.fail;
27
28 import java.io.StringWriter;
29 import java.math.MathContext;
30 import java.text.DecimalFormat;
31 import java.text.SimpleDateFormat;
32 import java.time.Instant;
33 import java.util.ArrayList;
34 import java.util.Arrays;
35 import java.util.Calendar;
36 import java.util.Comparator;
37 import java.util.Date;
38 import java.util.HashMap;
39 import java.util.HashSet;
40 import java.util.Iterator;
41 import java.util.List;
42 import java.util.Locale;
43 import java.util.Map;
44 import java.util.Set;
45 import java.util.SortedSet;
46 import java.util.TimeZone;
47 import java.util.TreeSet;
48 import java.util.regex.Pattern;
49
50 import org.apache.commons.jexl3.junit.Asserter;
51 import org.junit.jupiter.api.BeforeEach;
52 import org.junit.jupiter.api.Test;
53
54
55
56
57 @SuppressWarnings({"UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes"})
58 public class ArithmeticOperatorTest extends JexlTestCase {
59 public static class Aggregate {
60 public static int sum(final Iterable<Integer> ii) {
61 int sum = 0;
62 for(final Integer i : ii) {
63 sum += i;
64 }
65 return sum;
66 }
67 private Aggregate() {}
68 }
69 public static class DateArithmetic extends JexlArithmetic {
70 DateArithmetic(final boolean flag) {
71 super(flag);
72 }
73
74 public Object arrayGet(final Date date, final String identifier) {
75 return getDateValue(date, identifier);
76 }
77
78 public Object arraySet(final Date date, final String identifier, final Object value) throws Exception {
79 return setDateValue(date, identifier, value);
80 }
81
82 protected Object getDateValue(final Date date, final String key) {
83 try {
84 final Calendar cal = Calendar.getInstance(UTC);
85 cal.setTime(date);
86 switch (key) {
87 case "yyyy":
88 return cal.get(Calendar.YEAR);
89 case "MM":
90 return cal.get(Calendar.MONTH) + 1;
91 case "dd":
92 return cal.get(Calendar.DAY_OF_MONTH);
93 default:
94 break;
95 }
96
97 final SimpleDateFormat df = new SimpleDateFormat(key);
98 return df.format(date);
99
100 } catch (final Exception ex) {
101 return null;
102 }
103 }
104
105 public Date multiply(final Date d0, final Date d1) {
106 throw new ArithmeticException("unsupported");
107 }
108
109 public Date now() {
110 return new Date(System.currentTimeMillis());
111 }
112
113 public Object propertyGet(final Date date, final String identifier) {
114 return getDateValue(date, identifier);
115 }
116
117 public Object propertySet(final Date date, final String identifier, final Object value) throws Exception {
118 return setDateValue(date, identifier, value);
119 }
120
121 protected Object setDateValue(final Date date, final String key, final Object value) {
122 final Calendar cal = Calendar.getInstance(UTC);
123 cal.setTime(date);
124 switch (key) {
125 case "yyyy":
126 cal.set(Calendar.YEAR, toInteger(value));
127 break;
128 case "MM":
129 cal.set(Calendar.MONTH, toInteger(value) - 1);
130 break;
131 case "dd":
132 cal.set(Calendar.DAY_OF_MONTH, toInteger(value));
133 break;
134 default:
135 break;
136 }
137 date.setTime(cal.getTimeInMillis());
138 return date;
139 }
140 }
141
142 public static class DateContext extends MapContext {
143 private Locale locale = Locale.US;
144
145 public String format(final Date date, final String fmt) {
146 final SimpleDateFormat sdf = new SimpleDateFormat(fmt, locale);
147 sdf.setTimeZone(UTC);
148 return sdf.format(date);
149 }
150
151 public String format(final Number number, final String fmt) {
152 return new DecimalFormat(fmt).format(number);
153 }
154
155 void setLocale(final Locale l10n) {
156 this.locale = l10n;
157 }
158 }
159
160 public static class IterableContainer implements Iterable<Integer> {
161 private final SortedSet<Integer> values;
162
163 public IterableContainer(final int[] is) {
164 values = new TreeSet<>();
165 for (final int value : is) {
166 values.add(value);
167 }
168 }
169
170 public boolean contains(final int i) {
171 return values.contains(i);
172 }
173
174 public boolean contains(final int[] i) {
175 for(final int ii : i) {
176 if (!values.contains(ii)) {
177 return false;
178 }
179 }
180 return true;
181 }
182
183 public boolean endsWith(final int i) {
184 return values.last().equals(i);
185 }
186
187 public boolean endsWith(final int[] i) {
188 final SortedSet<Integer> sw = values.tailSet(values.size() - i.length);
189 int n = 0;
190 for (final Integer value : sw) {
191 if (!value.equals(i[n++])) {
192 return false;
193 }
194 }
195 return true;
196 }
197
198 @Override
199 public Iterator<Integer> iterator() {
200 return values.iterator();
201 }
202
203 public boolean startsWith(final int i) {
204 return values.first().equals(i);
205 }
206 public boolean startsWith(final int[] i) {
207 final SortedSet<Integer> sw = values.headSet(i.length);
208 int n = 0;
209 for (final Integer value : sw) {
210 if (!value.equals(i[n++])) {
211 return false;
212 }
213 }
214 return true;
215 }
216 }
217
218 public static class MatchingContainer {
219 private final Set<Integer> values;
220
221 public MatchingContainer(final int[] is) {
222 values = new HashSet<>();
223 for (final int value : is) {
224 values.add(value);
225 }
226 }
227
228 public boolean contains(final int value) {
229 return values.contains(value);
230 }
231 }
232
233 private static final TimeZone UTC = TimeZone.getTimeZone("UTC");
234
235 private Asserter asserter;
236
237
238
239
240 public ArithmeticOperatorTest() {
241 super("ArithmeticOperatorTest");
242 }
243
244 @BeforeEach
245 @Override
246 public void setUp() {
247 asserter = new Asserter(JEXL);
248 asserter.setStrict(false);
249 }
250
251 @Test void test373a() {
252 testSelfAssignOperators("y.add(x++)", 42, 42, 43);
253 }
254
255 @Test void test373b() {
256 testSelfAssignOperators("y.add(++x)", 42, 43, 43);
257 }
258
259 @Test void test373c() {
260 testSelfAssignOperators("y.add(x--)", 42, 42, 41);
261 }
262
263 @Test void test373d() {
264 testSelfAssignOperators("y.add(--x)", 42, 41, 41);
265 }
266
267 @Test
268 void test391() throws Exception {
269
270 for(final String src : Arrays.asList(
271 "2 =~ [1, 2, 3, 4]",
272 "[2, 3] =~ [1, 2, 3, 4]",
273 "[2, 3,...] =~ [1, 2, 3, 4]",
274 "3 =~ [1, 2, 3, 4,...]",
275 "[2, 3] =~ [1, 2, 3, 4,...]",
276 "[2, 3,...] =~ [1, 2, 3, 4,...]")) {
277 asserter.assertExpression(src, Boolean.TRUE);
278 }
279
280 final int[] ic = {1, 2, 3, 4};
281 final List<Integer> iic = new ArrayList<>();
282 for(final int v : ic) { iic.add(v); }
283 final int[] iv = {2, 3};
284 final List<Integer> iiv = new ArrayList<>();
285 for(final int v : iv) { iiv.add(v); }
286 final String src = "(x,y) -> x =~ y ";
287 for(final Object v : Arrays.asList(iv, iiv, 2)) {
288 for(final Object c : Arrays.asList(ic, iic)) {
289 asserter.assertExpression(src, Boolean.TRUE, v, c);
290 }
291 }
292 }
293
294 @Test
295 void testDateArithmetic() {
296 final Date d = new Date();
297 final JexlContext jc = new MapContext();
298 final JexlEngine jexl = new JexlBuilder().cache(32).arithmetic(new DateArithmetic(true)).create();
299 final JexlScript expr0 = jexl.createScript("date.yyyy = 1969; date.MM=7; date.dd=20; ", "date");
300 Object value0 = expr0.execute(jc, d);
301 assertNotNull(value0);
302 value0 = d;
303
304 assertEquals(1969, jexl.createScript("date.yyyy", "date").execute(jc, value0));
305 assertEquals(7, jexl.createScript("date.MM", "date").execute(jc, value0));
306 assertEquals(20, jexl.createScript("date.dd", "date").execute(jc, value0));
307 }
308
309 @Test
310 void testFormatArithmetic() {
311 final Calendar cal = Calendar.getInstance(UTC);
312 cal.set(1969, Calendar.AUGUST, 20);
313 final Date x0 = cal.getTime();
314 final String y0 = "MM/yy/dd";
315 final Number x1 = 42.12345;
316 final String y1 = "##0.##";
317 final DateContext jc = new DateContext();
318 final JexlEngine jexl = new JexlBuilder().cache(32).arithmetic(new DateArithmetic(true)).create();
319 final JexlScript expr0 = jexl.createScript("x.format(y)", "x", "y");
320 Object value10 = expr0.execute(jc, x0, y0);
321 final Object value20 = expr0.execute(jc, x0, y0);
322 assertEquals(value10, value20);
323 Object value11 = expr0.execute(jc, x1, y1);
324 final Object value21 = expr0.execute(jc, x1, y1);
325 assertEquals(value11, value21);
326 value10 = expr0.execute(jc, x0, y0);
327 assertEquals(value10, value20);
328 value11 = expr0.execute(jc, x1, y1);
329 assertEquals(value11, value21);
330 value10 = expr0.execute(jc, x0, y0);
331 assertEquals(value10, value20);
332 value11 = expr0.execute(jc, x1, y1);
333 assertEquals(value11, value21);
334
335 JexlScript expr1 = jexl.createScript("format(x, y)", "x", "y");
336 value10 = expr1.execute(jc, x0, y0);
337 assertEquals(value10, value20);
338 Object s0 = expr1.execute(jc, x0, "EEE dd MMM yyyy");
339 assertEquals("Wed 20 Aug 1969", s0);
340 jc.setLocale(Locale.FRANCE);
341 s0 = expr1.execute(jc, x0, "EEE dd MMM yyyy");
342 assertEquals("mer. 20 ao\u00fbt 1969", s0);
343
344 expr1 = jexl.createScript("format(now(), y)", "y");
345 final Object n0 = expr1.execute(jc, y0);
346 assertNotNull(n0);
347 expr1 = jexl.createScript("now().format(y)", "y");
348 final Object n1 = expr1.execute(jc, y0);
349 assertNotNull(n0);
350 assertEquals(n0, n1);
351 }
352
353 @Test
354 void testFormatArithmeticJxlt() throws Exception {
355 final Map<String, Object> ns = new HashMap<>();
356 ns.put("calc", Aggregate.class);
357 final Calendar cal = Calendar.getInstance(UTC);
358 cal.set(1969, Calendar.AUGUST, 20);
359 final Date x0 = cal.getTime();
360 final String y0 = "yyyy-MM-dd";
361 final DateContext jc = new DateContext();
362 final JexlEngine jexl = new JexlBuilder().cache(32).namespaces(ns).arithmetic(new DateArithmetic(true)).create();
363 final JxltEngine jxlt = jexl.createJxltEngine();
364
365 JxltEngine.Template expr0 = jxlt.createTemplate("${x.format(y)}", "x", "y");
366 StringWriter strw = new StringWriter();
367 expr0.evaluate(jc, strw, x0, y0);
368 String strws = strw.toString();
369 assertEquals("1969-08-20", strws);
370
371 expr0 = jxlt.createTemplate("${calc:sum(x .. y)}", "x", "y");
372 strw = new StringWriter();
373 expr0.evaluate(jc, strw, 1, 3);
374 strws = strw.toString();
375 assertEquals("6", strws);
376
377 final JxltEngine.Template expr1 = jxlt.createTemplate("${jexl:include(s, x, y)}", "s", "x", "y");
378 strw = new StringWriter();
379 expr1.evaluate(jc, strw, expr0, 1, 3);
380 strws = strw.toString();
381 assertEquals("6", strws);
382
383 expr0 = jxlt.createTemplate("${now().format(y)}", "y");
384 strw = new StringWriter();
385 expr0.evaluate(jc, strw, y0);
386 strws = strw.toString();
387 assertNotNull(strws);
388 }
389
390 @Test
391 void testIncrementOperatorOnNull() {
392 final JexlEngine jexl = new JexlBuilder().strict(false).create();
393 JexlScript script;
394 Object result;
395 script = jexl.createScript("var i = null; ++i");
396 result = script.execute(null);
397 assertEquals(1, result);
398 script = jexl.createScript("var i = null; --i");
399 result = script.execute(null);
400 assertEquals(-1, result);
401 }
402
403 @Test
404 @SuppressWarnings("unchecked")
405 void testInterval() {
406 final Map<String, Object> ns = new HashMap<>();
407 ns.put("calc", Aggregate.class);
408 final JexlEngine jexl = new JexlBuilder().namespaces(ns).create();
409 JexlScript script;
410 Object result;
411
412 script = jexl.createScript("1 .. 3");
413 result = script.execute(null);
414 assertInstanceOf(Iterable.class, result);
415 Iterator<Integer> ii = ((Iterable<Integer>) result).iterator();
416 assertEquals(Integer.valueOf(1), ii.next());
417 assertEquals(Integer.valueOf(2), ii.next());
418 assertEquals(Integer.valueOf(3), ii.next());
419
420 script = jexl.createScript("(4 - 3) .. (9 / 3)");
421 result = script.execute(null);
422 assertInstanceOf(Iterable.class, result);
423 ii = ((Iterable<Integer>) result).iterator();
424 assertEquals(Integer.valueOf(1), ii.next());
425 assertEquals(Integer.valueOf(2), ii.next());
426 assertEquals(Integer.valueOf(3), ii.next());
427
428
429 script = jexl.createScript("var x = 0; for(var y : ((5 - 4) .. (12 / 4))) { x = x + y }; x");
430 result = script.execute(null);
431 assertEquals(Integer.valueOf(6), result);
432
433 script = jexl.createScript("calc:sum(1 .. 3)");
434 result = script.execute(null);
435 assertEquals(Integer.valueOf(6), result);
436
437 script = jexl.createScript("calc:sum(-3 .. 3)");
438 result = script.execute(null);
439 assertEquals(Integer.valueOf(0), result);
440 }
441
442 @Test
443 void testMatch() throws Exception {
444
445 final int[] ai = {2, 4, 42, 54};
446 final List<Integer> al = new ArrayList<>();
447 for (final int i : ai) {
448 al.add(i);
449 }
450 final Map<Integer, String> am = new HashMap<>();
451 am.put(2, "two");
452 am.put(4, "four");
453 am.put(42, "forty-two");
454 am.put(54, "fifty-four");
455 final MatchingContainer ad = new MatchingContainer(ai);
456 final IterableContainer ic = new IterableContainer(ai);
457 final Set<Integer> as = ad.values;
458 final Object[] vars = {ai, al, am, ad, as, ic};
459
460 for (final Object variable : vars) {
461 asserter.setVariable("container", variable);
462 for (final int x : ai) {
463 asserter.setVariable("x", x);
464 asserter.assertExpression("x =~ container", Boolean.TRUE);
465 }
466 asserter.setVariable("x", 169);
467 asserter.assertExpression("x !~ container", Boolean.TRUE);
468 }
469 }
470
471 @Test
472 void testNotStartsEndsWith() throws Exception {
473 asserter.setVariable("x", "foobar");
474 asserter.assertExpression("x !^ 'foo'", Boolean.FALSE);
475 asserter.assertExpression("x !$ 'foo'", Boolean.TRUE);
476 asserter.setVariable("x", "barfoo");
477 asserter.assertExpression("x !^ 'foo'", Boolean.TRUE);
478 asserter.assertExpression("x !$ 'foo'", Boolean.FALSE);
479
480 final int[] ai = {2, 4, 42, 54};
481 final IterableContainer ic = new IterableContainer(ai);
482 asserter.setVariable("x", ic);
483 asserter.assertExpression("x !^ 2", Boolean.FALSE);
484 asserter.assertExpression("x !$ 54", Boolean.FALSE);
485 asserter.assertExpression("x !^ 4", Boolean.TRUE);
486 asserter.assertExpression("x !$ 42", Boolean.TRUE);
487 asserter.assertExpression("x !^ [2, 4]", Boolean.FALSE);
488 asserter.assertExpression("x !^ [42, 54]", Boolean.FALSE);
489 }
490
491 @Test
492 void testNotStartsEndsWithString() throws Exception {
493 asserter.setVariable("x", "foobar");
494 asserter.assertExpression("x !^ 'foo'", Boolean.FALSE);
495 asserter.assertExpression("x !$ 'foo'", Boolean.TRUE);
496 asserter.setVariable("x", "barfoo");
497 asserter.assertExpression("x !^ 'foo'", Boolean.TRUE);
498 asserter.assertExpression("x !$ 'foo'", Boolean.FALSE);
499 }
500
501 @Test
502 void testNotStartsEndsWithStringBuilder() throws Exception {
503 asserter.setVariable("x", new StringBuilder("foobar"));
504 asserter.assertExpression("x !^ 'foo'", Boolean.FALSE);
505 asserter.assertExpression("x !$ 'foo'", Boolean.TRUE);
506 asserter.setVariable("x", new StringBuilder("barfoo"));
507 asserter.assertExpression("x !^ 'foo'", Boolean.TRUE);
508 asserter.assertExpression("x !$ 'foo'", Boolean.FALSE);
509 }
510
511 @Test
512 void testNotStartsEndsWithStringDot() throws Exception {
513 asserter.setVariable("x.y", "foobar");
514 asserter.assertExpression("x.y !^ 'foo'", Boolean.FALSE);
515 asserter.assertExpression("x.y !$ 'foo'", Boolean.TRUE);
516 asserter.setVariable("x.y", "barfoo");
517 asserter.assertExpression("x.y !^ 'foo'", Boolean.TRUE);
518 asserter.assertExpression("x.y !$ 'foo'", Boolean.FALSE);
519 }
520
521 @Test
522 void testOperatorError() throws Exception {
523 runOperatorError(true);
524 runOperatorError(false);
525 }
526
527 private void runOperatorError(final boolean silent) {
528 final CaptureLog log = new CaptureLog();
529 final DateContext jc = new DateContext();
530 final Date d = new Date();
531 final JexlEngine jexl = new JexlBuilder().logger(log).strict(true).silent(silent).cache(32)
532 .arithmetic(new DateArithmetic(true)).create();
533 final JexlScript expr0 = jexl.createScript("date * date", "date");
534 try {
535 final Object value0 = expr0.execute(jc, d);
536 if (!silent) {
537 fail("should have failed");
538 } else {
539 assertEquals(1, log.count("warn"));
540 }
541 } catch (final JexlException.Operator xop) {
542 assertEquals("*", xop.getSymbol());
543 }
544 if (!silent) {
545 assertEquals(0, log.count("warn"));
546 }
547 }
548
549 @Test
550 void testRegexp() throws Exception {
551 asserter.setVariable("str", "abc456");
552 asserter.assertExpression("str =~ '.*456'", Boolean.TRUE);
553 asserter.assertExpression("str !~ 'ABC.*'", Boolean.TRUE);
554 asserter.setVariable("match", "abc.*");
555 asserter.setVariable("nomatch", ".*123");
556 asserter.assertExpression("str =~ match", Boolean.TRUE);
557 asserter.assertExpression("str !~ match", Boolean.FALSE);
558 asserter.assertExpression("str !~ nomatch", Boolean.TRUE);
559 asserter.assertExpression("str =~ nomatch", Boolean.FALSE);
560 asserter.setVariable("match", new StringBuilder("abc.*"));
561 asserter.setVariable("nomatch", new StringBuilder(".*123"));
562 asserter.assertExpression("str =~ match", Boolean.TRUE);
563 asserter.assertExpression("str !~ match", Boolean.FALSE);
564 asserter.assertExpression("str !~ nomatch", Boolean.TRUE);
565 asserter.assertExpression("str =~ nomatch", Boolean.FALSE);
566 asserter.setVariable("match", java.util.regex.Pattern.compile("abc.*"));
567 asserter.setVariable("nomatch", java.util.regex.Pattern.compile(".*123"));
568 asserter.assertExpression("str =~ match", Boolean.TRUE);
569 asserter.assertExpression("str !~ match", Boolean.FALSE);
570 asserter.assertExpression("str !~ nomatch", Boolean.TRUE);
571 asserter.assertExpression("str =~ nomatch", Boolean.FALSE);
572
573 asserter.assertExpression("'a' =~ ['a','b','c','d','e','f']", Boolean.TRUE);
574 asserter.assertExpression("'a' !~ ['a','b','c','d','e','f']", Boolean.FALSE);
575 asserter.assertExpression("'z' =~ ['a','b','c','d','e','f']", Boolean.FALSE);
576 asserter.assertExpression("'z' !~ ['a','b','c','d','e','f']", Boolean.TRUE);
577 }
578
579 @Test
580 void testRegexp2() throws Exception {
581 asserter.setVariable("str", "abc456");
582 asserter.assertExpression("str =~ ~/.*456/", Boolean.TRUE);
583 asserter.assertExpression("str !~ ~/ABC.*/", Boolean.TRUE);
584 asserter.assertExpression("str =~ ~/abc\\d{3}/", Boolean.TRUE);
585
586 asserter.assertExpression("matches(str, ~/.*456/)", Boolean.TRUE);
587 asserter.setVariable("str", "4/6");
588 asserter.assertExpression("str =~ ~/\\d\\/\\d/", Boolean.TRUE);
589 }
590 void testSelfAssignOperators(final String text, final int x, final int y0, final int x0) {
591
592 final JexlEngine jexl = new JexlBuilder().safe(true).create();
593 final JexlScript script = jexl.createScript(text);
594 final JexlContext context = new MapContext();
595 context.set("x", x);
596 final List<Number> y = new ArrayList<>();
597 context.set("y", y);
598 final Object result = script.execute(context);
599 assertEquals(x0, context.get("x"), "x0");
600 assertEquals(y0, y.get(0), "y0");
601 }
602 @Test
603 void testStartsEndsWith() throws Exception {
604 asserter.setVariable("x", "foobar");
605 asserter.assertExpression("x =^ 'foo'", Boolean.TRUE);
606 asserter.assertExpression("x =$ 'foo'", Boolean.FALSE);
607 asserter.setVariable("x", "barfoo");
608 asserter.assertExpression("x =^ 'foo'", Boolean.FALSE);
609 asserter.assertExpression("x =$ 'foo'", Boolean.TRUE);
610
611 final int[] ai = {2, 4, 42, 54};
612 final IterableContainer ic = new IterableContainer(ai);
613 asserter.setVariable("x", ic);
614 asserter.assertExpression("x =^ 2", Boolean.TRUE);
615 asserter.assertExpression("x =$ 54", Boolean.TRUE);
616 asserter.assertExpression("x =^ 4", Boolean.FALSE);
617 asserter.assertExpression("x =$ 42", Boolean.FALSE);
618 asserter.assertExpression("x =^ [2, 4]", Boolean.TRUE);
619 asserter.assertExpression("x =^ [42, 54]", Boolean.TRUE);
620 }
621 @Test
622 void testStartsEndsWithString() throws Exception {
623 asserter.setVariable("x", "foobar");
624 asserter.assertExpression("x =^ 'foo'", Boolean.TRUE);
625 asserter.assertExpression("x =$ 'foo'", Boolean.FALSE);
626 asserter.setVariable("x", "barfoo");
627 asserter.assertExpression("x =^ 'foo'", Boolean.FALSE);
628 asserter.assertExpression("x =$ 'foo'", Boolean.TRUE);
629 }
630
631 @Test
632 void testStartsEndsWithStringBuilder() throws Exception {
633 asserter.setVariable("x", new StringBuilder("foobar"));
634 asserter.assertExpression("x =^ 'foo'", Boolean.TRUE);
635 asserter.assertExpression("x =$ 'foo'", Boolean.FALSE);
636 asserter.setVariable("x", new StringBuilder("barfoo"));
637 asserter.assertExpression("x =^ 'foo'", Boolean.FALSE);
638 asserter.assertExpression("x =$ 'foo'", Boolean.TRUE);
639 }
640
641 @Test
642 void testStartsEndsWithStringDot() throws Exception {
643 asserter.setVariable("x.y", "foobar");
644 asserter.assertExpression("x.y =^ 'foo'", Boolean.TRUE);
645 asserter.assertExpression("x.y =$ 'foo'", Boolean.FALSE);
646 asserter.setVariable("x.y", "barfoo");
647 asserter.assertExpression("x.y =^ 'foo'", Boolean.FALSE);
648 asserter.assertExpression("x.y =$ 'foo'", Boolean.TRUE);
649 }
650
651
652
653
654
655 private static class PropertyComparator implements JexlCache.Reference, Comparator<Object> {
656 private final JexlContext context = JexlEngine.getThreadContext();
657 private final JexlArithmetic arithmetic;
658 private final JexlOperator.Uberspect operator;
659 private final JexlScript expr;
660 private Object cache;
661
662 PropertyComparator(JexlArithmetic jexla, JexlScript expr) {
663 this.arithmetic = jexla;
664 this.operator = JexlEngine.getThreadEngine().getUberspect().getOperator(arithmetic);
665 this.expr = expr;
666 }
667 @Override
668 public int compare(Object o1, Object o2) {
669 final Object left = expr.execute(context, o1);
670 final Object right = expr.execute(context, o2);
671 Object result = operator.tryOverload(this, JexlOperator.COMPARE, left, right);
672 if (result instanceof Integer) {
673 return (int) result;
674 }
675 return arithmetic.compare(left, right, JexlOperator.COMPARE);
676 }
677
678 @Override
679 public Object getCache() {
680 return cache;
681 }
682
683 @Override
684 public void setCache(Object cache) {
685 this.cache = cache;
686 }
687 }
688
689 public static class SortingArithmetic extends JexlArithmetic {
690 public SortingArithmetic(boolean strict) {
691 this(strict, null, Integer.MIN_VALUE);
692 }
693
694 private SortingArithmetic(boolean strict, MathContext context, int scale) {
695 super(strict, context, scale);
696 }
697
698 public int compare(Integer left, Integer right) {
699 return left.compareTo(right);
700 }
701
702 public int compare(String left, String right) {
703 return left.compareTo(right);
704 }
705
706
707
708
709
710
711
712 public void sort(final Object[] array, final JexlScript expr) {
713 Arrays.sort(array, new PropertyComparator(this, expr));
714 }
715 }
716
717 @Test
718 void testSortArray() {
719 final JexlEngine jexl = new JexlBuilder()
720 .cache(32)
721 .arithmetic(new SortingArithmetic(true))
722 .silent(false).create();
723
724 final String src = "[{'id':1,'name':'John','type':9},{'id':2,'name':'Doe','type':7},{'id':3,'name':'Doe','type':10}]";
725 final Object a = jexl.createExpression(src).evaluate(null);
726 assertNotNull(a);
727
728 final Map[] m = (Map[]) a;
729 assertEquals(9, m[0].get("type"));
730 assertEquals(7, m[1].get("type"));
731
732 jexl.createScript("array.sort( e -> e.type )", "array").execute(null, a);
733
734 assertEquals(7, m[0].get("type"));
735 assertEquals(9, m[1].get("type"));
736 }
737
738 public static class MatchingArithmetic extends JexlArithmetic {
739 public MatchingArithmetic(final boolean astrict) {
740 super(astrict);
741 }
742
743 public boolean contains(final Pattern[] container, final String str) {
744 for(final Pattern pattern : container) {
745 if (pattern.matcher(str).matches()) {
746 return true;
747 }
748 }
749 return false;
750 }
751 }
752
753 @Test
754 void testPatterns() {
755 final JexlEngine jexl = new JexlBuilder().arithmetic(new MatchingArithmetic(true)).create();
756 final JexlScript script = jexl.createScript("str =~ [~/abc.*/, ~/def.*/]", "str");
757 assertTrue((boolean) script.execute(null, "abcdef"));
758 assertTrue((boolean) script.execute(null, "defghi"));
759 assertFalse((boolean) script.execute(null, "ghijkl"));
760 }
761
762 public static class Arithmetic428 extends JexlArithmetic {
763 public Arithmetic428(boolean strict) {
764 this(strict, null, Integer.MIN_VALUE);
765 }
766
767 private Arithmetic428(boolean strict, MathContext context, int scale) {
768 super(strict, context, scale);
769 }
770
771 public int compare(Instant lhs, String str) {
772 Instant rhs = Instant.parse(str);
773 return lhs.compareTo(rhs);
774 }
775 }
776
777 static final List<Integer> LOOPS = new ArrayList<>(Arrays.asList(0, 1));
778
779 @Test
780 void test428() {
781
782 final JexlEngine jexl = new JexlBuilder().cache(32).arithmetic(new Arithmetic428(true)).create();
783 final String rhsstr ="2024-09-09T10:42:42.00Z";
784 final Instant rhs = Instant.parse(rhsstr);
785 final String lhs = "2020-09-09T01:24:24.00Z";
786 JexlScript script;
787 script = jexl.createScript("x < y", "x", "y");
788 final JexlScript s0 = script;
789 assertThrows(JexlException.class, () -> s0.execute(null, 42, rhs));
790 for(int i : LOOPS) { assertTrue((boolean) script.execute(null, lhs, rhs)); }
791 for(int i : LOOPS) { assertTrue((boolean) script.execute(null, lhs, rhs)); }
792 for(int i : LOOPS) { assertFalse((boolean) script.execute(null, rhs, lhs)); }
793 for(int i : LOOPS) { assertFalse((boolean) script.execute(null, rhs, lhs)); }
794 for(int i : LOOPS) { assertTrue((boolean) script.execute(null, lhs, rhs)); }
795 for(int i : LOOPS) { assertFalse((boolean) script.execute(null, rhs, lhs)); }
796
797 script = jexl.createScript("x <= y", "x", "y");
798 final JexlScript s1 = script;
799 assertThrows(JexlException.class, () -> s1.execute(null, 42, rhs));
800 assertTrue((boolean) script.execute(null, lhs, rhs));
801 assertFalse((boolean) script.execute(null, rhs, lhs));
802
803 script = jexl.createScript("x >= y", "x", "y");
804 final JexlScript s2 = script;
805 assertThrows(JexlException.class, () -> s2.execute(null, 42, rhs));
806 assertFalse((boolean) script.execute(null, lhs, rhs));
807 assertFalse((boolean) script.execute(null, lhs, rhs));
808 assertTrue((boolean) script.execute(null, rhs, lhs));
809 assertTrue((boolean) script.execute(null, rhs, lhs));
810 assertFalse((boolean) script.execute(null, lhs, rhs));
811 assertTrue((boolean) script.execute(null, rhs, lhs));
812
813 script = jexl.createScript("x > y", "x", "y");
814 final JexlScript s3 = script;
815 assertThrows(JexlException.class, () -> s3.execute(null, 42, rhs));
816 assertFalse((boolean) script.execute(null, lhs, rhs));
817 assertTrue((boolean) script.execute(null, rhs, lhs));
818
819 script = jexl.createScript("x == y", "x", "y");
820 assertFalse((boolean) script.execute(null, 42, rhs));
821 assertFalse((boolean) script.execute(null, lhs, rhs));
822 assertFalse((boolean) script.execute(null, lhs, rhs));
823 assertTrue((boolean) script.execute(null, rhs, rhsstr));
824 assertTrue((boolean) script.execute(null, rhsstr, rhs));
825 assertFalse((boolean) script.execute(null, lhs, rhs));
826
827 script = jexl.createScript("x != y", "x", "y");
828 assertTrue((boolean) script.execute(null, 42, rhs));
829 assertTrue((boolean) script.execute(null, lhs, rhs));
830 assertFalse((boolean) script.execute(null, rhs, rhsstr));
831 }
832
833 public static class Arithmetic429 extends JexlArithmetic {
834 public Arithmetic429(boolean astrict) {
835 super(astrict);
836 }
837
838 public int compare(String lhs, Number rhs) {
839 return lhs.compareTo(rhs.toString());
840 }
841 }
842
843 @Test
844 void test429a() {
845 final JexlEngine jexl = new JexlBuilder()
846 .arithmetic(new Arithmetic429(true))
847 .cache(32)
848 .create();
849 String src;
850 JexlScript script;
851 src = "'1.1' > 0";
852 script = jexl.createScript(src);
853 assertTrue((boolean) script.execute(null));
854 src = "1.2 <= '1.20'";
855 script = jexl.createScript(src);
856 assertTrue((boolean) script.execute(null));
857 src = "1.2 >= '1.2'";
858 script = jexl.createScript(src);
859 assertTrue((boolean) script.execute(null));
860 src = "1.2 < '1.2'";
861 script = jexl.createScript(src);
862 assertFalse((boolean) script.execute(null));
863 src = "1.2 > '1.2'";
864 script = jexl.createScript(src);
865 assertFalse((boolean) script.execute(null));
866 src = "1.20 == 'a'";
867 script = jexl.createScript(src);
868 assertFalse((boolean) script.execute(null));
869 }
870 }